Fix plugin updates.
This commit is contained in:
+77
-17
@@ -187,6 +187,17 @@ grep -qx "GOOGLE_AUTH_ALLOWED_EMAILS=guest@partner.com" "$ENV_FILE" || fail "env
|
||||
grep -qx "GOOGLE_AUTH_DENIED_EMAILS=former@signal.org" "$ENV_FILE" || fail "env file missing global denied emails"
|
||||
grep -qx "GOOGLE_AUTH_APP_CONFIG_DIR=/data/apps" "$ENV_FILE" || fail "env file must point the service at the mount"
|
||||
grep -q "ceo@signal.org" "$ENV_FILE" && fail "per-app entries must not be baked into the env file"
|
||||
[[ "$(stat -c '%a' "$ENV_FILE")" == "600" ]] || fail "the env file holds the client secret and must stay 0600"
|
||||
# Writing the env file tightens the umask to protect that secret. It must not
|
||||
# tighten anything else: google-auth:enable writes the env file (starting the
|
||||
# service) and then creates the app's config directory, which the unprivileged
|
||||
# service has to be able to traverse.
|
||||
fn-ga-app-set-enabled umask-app true
|
||||
[[ "$(stat -c '%a' "$DATA/apps/umask-app")" == "711" ]] ||
|
||||
fail "a directory created after the env file must still be traversable by the container uid"
|
||||
fn-ga-exclude-add umask-app /api/hook
|
||||
[[ "$(stat -c '%a' "$DATA/apps/umask-app")" == "711" ]] ||
|
||||
fail "adding an exclusion must leave the app directory traversable"
|
||||
echo "ok: service env file"
|
||||
|
||||
# --- report shows both scopes, enabled or not ---
|
||||
@@ -216,40 +227,89 @@ grep -q 'proxy_set_header X-Google-Auth-App "my-app";' "$APP_CONF" ||
|
||||
echo "ok: per-app list change refreshes the nginx config"
|
||||
|
||||
# --- a running service only counts if it can really read per-app lists ---
|
||||
# It takes both the bind mount and the env var naming it. A container recreated
|
||||
# from a service.env written before per-app lists existed has the mount but not
|
||||
# the variable, ignores every per-app list, and still looks healthy — so the
|
||||
# check must not be satisfied by the mount alone.
|
||||
# It takes the bind mount, the env var naming it, and a binary that reads both.
|
||||
# Every combination that is missing one still looks healthy from outside while
|
||||
# quietly falling back to the global lists, so none of them may satisfy the
|
||||
# check.
|
||||
mkdir -p "$WORK/bin-docker"
|
||||
cat >"$WORK/bin-docker/docker" <<'EOF'
|
||||
#!/bin/sh
|
||||
# Stands in for `docker container inspect`, replaying a canned inspection.
|
||||
if [ "$1" = "container" ] && [ "$2" = "inspect" ]; then
|
||||
cat "$DOCKER_INSPECT_FIXTURE"
|
||||
exit 0
|
||||
fi
|
||||
# Stands in for the docker CLI, replaying canned inspections.
|
||||
case "$1 $2" in
|
||||
"container inspect") cat "$DOCKER_INSPECT_FIXTURE" ;;
|
||||
"image inspect") printf '%s\n' "$DOCKER_IMAGE_FIXTURE" ;;
|
||||
esac
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$WORK/bin-docker/docker"
|
||||
cat >"$WORK/bin-docker/curl" <<'EOF'
|
||||
#!/bin/sh
|
||||
# Stands in for the healthz probe against the running service. An empty
|
||||
# fixture means the service did not answer.
|
||||
[ -s "$CURL_BODY_FIXTURE" ] || exit 7
|
||||
cat "$CURL_BODY_FIXTURE"
|
||||
EOF
|
||||
chmod +x "$WORK/bin-docker/docker" "$WORK/bin-docker/curl"
|
||||
|
||||
# Runs the check against one canned inspection, in a subshell so the stub and
|
||||
# its fixture do not leak into the rest of the file.
|
||||
# Runs the check against one canned healthz body and one canned container
|
||||
# inspection, in a subshell so the stubs and fixtures do not leak into the rest
|
||||
# of the file.
|
||||
# shellcheck disable=SC2030,SC2031 # the subshell is what keeps the stubs local
|
||||
reads_app_lists() (
|
||||
local healthz="$1"
|
||||
shift
|
||||
export DOCKER_INSPECT_FIXTURE="$WORK/inspect-fixture"
|
||||
export CURL_BODY_FIXTURE="$WORK/healthz-fixture"
|
||||
export DOCKER_IMAGE_FIXTURE=""
|
||||
printf '%s\n' "$@" >"$DOCKER_INSPECT_FIXTURE"
|
||||
printf '%s' "$healthz" >"$CURL_BODY_FIXTURE"
|
||||
PATH="$WORK/bin-docker:$PATH"
|
||||
fn-ga-service-reads-app-lists
|
||||
)
|
||||
|
||||
reads_app_lists "mount=/data/apps" "env=GOOGLE_AUTH_APP_CONFIG_DIR=/data/apps" "env=GOOGLE_AUTH_CLIENT_ID=x" ||
|
||||
fail "a container with both the mount and the env var should read per-app lists"
|
||||
reads_app_lists "mount=/data/apps" "env=GOOGLE_AUTH_CLIENT_ID=x" &&
|
||||
CURRENT_HEALTHZ='{"ok":true,"app_config_dir":"/data/apps"}'
|
||||
# The reply from a build made before per-app lists existed.
|
||||
LEGACY_HEALTHZ='ok'
|
||||
|
||||
reads_app_lists "$CURRENT_HEALTHZ" "mount=/data/apps" "env=GOOGLE_AUTH_APP_CONFIG_DIR=/data/apps" "env=GOOGLE_AUTH_CLIENT_ID=x" ||
|
||||
fail "a container with the mount, the env var, and a current binary should read per-app lists"
|
||||
# The case the plugin used to miss entirely: docker metadata is perfect because
|
||||
# the current shell created the container, but the image it started is old, so
|
||||
# the binary inside ignores the mount and every per-app list with it.
|
||||
reads_app_lists "$LEGACY_HEALTHZ" "mount=/data/apps" "env=GOOGLE_AUTH_APP_CONFIG_DIR=/data/apps" "env=GOOGLE_AUTH_CLIENT_ID=x" &&
|
||||
fail "a container running a pre-per-app-lists binary must not count, however well configured"
|
||||
reads_app_lists '{"ok":true,"app_config_dir":"/somewhere/else"}' "mount=/data/apps" "env=GOOGLE_AUTH_APP_CONFIG_DIR=/data/apps" &&
|
||||
fail "a service reading some other directory is not reading the mounted one"
|
||||
reads_app_lists "$CURRENT_HEALTHZ" "mount=/data/apps" "env=GOOGLE_AUTH_CLIENT_ID=x" &&
|
||||
fail "a container with the mount but no GOOGLE_AUTH_APP_CONFIG_DIR ignores per-app lists"
|
||||
reads_app_lists "env=GOOGLE_AUTH_APP_CONFIG_DIR=/data/apps" &&
|
||||
reads_app_lists "$CURRENT_HEALTHZ" "env=GOOGLE_AUTH_APP_CONFIG_DIR=/data/apps" &&
|
||||
fail "a container with the env var but no mount has nothing to read"
|
||||
reads_app_lists "" && fail "a container with neither should not count"
|
||||
reads_app_lists "$CURRENT_HEALTHZ" "" && fail "a container with neither should not count"
|
||||
# Unreachable service: with no answer to go on, an image that cannot be shown
|
||||
# to match the plugin's source is assumed stale rather than assumed good.
|
||||
reads_app_lists "" "mount=/data/apps" "env=GOOGLE_AUTH_APP_CONFIG_DIR=/data/apps" &&
|
||||
fail "an unanswering service with an unverifiable image should not count"
|
||||
echo "ok: per-app list readiness check"
|
||||
|
||||
# --- the built image has to match the source the plugin is running from ---
|
||||
# The shell half of the plugin upgrades as soon as the files change; the binary
|
||||
# only upgrades when the image is rebuilt. Reusing an image just because one
|
||||
# exists is what lets a per-app list be configured, reported, and ignored.
|
||||
# shellcheck disable=SC2030,SC2031 # the subshell is what keeps the stubs local
|
||||
image_current() (
|
||||
export DOCKER_IMAGE_FIXTURE="$1"
|
||||
PATH="$WORK/bin-docker:$PATH"
|
||||
fn-ga-image-current
|
||||
)
|
||||
|
||||
SOURCE_HASH="$(fn-ga-source-hash)"
|
||||
[[ -n "$SOURCE_HASH" ]] || fail "source hash should not be empty"
|
||||
[[ "$SOURCE_HASH" == "$(fn-ga-source-hash)" ]] || fail "source hash should be stable across calls"
|
||||
image_current "$SOURCE_HASH" || fail "an image labelled with the current source hash is current"
|
||||
image_current "0000000000000000" && fail "an image built from other source is not current"
|
||||
image_current "<no value>" && fail "an unlabelled image (built before this check) is not current"
|
||||
image_current "" && fail "an image with no label value is not current"
|
||||
echo "ok: service image tracks the plugin source"
|
||||
|
||||
# --- lifecycle triggers carry per-app lists ---
|
||||
"$ROOT/post-app-rename" my-app renamed-app
|
||||
[[ ! -d "$DATA/apps/my-app" ]] || fail "rename should move the app's directory"
|
||||
|
||||
Reference in New Issue
Block a user