Provision repository registry credentials as sandbox secrets
Repositories need registry tokens to install dependencies, and those live
behind 1Password or a keychain that only exists on the host. A secrets file in
the user's per-repository config names each variable, the hosts it
authenticates to, and a command that prints it; the command runs on the host
from the repository root and its output becomes an sbx custom secret.
Custom secrets keep the value out of the sandbox entirely: the environment
variable is set to a placeholder and the proxy substitutes the real secret into
outbound request headers for the declared hosts. A committed .npmrc using
${VAR} interpolation therefore works unchanged while the agent sees only the
placeholder. Placeholders are derived from the repository and variable name so
re-running setup does not invalidate one already exported into a running
sandbox, and the value is piped rather than passed as --value, which would put
it in the process list.
The declaration lives in user config rather than the repository for the same
reason AWS profile approval does: a checkout must not choose which host
commands run or which credentials resolve.
A failed resolver has its own stderr surfaced, since it names where to obtain
the credential, and the remaining secrets still provision.
sbx secret set-custom was measured to overwrite silently and has no --force
flag, so the non-interactive test now matches sbx secret set precisely rather
than by prefix.
This commit is contained in:
@@ -28,7 +28,7 @@ require_force() {
|
||||
fail "no invocation of '$pattern' found; has it been renamed?"
|
||||
}
|
||||
|
||||
require_force 'sbx secret set'
|
||||
require_force 'sbx secret set '
|
||||
require_force 'sbx skills import'
|
||||
require_force 'sbx rm '
|
||||
|
||||
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source-path=SCRIPTDIR
|
||||
# shellcheck source=tasks/ai/sbx
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/../tasks/ai/sbx"
|
||||
|
||||
failures=0
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
failures=$((failures + 1))
|
||||
}
|
||||
|
||||
REPOSITORY="CareEvolution/api-portal"
|
||||
REPO_CONFIG_DIR="$work"
|
||||
|
||||
cat >"$work/secrets" <<'EOF'
|
||||
# Registry credentials for this repository
|
||||
FONTAWESOME_API_KEY | npm.fontawesome.com | scripts/npm-auth.sh print FONTAWESOME_API_KEY
|
||||
|
||||
PROGET_NPM_TOKEN|proget.careevolution.com|scripts/npm-auth.sh print PROGET_NPM_TOKEN
|
||||
MULTI | a.example.com,b.example.com | echo hi # trailing comment
|
||||
|
||||
MISSING_COMMAND | host.example.com
|
||||
NO_HOST || echo hi
|
||||
EOF
|
||||
|
||||
mapfile -t lines < <(read_secret_declarations 2>/dev/null)
|
||||
|
||||
((${#lines[@]} == 3)) ||
|
||||
fail "expected 3 valid declarations, got ${#lines[@]}: ${lines[*]}"
|
||||
|
||||
IFS='|' read -r var hosts command <<<"${lines[0]}"
|
||||
[[ "$var" == "FONTAWESOME_API_KEY" ]] || fail "var mis-parsed: '$var'"
|
||||
[[ "$hosts" == "npm.fontawesome.com" ]] || fail "hosts mis-parsed: '$hosts'"
|
||||
[[ "$command" == "scripts/npm-auth.sh print FONTAWESOME_API_KEY" ]] ||
|
||||
fail "command mis-parsed: '$command'"
|
||||
|
||||
IFS='|' read -r var hosts command <<<"${lines[1]}"
|
||||
[[ "$var" == "PROGET_NPM_TOKEN" ]] || fail "unpadded var mis-parsed: '$var'"
|
||||
[[ "$hosts" == "proget.careevolution.com" ]] || fail "unpadded host mis-parsed: '$hosts'"
|
||||
|
||||
IFS='|' read -r var hosts command <<<"${lines[2]}"
|
||||
[[ "$hosts" == "a.example.com,b.example.com" ]] || fail "multi-host mis-parsed: '$hosts'"
|
||||
[[ "$command" == "echo hi" ]] || fail "trailing comment not stripped: '$command'"
|
||||
|
||||
printf '%s\n' "${lines[@]}" | grep -q MISSING_COMMAND &&
|
||||
fail "a declaration without a command was accepted"
|
||||
printf '%s\n' "${lines[@]}" | grep -q NO_HOST &&
|
||||
fail "a declaration without a host was accepted"
|
||||
|
||||
REPO_CONFIG_DIR="$work/nonexistent"
|
||||
mapfile -t none < <(read_secret_declarations 2>/dev/null)
|
||||
((${#none[@]} == 0)) || fail "absent file produced ${#none[@]} declarations"
|
||||
|
||||
first="$(secret_placeholder FONTAWESOME_API_KEY)"
|
||||
second="$(secret_placeholder FONTAWESOME_API_KEY)"
|
||||
[[ "$first" == "$second" ]] || fail "placeholder is not stable: $first vs $second"
|
||||
|
||||
[[ "$first" == sbx-cs-* ]] || fail "placeholder lacks the sbx-cs- prefix: $first"
|
||||
|
||||
[[ "$(secret_placeholder PROGET_NPM_TOKEN)" != "$first" ]] ||
|
||||
fail "two variables share one placeholder"
|
||||
|
||||
REPOSITORY="other/repo"
|
||||
[[ "$(secret_placeholder FONTAWESOME_API_KEY)" != "$first" ]] ||
|
||||
fail "placeholder does not vary by repository"
|
||||
|
||||
if ((failures)); then
|
||||
printf '%d assertion(s) failed\n' "$failures" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'All secret declaration assertions passed.\n'
|
||||
Reference in New Issue
Block a user