Allow per-app allow/deny.
This commit is contained in:
@@ -19,6 +19,8 @@ GOOGLE_AUTH_SERVICE_NAME="dokku-google-auth"
|
||||
GOOGLE_AUTH_IMAGE="dokku-google-auth:latest"
|
||||
GOOGLE_AUTH_DEFAULT_PORT="2999"
|
||||
GOOGLE_AUTH_ROUTE_PREFIX="/_google-auth"
|
||||
# Where the per-app data directory is bind-mounted inside the service container.
|
||||
GOOGLE_AUTH_APP_CONFIG_MOUNT="/data/apps"
|
||||
|
||||
# Fallbacks so the plugin can be exercised outside a dokku host (tests, dev).
|
||||
if ! declare -f dokku_log_info1 >/dev/null 2>&1; then
|
||||
@@ -73,19 +75,57 @@ fn-ga-global-set-list() {
|
||||
chmod 600 "$file"
|
||||
}
|
||||
|
||||
# Additive counterparts to fn-ga-global-set-list, for the allow/deny commands.
|
||||
fn-ga-global-list-add() {
|
||||
declare KEY="$1" VALUE="$2"
|
||||
mkdir -p "$GOOGLE_AUTH_DATA_ROOT/global"
|
||||
local file="$GOOGLE_AUTH_DATA_ROOT/global/$KEY"
|
||||
touch "$file"
|
||||
chmod 600 "$file"
|
||||
# --- access lists, scoped to "global" or to one app ---
|
||||
#
|
||||
# An app's allow entries replace the global ones for that app; deny entries from
|
||||
# both scopes are combined. The auth service implements that precedence — these
|
||||
# helpers only store the entries.
|
||||
|
||||
fn-ga-list-file() {
|
||||
declare SCOPE="$1" KEY="$2"
|
||||
if [[ "$SCOPE" == "global" ]]; then
|
||||
echo "$GOOGLE_AUTH_DATA_ROOT/global/$KEY"
|
||||
else
|
||||
echo "$(fn-ga-app-dir "$SCOPE")/$KEY"
|
||||
fi
|
||||
}
|
||||
|
||||
fn-ga-list-get() {
|
||||
declare SCOPE="$1" KEY="$2"
|
||||
cat "$(fn-ga-list-file "$SCOPE" "$KEY")" 2>/dev/null || true
|
||||
}
|
||||
|
||||
fn-ga-list-count() {
|
||||
declare SCOPE="$1" KEY="$2"
|
||||
fn-ga-list-get "$SCOPE" "$KEY" | grep -c . || true
|
||||
}
|
||||
|
||||
fn-ga-list-contains() {
|
||||
declare SCOPE="$1" KEY="$2" VALUE="$3"
|
||||
grep -qxF "$VALUE" "$(fn-ga-list-file "$SCOPE" "$KEY")" 2>/dev/null
|
||||
}
|
||||
|
||||
fn-ga-list-add() {
|
||||
declare SCOPE="$1" KEY="$2" VALUE="$3"
|
||||
local file
|
||||
file="$(fn-ga-list-file "$SCOPE" "$KEY")"
|
||||
if [[ "$SCOPE" == "global" ]]; then
|
||||
mkdir -p "$GOOGLE_AUTH_DATA_ROOT/global"
|
||||
touch "$file"
|
||||
chmod 600 "$file"
|
||||
else
|
||||
fn-ga-app-dir-ensure "$SCOPE" >/dev/null
|
||||
touch "$file"
|
||||
# Readable through the service's read-only bind mount; see fn-ga-app-dir-ensure.
|
||||
chmod 644 "$file"
|
||||
fi
|
||||
grep -qxF "$VALUE" "$file" || printf '%s\n' "$VALUE" >>"$file"
|
||||
}
|
||||
|
||||
fn-ga-global-list-remove() {
|
||||
declare KEY="$1" VALUE="$2"
|
||||
local file="$GOOGLE_AUTH_DATA_ROOT/global/$KEY" tmp
|
||||
fn-ga-list-remove() {
|
||||
declare SCOPE="$1" KEY="$2" VALUE="$3"
|
||||
local file tmp
|
||||
file="$(fn-ga-list-file "$SCOPE" "$KEY")"
|
||||
[[ -f "$file" ]] || return 0
|
||||
tmp="$(mktemp)"
|
||||
grep -vxF "$VALUE" "$file" >"$tmp" || true
|
||||
@@ -93,14 +133,88 @@ fn-ga-global-list-remove() {
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
fn-ga-global-list-contains() {
|
||||
declare KEY="$1" VALUE="$2"
|
||||
grep -qxF "$VALUE" "$GOOGLE_AUTH_DATA_ROOT/global/$KEY" 2>/dev/null
|
||||
# Sets GA_SCOPE from a command's first argument: "global" for --global,
|
||||
# otherwise a verified app name. Fails (and exits) on anything else.
|
||||
fn-ga-resolve-scope() {
|
||||
declare CMD="$1" ARG="${2:-}"
|
||||
case "$ARG" in
|
||||
--global)
|
||||
GA_SCOPE=global
|
||||
;;
|
||||
"")
|
||||
dokku_log_fail "usage: dokku $CMD <app>|--global [<entry>...]"
|
||||
;;
|
||||
-*)
|
||||
dokku_log_fail "unknown flag '$ARG' — pass an app name or --global"
|
||||
;;
|
||||
*)
|
||||
verify_app_name "$ARG"
|
||||
GA_SCOPE="$ARG"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
fn-ga-global-list-count() {
|
||||
declare KEY="$1"
|
||||
fn-ga-global-get-list "$KEY" | grep -c . || true
|
||||
# Human-readable scope for log lines: "globally" or "for my-app".
|
||||
fn-ga-scope-label() {
|
||||
declare SCOPE="$1"
|
||||
if [[ "$SCOPE" == "global" ]]; then
|
||||
echo "globally"
|
||||
else
|
||||
echo "for $SCOPE"
|
||||
fi
|
||||
}
|
||||
|
||||
# The scope as it is typed on the command line, for suggested commands.
|
||||
fn-ga-scope-arg() {
|
||||
declare SCOPE="$1"
|
||||
if [[ "$SCOPE" == "global" ]]; then
|
||||
echo "--global"
|
||||
else
|
||||
echo "$SCOPE"
|
||||
fi
|
||||
}
|
||||
|
||||
# Advisory mirror of the service's allow-list precedence (an app's entries
|
||||
# replace the global ones), used only to warn operators. The authority is
|
||||
# emailAllowedFor in internal/authproxy.
|
||||
fn-ga-email-effectively-allowed() {
|
||||
declare SCOPE="$1" EMAIL="$2"
|
||||
local effective="$SCOPE"
|
||||
if [[ "$SCOPE" != "global" ]] &&
|
||||
[[ "$(fn-ga-list-count "$SCOPE" allowed-domains)" -eq 0 &&
|
||||
"$(fn-ga-list-count "$SCOPE" allowed-emails)" -eq 0 ]]; then
|
||||
effective=global
|
||||
fi
|
||||
fn-ga-list-contains "$effective" allowed-emails "$EMAIL" && return 0
|
||||
fn-ga-list-contains "$effective" allowed-domains "${EMAIL##*@}"
|
||||
}
|
||||
|
||||
# Makes a list change take effect. Global lists travel in the container's
|
||||
# environment and need a restart; per-app lists are read live through the bind
|
||||
# mount, so they only need the mount to actually be there.
|
||||
fn-ga-apply-list-change() {
|
||||
declare SCOPE="$1"
|
||||
if [[ "$SCOPE" == "global" ]]; then
|
||||
fn-ga-reload-service-config
|
||||
return 0
|
||||
fi
|
||||
# An app's lists only apply if its nginx config tells the service which app a
|
||||
# request belongs to. Configs written before that header existed would make
|
||||
# the app fall back to the global lists, so refresh it here rather than wait
|
||||
# for the next deploy. This is a no-op when the config is already current.
|
||||
if fn-google-auth-app-enabled "$SCOPE"; then
|
||||
fn-ga-apply "$SCOPE"
|
||||
fi
|
||||
if ! fn-ga-service-running; then
|
||||
dokku_log_verbose "auth service is not running; changes apply when it starts"
|
||||
return 0
|
||||
fi
|
||||
if ! fn-ga-service-has-app-mount; then
|
||||
dokku_log_info1 "recreating the auth service so it can read per-app lists"
|
||||
fn-ga-service-start
|
||||
return 0
|
||||
fi
|
||||
dokku_log_verbose "in effect within a few seconds (no restart needed)"
|
||||
}
|
||||
|
||||
# Entries end up in a comma/space separated env var, so they may contain
|
||||
@@ -137,6 +251,20 @@ fn-ga-app-dir() {
|
||||
echo "$GOOGLE_AUTH_DATA_ROOT/apps/$APP"
|
||||
}
|
||||
|
||||
# Creates an app's state directory with modes the auth service can use. It reads
|
||||
# per-app lists through a read-only bind mount as an unprivileged uid, so the
|
||||
# directories must be traversable and the list files readable. Nothing is
|
||||
# exposed to other users on the host: $GOOGLE_AUTH_DATA_ROOT itself stays 0700,
|
||||
# and secrets live in global/, which is never mounted.
|
||||
fn-ga-app-dir-ensure() {
|
||||
declare APP="$1"
|
||||
local dir
|
||||
dir="$(fn-ga-app-dir "$APP")"
|
||||
mkdir -p "$dir"
|
||||
chmod 711 "$GOOGLE_AUTH_DATA_ROOT/apps" "$dir" 2>/dev/null || true
|
||||
printf '%s' "$dir"
|
||||
}
|
||||
|
||||
fn-google-auth-app-enabled() {
|
||||
declare APP="$1"
|
||||
[[ -f "$(fn-ga-app-dir "$APP")/enabled" ]]
|
||||
@@ -272,6 +400,9 @@ fn-ga-generate-conf() {
|
||||
# Managed by the dokku google-auth plugin — do not edit by hand.
|
||||
# Regenerated on every deploy and by google-auth:* commands.
|
||||
|
||||
# Every location below sets X-Google-Auth-App explicitly, which both tells the
|
||||
# auth service whose access lists to apply and overwrites any value a client
|
||||
# tried to send.
|
||||
location = ${GOOGLE_AUTH_ROUTE_PREFIX}/verify {
|
||||
internal;
|
||||
proxy_pass http://127.0.0.1:${port};
|
||||
@@ -280,6 +411,7 @@ location = ${GOOGLE_AUTH_ROUTE_PREFIX}/verify {
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_set_header X-Forwarded-For \$remote_addr;
|
||||
proxy_set_header X-Google-Auth-App "${APP}";
|
||||
}
|
||||
|
||||
location ^~ ${GOOGLE_AUTH_ROUTE_PREFIX}/ {
|
||||
@@ -290,6 +422,7 @@ location ^~ ${GOOGLE_AUTH_ROUTE_PREFIX}/ {
|
||||
proxy_set_header X-Forwarded-For \$remote_addr;
|
||||
proxy_set_header X-Forwarded-Port \$server_port;
|
||||
proxy_set_header X-Auth-Request-Redirect "";
|
||||
proxy_set_header X-Google-Auth-App "${APP}";
|
||||
}
|
||||
|
||||
location @google_auth_signin {
|
||||
@@ -299,6 +432,7 @@ location @google_auth_signin {
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_set_header X-Forwarded-For \$remote_addr;
|
||||
proxy_set_header X-Auth-Request-Redirect \$request_uri;
|
||||
proxy_set_header X-Google-Auth-App "${APP}";
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -321,6 +455,7 @@ EOF
|
||||
proxy_set_header X-Auth-Request-User "";
|
||||
proxy_set_header X-Auth-Request-Email "";
|
||||
proxy_set_header X-Auth-Request-Name "";
|
||||
proxy_set_header X-Google-Auth-App "";
|
||||
}
|
||||
EOF
|
||||
done < <(fn-ga-excludes "$APP")
|
||||
@@ -341,6 +476,7 @@ $(fn-ga-proxy-directives "$upstream" "$timeout")
|
||||
proxy_set_header X-Auth-Request-User \$google_auth_user;
|
||||
proxy_set_header X-Auth-Request-Email \$google_auth_email;
|
||||
proxy_set_header X-Auth-Request-Name \$google_auth_name;
|
||||
proxy_set_header X-Google-Auth-App "";
|
||||
}
|
||||
EOF
|
||||
}
|
||||
@@ -479,6 +615,14 @@ fn-ga-service-running() {
|
||||
[[ "$(docker container inspect -f '{{.State.Running}}' "$GOOGLE_AUTH_SERVICE_NAME" 2>/dev/null)" == "true" ]]
|
||||
}
|
||||
|
||||
# True when the running container has the per-app config mount. A container
|
||||
# started by an older version of this plugin will not, and would silently
|
||||
# ignore per-app lists.
|
||||
fn-ga-service-has-app-mount() {
|
||||
docker container inspect -f '{{range .Mounts}}{{println .Destination}}{{end}}' \
|
||||
"$GOOGLE_AUTH_SERVICE_NAME" 2>/dev/null | grep -qxF "$GOOGLE_AUTH_APP_CONFIG_MOUNT"
|
||||
}
|
||||
|
||||
fn-ga-write-env-file() {
|
||||
local envfile="$GOOGLE_AUTH_DATA_ROOT/service.env"
|
||||
local domains emails denied
|
||||
@@ -495,6 +639,7 @@ GOOGLE_AUTH_AUTH_HOST=$(fn-ga-global-get auth-host)
|
||||
GOOGLE_AUTH_ALLOWED_DOMAINS=$domains
|
||||
GOOGLE_AUTH_ALLOWED_EMAILS=$emails
|
||||
GOOGLE_AUTH_DENIED_EMAILS=$denied
|
||||
GOOGLE_AUTH_APP_CONFIG_DIR=$GOOGLE_AUTH_APP_CONFIG_MOUNT
|
||||
GOOGLE_AUTH_COOKIE_NAME=$(fn-ga-global-get cookie-name _google_auth)
|
||||
GOOGLE_AUTH_SESSION_TTL=$(fn-ga-global-get session-ttl 24h)
|
||||
GOOGLE_AUTH_ALLOW_INSECURE=$(fn-ga-global-get allow-insecure false)
|
||||
@@ -508,12 +653,15 @@ fn-ga-service-start() {
|
||||
fn-ga-write-env-file
|
||||
local port
|
||||
port="$(fn-ga-global-get port "$GOOGLE_AUTH_DEFAULT_PORT")"
|
||||
mkdir -p "$GOOGLE_AUTH_DATA_ROOT/apps"
|
||||
chmod 711 "$GOOGLE_AUTH_DATA_ROOT/apps" 2>/dev/null || true
|
||||
docker container rm -f "$GOOGLE_AUTH_SERVICE_NAME" >/dev/null 2>&1 || true
|
||||
docker container run -d \
|
||||
--name "$GOOGLE_AUTH_SERVICE_NAME" \
|
||||
--restart=unless-stopped \
|
||||
-p "127.0.0.1:${port}:2999" \
|
||||
--env-file "$GOOGLE_AUTH_DATA_ROOT/service.env" \
|
||||
-v "$GOOGLE_AUTH_DATA_ROOT/apps:${GOOGLE_AUTH_APP_CONFIG_MOUNT}:ro" \
|
||||
"$GOOGLE_AUTH_IMAGE" >/dev/null
|
||||
dokku_log_info1 "google-auth service running on 127.0.0.1:${port}"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user