feat(mosaic): complete credential lifecycle and fail closed
This commit is contained in:
@@ -534,12 +534,21 @@ get_gitea_token() {
|
||||
# would post PRs/issues/reviews under the WRONG agent (e.g. rev2's review attributed
|
||||
# to coder3), corrupting Gate-16 author≠reviewer separation. Hard-stop instead so the
|
||||
# caller aborts loudly rather than acting as the wrong identity.
|
||||
echo "Error: git identity '$_ident' requested (via $_ident_src) for host '$host', but no per-slot token at $_idtok." >&2
|
||||
echo " Refusing to borrow another slot's token. Provision the per-slot token, or unset the identity to use shared credentials." >&2
|
||||
printf 'MOSAIC_CREDENTIAL_REFUSAL outcome=refused reason=no-token-for-identity identity=%s host=%s shared_path_entered=false source=%s path=%s\n' \
|
||||
"$_ident" "$host" "$_ident_src" "$_idtok" >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fleet automation never borrows a shared human/default credential. An
|
||||
# explicit interactive caller may still reach the shared paths below, but
|
||||
# a fleet process must name an identity and resolve that identity exactly.
|
||||
if [[ -n "${MOSAIC_AGENT_NAME:-}" ]]; then
|
||||
printf 'MOSAIC_CREDENTIAL_REFUSAL outcome=refused reason=identity-required identity=<unset> host=%s shared_path_entered=false source=MOSAIC_AGENT_NAME\n' \
|
||||
"$host" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 1. Mosaic credential loader (host → service mapping, run in subshell to avoid polluting env)
|
||||
if [[ -f "$cred_loader" ]]; then
|
||||
local token
|
||||
|
||||
@@ -45,8 +45,23 @@ if [ -n "$ident" ]; then
|
||||
echo "password=$(cat "$idtok")"
|
||||
exit 0
|
||||
fi
|
||||
if [ -n "${MOSAIC_AGENT_NAME:-}" ]; then
|
||||
echo "quit=true"
|
||||
printf 'MOSAIC_CREDENTIAL_REFUSAL outcome=refused reason=no-token-for-identity identity=%s host=%s shared_path_entered=false source=git-credential-mosaic path=%s\n' \
|
||||
"$ident" "$host" "$idtok" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ -n "${MOSAIC_AGENT_NAME:-}" ] && [ -z "$ident" ]; then
|
||||
case "$host" in
|
||||
git.uscllc.com|git.mosaicstack.dev)
|
||||
echo "quit=true"
|
||||
printf 'MOSAIC_CREDENTIAL_REFUSAL outcome=refused reason=identity-required identity=<unset> host=%s shared_path_entered=false source=git-credential-mosaic\n' "$host" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
case "$host" in
|
||||
git.uscllc.com) svc=gitea-usc;;
|
||||
git.mosaicstack.dev) svc=gitea-mosaicstack;;
|
||||
|
||||
@@ -85,6 +85,16 @@ out=$(run_helper "git.mosaicstack.dev" "")
|
||||
assert_eq "shared fallback: username" "username=git" "$(echo "$out" | grep '^username=')"
|
||||
assert_eq "shared fallback: password" "password=shared-mosaicstack-token" "$(echo "$out" | grep '^password=')"
|
||||
|
||||
set +e
|
||||
out=$(run_helper "git.mosaicstack.dev" "" MOSAIC_AGENT_NAME=synthetic-seat 2>"$WORK_DIR/fleet-unset.stderr")
|
||||
rc=$?
|
||||
set -e
|
||||
err=$(cat "$WORK_DIR/fleet-unset.stderr")
|
||||
if [[ "$rc" -eq 0 || "$out" != *"quit=true"* || "$err" != *"reason=identity-required"* || "$err" != *"shared_path_entered=false"* ]]; then
|
||||
echo "FAIL: fleet unset identity did not stop at the resolver marker" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. git-supplied username resolves to an identity WITH a per-slot token ->
|
||||
# that identity + token wins over the shared account.
|
||||
@@ -120,6 +130,16 @@ out=$(run_helper "git.mosaicstack.dev" "no-such-agent")
|
||||
assert_eq "no per-slot token: username" "username=git" "$(echo "$out" | grep '^username=')"
|
||||
assert_eq "no per-slot token: password" "password=shared-mosaicstack-token" "$(echo "$out" | grep '^password=')"
|
||||
|
||||
set +e
|
||||
out=$(run_helper "git.mosaicstack.dev" "no-such-agent" MOSAIC_AGENT_NAME=synthetic-seat 2>"$WORK_DIR/fleet-missing.stderr")
|
||||
rc=$?
|
||||
set -e
|
||||
err=$(cat "$WORK_DIR/fleet-missing.stderr")
|
||||
if [[ "$rc" -eq 0 || "$out" != *"quit=true"* || "$err" != *"reason=no-token-for-identity"* || "$err" != *"shared_path_entered=false"* ]]; then
|
||||
echo "FAIL: fleet missing token did not stop at the resolver marker" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. Correct per-slot token PATH is chosen per host: same agent id, different
|
||||
# host prefix (gitea-usc- vs gitea-mosaicstack-).
|
||||
|
||||
@@ -85,7 +85,23 @@ call_get_gitea_token() {
|
||||
# ---------------------------------------------------------------------------
|
||||
git -C "$REPO_DIR" config --unset mosaic.gitIdentity 2>/dev/null || true
|
||||
out=$(call_get_gitea_token "git.mosaicstack.dev")
|
||||
assert_eq "shared fallback (no identity)" "shared-mosaicstack-token" "$out"
|
||||
assert_eq "interactive shared fallback (no identity)" "shared-mosaicstack-token" "$out"
|
||||
|
||||
# Fleet context with no explicit identity refuses before the shared path. This
|
||||
# is the marker-emission positive control for the fail-closed mechanism.
|
||||
set +e
|
||||
out=$(call_get_gitea_token "git.mosaicstack.dev" MOSAIC_AGENT_NAME=synthetic-seat 2>"$WORK_DIR/stderr-fleet-unset.tmp")
|
||||
rc=$?
|
||||
set -e
|
||||
err=$(cat "$WORK_DIR/stderr-fleet-unset.tmp")
|
||||
if [[ "$rc" -eq 0 || -n "$out" ]]; then
|
||||
echo "FAIL: fleet unset identity must refuse with empty stdout" >&2
|
||||
fail=1
|
||||
fi
|
||||
if [[ "$err" != *"MOSAIC_CREDENTIAL_REFUSAL"* || "$err" != *"reason=identity-required"* || "$err" != *"shared_path_entered=false"* ]]; then
|
||||
echo "FAIL: fleet unset identity did not emit the stable resolver refusal marker: $err" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. git config mosaic.gitIdentity resolves to an agent WITH a per-slot
|
||||
@@ -93,8 +109,8 @@ assert_eq "shared fallback (no identity)" "shared-mosaicstack-token" "$out"
|
||||
# ---------------------------------------------------------------------------
|
||||
echo -n "agentA-mosaicstack-token" > "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-agentA.token"
|
||||
git -C "$REPO_DIR" config mosaic.gitIdentity agentA
|
||||
out=$(call_get_gitea_token "git.mosaicstack.dev")
|
||||
assert_eq "git-config identity token" "agentA-mosaicstack-token" "$out"
|
||||
out=$(call_get_gitea_token "git.mosaicstack.dev" MOSAIC_AGENT_NAME=agentA)
|
||||
assert_eq "confirmed fleet identity bypasses shared path" "agentA-mosaicstack-token" "$out"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. MOSAIC_GIT_IDENTITY env beats git config mosaic.gitIdentity.
|
||||
@@ -143,13 +159,17 @@ assert_failloud() {
|
||||
echo "$stderr" >&2
|
||||
fail=1
|
||||
fi
|
||||
if [[ "$stderr" != *"MOSAIC_CREDENTIAL_REFUSAL"* || "$stderr" != *"reason=no-token-for-identity"* || "$stderr" != *"shared_path_entered=false"* ]]; then
|
||||
echo "FAIL: $desc — stable resolver refusal marker missing: $stderr" >&2
|
||||
fail=1
|
||||
fi
|
||||
if [[ "$stderr" != *"$expected_tok_path"* ]]; then
|
||||
echo "FAIL: $desc — stderr does not name the expected per-slot token path '$expected_tok_path':" >&2
|
||||
echo "$stderr" >&2
|
||||
fail=1
|
||||
fi
|
||||
if [[ "$stderr" == *"shared"*"token"* ]]; then
|
||||
echo "FAIL: $desc — stderr unexpectedly mentions a shared token value:" >&2
|
||||
if [[ "$stderr" == *"shared-mosaicstack-token"* || "$stderr" == *"shared-usc-token"* ]]; then
|
||||
echo "FAIL: $desc — stderr unexpectedly contains a shared credential value:" >&2
|
||||
echo "$stderr" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user