From 4c4946855016d7ef0c5f465251319af17a606e52 Mon Sep 17 00:00:00 2001 From: Giovanni Ferri Date: Sun, 6 Sep 2026 12:00:07 +0100 Subject: [PATCH] bootstrap encrypted Bitwarden provider secret automatically --- docs/deploy/runbook.md | 34 +++++++++++++------- omni/scripts/apply-inline-manifests.sh | 43 +++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 16 deletions(-) diff --git a/docs/deploy/runbook.md b/docs/deploy/runbook.md index d036deb..79fcba6 100644 --- a/docs/deploy/runbook.md +++ b/docs/deploy/runbook.md @@ -118,23 +118,33 @@ Detail: `syscode-ai-internal-plans/projects/image-factory-registry/handoffs/2026 ## Secrets bootstrap (unraid-lab, ESO + Bitwarden) -DR order for the secrets pipeline: Argo installs ESO + `bitwarden-sdk-server` -(app `external-secrets`); cert-manager issues the sdk-server serving cert. -The single provider token is SOPS-encrypted in the private repo -`syscod3/homelab-secrets` at `unraid-lab/bws-token.enc.yaml`. Restore it once -with: +The provider token is secret zero. It must exist before External Secrets can +read any other Bitwarden value. The automatic path is +`omni/scripts/apply-inline-manifests.sh unraid-lab --apply`: it decrypts the +SOPS file from the sibling private `homelab-secrets` checkout, validates that it +is the expected `external-secrets/bws-token` Secret, and adds it to the same +Omni inline-manifest ConfigPatch as the Argo bootstrap. The decrypted value is +written only to a temporary file and the ConfigPatch is stored by Omni. + +The default private-repository location is `../homelab-secrets` relative to +this repository. Override it with `HOMELAB_SECRETS_ROOT` when the checkouts are +elsewhere. The encrypted source remains: +`homelab-secrets/unraid-lab/bws-token.enc.yaml`. + +After the cluster starts, Argo installs ESO and the Bitwarden SDK server. ESO +then uses `bws-token` to create the remaining secrets, including the Argo +cluster-registration Secret and Tailscale OAuth Secret. Re-running the command +is safe because the ConfigPatch and Kubernetes Secret are applied +idempotently. + +For recovery or verification only, the equivalent manual command is: ```sh sops -d /path/to/homelab-secrets/unraid-lab/bws-token.enc.yaml | \ - kubectl --context omni-unraid-lab apply -f - + kubectl --context omni-unraid-lab -n external-secrets apply -f - ``` -Everything else (grafana-cloud, argocd-pocketid-oidc, and -tailscale/operator-oauth) then syncs via ExternalSecrets against -ClusterSecretStore `bitwarden`. The Tailscale Bitwarden value has the client -ID on line one and client secret on line two; ESO templates those into the -keys the operator requires. Never create per-namespace provider tokens; never -paste the token into any agent session. +Never commit the decrypted file or paste the token into a terminal transcript. ## Secrets bootstrap (oci-lab, ESO + Bitwarden) diff --git a/omni/scripts/apply-inline-manifests.sh b/omni/scripts/apply-inline-manifests.sh index 1ab4ab5..f38a6aa 100755 --- a/omni/scripts/apply-inline-manifests.sh +++ b/omni/scripts/apply-inline-manifests.sh @@ -18,7 +18,7 @@ case "$CLUSTER" in ;; unraid-lab) MANIFESTS="$REPO_ROOT/clusters/$CLUSTER/omni/inline-manifests.yaml" - PATCH_ID="202-cluster-unraid-lab-omni/patches/inline-manifests.yaml" + PATCH_ID="200-cluster-unraid-lab-omni/patches/inline-manifests.yaml" ARGOCD_CONFIG="$REPO_ROOT/bootstrap/argocd-cm.yaml" ;; esac @@ -35,6 +35,41 @@ esac temporary="$(mktemp -d)" trap 'rm -rf "$temporary"' EXIT +EFFECTIVE_MANIFESTS="$temporary/inline-manifests.yaml" +cp "$MANIFESTS" "$EFFECTIVE_MANIFESTS" + +if [[ "$CLUSTER" == "unraid-lab" ]]; then + HOMELAB_SECRETS_ROOT="${HOMELAB_SECRETS_ROOT:-$REPO_ROOT/../homelab-secrets}" + BWS_TOKEN_FILE="$HOMELAB_SECRETS_ROOT/unraid-lab/bws-token.enc.yaml" + [[ -f "$BWS_TOKEN_FILE" ]] || { + printf 'missing encrypted bootstrap secret: %s\n' "$BWS_TOKEN_FILE" >&2 + exit 1 + } + command -v sops >/dev/null || { + printf 'sops is required to decrypt the unraid-lab bootstrap secret\n' >&2 + exit 1 + } + sops -d "$BWS_TOKEN_FILE" >"$temporary/bws-token.yaml" + python3 - "$temporary/bws-token.yaml" <<'PY' +import sys + +import yaml + +secret = yaml.safe_load(open(sys.argv[1])) +if ( + secret.get("apiVersion") != "v1" + or secret.get("kind") != "Secret" + or secret.get("metadata", {}).get("name") != "bws-token" + or secret.get("metadata", {}).get("namespace") != "external-secrets" + or "token" not in secret.get("stringData", {}) | secret.get("data", {}) +): + raise SystemExit("unexpected bws-token bootstrap Secret shape") +PY + BWS_TOKEN="$temporary/bws-token.yaml" yq -i \ + '.cluster.inlineManifests += [{"name":"bootstrap-bws-token","contents": loadstr(strenv(BWS_TOKEN))}] | + (.cluster.inlineManifests[-1].contents) style="literal"' "$EFFECTIVE_MANIFESTS" +fi + cat >"$temporary/configpatch.yaml" <