Stabilize forgejo namespace auth and secrets
Some checks failed
Build Apple / Build App (iOS Simulator) (push) Has been cancelled
Build Rust / Cargo Test (push) Failing after 9s
Build Site / Next.js Build (push) Failing after 8s
Build Apple / Build App (macOS) (push) Has been cancelled

This commit is contained in:
Conrad Kramer 2026-03-19 04:08:10 -07:00
parent 5c0a9b3f54
commit 5b09f3a742
8 changed files with 59 additions and 49 deletions

View file

@ -107,18 +107,25 @@ burrow_encrypt_secret_from_file() {
local secret_path="$2"
local source_path="$3"
local agenix_path
local identity_path
local backup_file=""
if [[ ! -s "${source_path}" ]]; then
echo "secret source missing or empty: ${source_path}" >&2
return 1
fi
agenix_path="$(burrow_secret_repo_path "${repo_root}" "${secret_path}")"
identity_path="$(burrow_agenix_identity_path "${repo_root}")"
if [[ -n "${identity_path}" ]]; then
nix --extra-experimental-features "nix-command flakes" run "${repo_root}#agenix" -- -e "${agenix_path}" -i "${identity_path}" < "${source_path}"
else
nix --extra-experimental-features "nix-command flakes" run "${repo_root}#agenix" -- -e "${agenix_path}" < "${source_path}"
if [[ -f "${secret_path}" ]]; then
backup_file="$(mktemp "${TMPDIR:-/tmp}/burrow-secret-backup.XXXXXX")"
cp "${secret_path}" "${backup_file}"
fi
rm -f "${secret_path}"
if ! nix --extra-experimental-features "nix-command flakes" run "${repo_root}#agenix" -- -e "${agenix_path}" < "${source_path}"; then
if [[ -n "${backup_file}" && -f "${backup_file}" ]]; then
mv "${backup_file}" "${secret_path}"
fi
return 1
fi
[[ -n "${backup_file}" ]] && rm -f "${backup_file}"
}

View file

@ -146,25 +146,36 @@ dispatcher_secret="${REPO_ROOT}/secrets/forgejo/nsc-dispatcher-config.age"
autoscaler_secret="${REPO_ROOT}/secrets/forgejo/nsc-autoscaler-config.age"
if [[ "${REFRESH_TOKEN}" -eq 1 ]]; then
"${NSC_BIN}" auth check-login --duration 20m >/dev/null
raw_token_file="$(mktemp)"
trap 'rm -f "${raw_token_file}"; cleanup' EXIT
"${NSC_BIN}" auth generate-dev-token --output_to "${raw_token_file}" >/dev/null
RAW_NSC_TOKEN_FILE="${raw_token_file}" TOKEN_FILE="${token_file}" python3 - <<'PY'
ssh \
-i "${SSH_KEY}" \
-o IdentitiesOnly=yes \
-o UserKnownHostsFile="${KNOWN_HOSTS_FILE}" \
-o StrictHostKeyChecking=accept-new \
"${HOST}" \
'sudo -u forgejo-nsc python3 - <<'"'"'PY'"'"'
import json
import os
from pathlib import Path
raw = Path(os.environ["RAW_NSC_TOKEN_FILE"]).read_text(encoding="utf-8").strip()
if not raw:
raise SystemExit("generated Namespace token is empty")
payload = {}
Path(os.environ["TOKEN_FILE"]).write_text(
json.dumps({"bearer_token": raw}, indent=2) + "\n",
encoding="utf-8",
)
PY
rm -f "${raw_token_file}"
token_json = Path("/var/lib/forgejo-nsc/.config/ns/token.json")
if token_json.exists():
data = json.loads(token_json.read_text(encoding="utf-8"))
session = str(data.get("session_token", "")).strip()
if session:
payload["session_token"] = session
token_cache = Path("/var/lib/forgejo-nsc/.config/ns/token.cache")
if token_cache.exists():
bearer = token_cache.read_text(encoding="utf-8").strip()
if bearer:
payload["bearer_token"] = bearer
if not payload:
raise SystemExit("forgejo-nsc host does not have a usable Namespace session")
print(json.dumps(payload, indent=2))
PY' > "${token_file}"
chmod 600 "${token_file}"
elif [[ -f "${token_secret}" ]]; then
burrow_decrypt_age_secret_to_temp "${REPO_ROOT}" "${token_secret}" > "${token_file}"
@ -186,7 +197,12 @@ try:
except json.JSONDecodeError:
parsed = None
if isinstance(parsed, dict) and isinstance(parsed.get("bearer_token"), str) and parsed["bearer_token"].strip():
if isinstance(parsed, dict):
bearer = parsed.get("bearer_token")
session = parsed.get("session_token")
if isinstance(bearer, str) and bearer.strip():
raise SystemExit(0)
if isinstance(session, str) and session.strip():
raise SystemExit(0)
path.write_text(json.dumps({"bearer_token": raw}, indent=2) + "\n", encoding="utf-8")

View file

@ -88,27 +88,9 @@ if [[ "${ROTATE_PAT}" -eq 1 ]]; then
"${SCRIPT_DIR}/provision-forgejo-nsc.sh" --host "${HOST}" --ssh-key "${SSH_KEY}"
fi
token_file="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"" \
"" \
"${REPO_ROOT}/secrets/forgejo/nsc-token.age"
)"
dispatcher_file="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"" \
"" \
"${REPO_ROOT}/secrets/forgejo/nsc-dispatcher-config.age"
)"
autoscaler_file="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"" \
"" \
"${REPO_ROOT}/secrets/forgejo/nsc-autoscaler-config.age"
)"
token_file="${REPO_ROOT}/secrets/forgejo/nsc-token.age"
dispatcher_file="${REPO_ROOT}/secrets/forgejo/nsc-dispatcher-config.age"
autoscaler_file="${REPO_ROOT}/secrets/forgejo/nsc-autoscaler-config.age"
for path in "${token_file}" "${dispatcher_file}" "${autoscaler_file}"; do
if [[ ! -s "${path}" ]]; then

Binary file not shown.

View file

@ -158,10 +158,11 @@ instances:
For Burrow, use `Scripts/provision-forgejo-nsc.sh` to mint the Forgejo PAT,
generate a Namespace token from the logged-in Namespace account, and refresh
`secrets/forgejo/{nsc-token,nsc-dispatcher-config,nsc-autoscaler-config}.age`.
The token file is emitted as JSON with a `bearer_token` field so both the
Compute API path and the `nsc` CLI fallback can consume the same secret
material. The forge host consumes the encrypted secrets through agenix; avoid
keeping local plaintext `intake/` copies around.
The token file is emitted as JSON with a long-lived `session_token` plus the
current `bearer_token`. The `nsc` CLI paths use the session-backed login flow,
while the Compute API path can consume the bearer token directly. The forge
host consumes the encrypted secrets through agenix; avoid keeping local
plaintext `intake/` copies around.
Long-lived runtime state is now sourced from age-encrypted files:

View file

@ -197,6 +197,10 @@ func (d *Dispatcher) LaunchRunner(ctx context.Context, req LaunchRequest) (strin
args = append(args, "--", "/bin/sh", "-c", script)
cmd := exec.CommandContext(ctx, d.opts.BinaryPath, args...)
// The Linux `nsc run` path uses the CLI auth flow. Keep using the service
// account's refreshed Namespace login session instead of forcing the
// short-lived NSC_TOKEN_FILE bearer token into CLI requests.
cmd.Env = nscCLIEnv()
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = &buf