Files
2026-05-25 19:06:03 -05:00
..
2026-05-25 19:06:03 -05:00

Secrets

Secrets live in Google Cloud Secret Manager, not in this repo. Ansible fetches them at deploy time via gcloud secrets versions access (the controller — operator's laptop or Cloud Build — must be authed to GCP via ADC).

This directory only holds documentation now; nothing here is committed beyond this file.

One-time setup

Pick a GCP project (the same one that owns the Cloud DNS zone is the obvious fit) and enable the API:

gcloud services enable secretmanager.googleapis.com --project <PROJECT>

Set gcp_project in ansible/group_vars/all.yml.

Grant the Cloud Build service account (the one referenced from cloudbuild.yaml) roles/secretmanager.secretAccessor on each of the secrets below.

For local operator runs:

gcloud auth application-default login

ADC lands at ~/.config/gcloud/application_default_credentials.json and Ansible's gcloud invocations pick it up automatically.

Required secrets

vaultwarden-msmtp — outbound mail (system-wide msmtp)

msmtp_smtp_host: smtp.example.com
msmtp_smtp_port: 587
msmtp_smtp_user: vault@example.com
msmtp_smtp_password: <smtp password>
msmtp_from: vault@example.com
gcloud secrets create vaultwarden-msmtp --replication-policy=automatic
gcloud secrets versions add vaultwarden-msmtp --data-file=- < msmtp.yml

vaultwarden-wireguard — WG server privkey

wg_server_privkey: <output of `wg genkey`>

Peer pubkeys + addresses live in the inventory file (they aren't secret — public-key crypto, the name says it).

wg genkey > /tmp/wg.key
printf 'wg_server_privkey: %s\n' "$(cat /tmp/wg.key)" \
  | gcloud secrets create vaultwarden-wireguard --replication-policy=automatic --data-file=-
shred -u /tmp/wg.key

vaultwarden-vaultwarden — admin token + GCP SA JSON

# Argon2id hash of the admin password. Generate with:
#   podman run --rm -it docker.io/vaultwarden/server /vaultwarden hash
# Leave empty/absent to disable /admin entirely.
admin_token_argon2: |
  $argon2id$v=19$...

# GCP service account JSON (dns.admin on the vault DNS zone). Used by
# Caddy for DNS-01 challenges. Paste the whole JSON as a YAML literal.
gcp_sa_dns_json: |
  {
    "type": "service_account",
    ...
  }

vaultwarden-restic — backup destination

restic_repository: s3:s3.<region>.r2.cloudflarestorage.com/<bucket>
restic_password: <strong, randomly-generated>
r2_access_key_id: <R2 access key>
r2_secret_access_key: <R2 secret key>

vaultwarden-healthchecks — per-timer ping URLs

hc_backup_url:         https://hc-ping.com/<uuid>
hc_restic_check_url:   https://hc-ping.com/<uuid>
hc_dnf_automatic_url:  https://hc-ping.com/<uuid>
hc_podman_update_url:  https://hc-ping.com/<uuid>

Create one Healthchecks check per timer with the expected interval set to match the corresponding systemd timer.

Cloud Build-only secrets

These are referenced from cloudbuild.yaml and only matter when running deploys through Cloud Build. Each holds a single raw value (not YAML).

vaultwarden-hcloud-token — Hetzner Cloud API token

read -s -p "hcloud token: " TOK; echo
printf '%s' "$TOK" \
  | gcloud secrets create vaultwarden-hcloud-token --replication-policy=automatic --data-file=-
unset TOK

Used by Pulumi (to provision the host) and by the Cloud Build deploy step's firewall flip-and-close trap.

vaultwarden-ssh-private-key — Ansible SSH key

The private half of the keypair whose public half is in pulumi config vaultwarden:sshPubkey. Cloud Build writes this to ~/.ssh/id_ed25519 for the duration of the deploy step.

ssh-keygen -t ed25519 -f /tmp/vault-ssh -N ""
gcloud secrets create vaultwarden-ssh-private-key --replication-policy=automatic --data-file=/tmp/vault-ssh
# Put /tmp/vault-ssh.pub into Pulumi config as vaultwarden:sshPubkey.
shred -u /tmp/vault-ssh /tmp/vault-ssh.pub

Editing

# Read:
gcloud secrets versions access latest --secret=vaultwarden-msmtp

# Update (creates a new version; old versions remain in audit history):
gcloud secrets versions add vaultwarden-msmtp --data-file=msmtp.yml

# Delete an old version (optional; versioned secrets are cheap):
gcloud secrets versions destroy 1 --secret=vaultwarden-msmtp

Why no local SOPS files

The repo used to ship .sops.yaml and secrets/*.sops.yml. Trade-off moving to GCSM + OIDC:

  • + No long-lived age key on the operator's laptop. ADC re-authes via browser; Cloud Build authenticates via its own SA identity.
  • + Rotation happens in Secret Manager without a git commit.
  • + Audit trail moves to GCP audit logs (centralized, searchable).
  • − Secrets aren't versioned in git history (they're versioned in GCSM instead — different system, same property).
  • − Deploys now depend on Secret Manager API being reachable.