8.2 KiB
Secrets
This project uses a TF-provisions, op-injects, ansible-consumes model. No
ansible-vault, no encrypted vault.yml in git, no custom password-file script.
For how secrets fit into the broader architecture, see architecture.md section 5 (1Password).
flowchart TB
ONEP[("1Password<br/>yucca_tf, yucca_tf_staging, ...<br/><i>source of truth</i>")]
TF[Terraform / Tofu<br/>tf/deployment/staging/austin/ceph/]
REPO[/"inventories/<partition>-<region>/<cluster>/<br/>inventory.ini (TF-gen, gitignored)<br/>secrets.yml.tpl (TF-gen, gitignored)"/]
WRAP[scripts/ansible-play.sh<br/><i>mktemp + op inject -> run ansible-playbook --extra-vars @tmp</i>]
ANS[ansible-playbook]
TF -.->|reads via op run --env-file| ONEP
TF -->|renders| REPO
WRAP -->|reads template| REPO
WRAP -.->|op inject -f<br/>at play time| ONEP
WRAP --> ANS
What lives where
| Vault | Purpose | Who writes |
|---|---|---|
yucca_tf (team-shared) |
Cross-partition shared state: TF state S3 credentials | Operator (manual) |
yucca_tf_staging (team-shared) |
Live values for the staging partition (sietch today) -- <CLUSTER>_CEPH_* items |
Superuser service account (TF) + operator via op CLI |
yucca_tf_staging_manual (team-shared) |
Human-fillable staging placeholders (3rd-party API tokens, OAuth client secrets) -- not yet used by ceph-cluster | Operator (manual) |
Each partition has the same pair; the dev and prod analogues
(yucca_tf_dev(_manual), yucca_tf_prod_manual) land as siblings. The vault
a given cluster reads from is declared per-cluster in
tf/deployment/<partition>/<region>/ceph/clusters.auto.tfvars (field vault)
-- sietch points at yucca_tf_staging. TF derives item paths from that field
at render time; changing it + tofu apply re-renders secrets.yml.tpl with
the new vault path.
Item naming
Format: <CLUSTER>_CEPH_<ROLE>_PASSWORD
Password items (category Password, consumed via op inject at
playbook time):
| Item | Field | Consumed as |
|---|---|---|
SIETCH_CEPH_OPS_PASSWORD |
password |
vault_ops_password |
SIETCH_CEPH_DASHBOARD_PASSWORD |
password |
vault_ceph_dashboard_password |
SIETCH_CEPH_GRAFANA_PASSWORD |
password |
vault_grafana_admin_password |
SSH Key items (category SSH Key, consumed via
scripts/install-ssh-keys.sh on operator workstations and
rotate-ssh-key.yml on cluster nodes). SSH keys use the same storage model as
the passwords. They are generated natively in 1Password
(--ssh-generate-key=ed25519), so the private key never touches operator disk
at creation. Rotation is forward-only: generate a new key, distribute the
public half, retire the old one.
| Item | Field | Consumed as |
|---|---|---|
SIETCH_CEPH_ANSIBLE_IAC_SSH_KEY |
private_key |
~/.ssh/id_ed25519_sietch on operator workstation |
SIETCH_CEPH_ANSIBLE_IAC_SSH_KEY |
public_key |
sietch nodes' ansible-iac@:~/.ssh/authorized_keys |
S3 service-user items (predetermined keys passed to
radosgw-admin user create --access-key=X --secret-key=Y at deploy
time -- Yucca app / restic client can be pre-configured with matching
credentials without waiting for post-bootstrap capture):
| Item | Field | Consumed as |
|---|---|---|
SIETCH_CEPH_S3_SVC_YUCCA_RESTIC_ACCESS_KEY |
password |
vault_s3_restic_access_key -> ceph_rgw_s3_user_access_key |
SIETCH_CEPH_S3_SVC_YUCCA_RESTIC_SECRET_KEY |
password |
vault_s3_restic_secret_key -> ceph_rgw_s3_user_secret_key |
SIETCH_CEPH_S3_SVC_YUCCA_DB_BACKUP_ACCESS_KEY |
password |
vault_db_backup_access_key -> ceph_rgw_db_backup_user_access_key (also read by the talos stack into the yucca-db-backup-s3 Secret for CNPG barman) |
SIETCH_CEPH_S3_SVC_YUCCA_DB_BACKUP_SECRET_KEY |
password |
vault_db_backup_secret_key -> ceph_rgw_db_backup_user_secret_key (same dual consumption) |
Disaster-recovery items (populated by mise run capture after
deploy -- stored in 1P for recovery if the bootstrap node's filesystem
is lost):
| Item | Field | Source |
|---|---|---|
<CLUSTER>_CEPH_RGW_TLS_CERT |
password (concealed) |
/etc/ceph/rgw-ssl.crt on bootstrap |
<CLUSTER>_CEPH_RGW_TLS_KEY |
password (concealed) |
/etc/ceph/rgw-ssl.key on bootstrap |
<CLUSTER>_CEPH_CLIENT_ADMIN_KEYRING |
password (concealed) |
/etc/ceph/ceph.client.admin.keyring on bootstrap |
Items are created on the first mise run capture; later runs overwrite them
only if the content changed. <CLUSTER>_CEPH_RGW_TLS_CERT is no longer
DR-only: the talos stack reads it into the yucca-db-backup-s3 Secret as the
CA bundle CNPG's barman plugin verifies the RGW endpoint with — run the
capture before the talos apply on a fresh cluster.
Item names are derived in tf/shared/modules/ceph-cluster/main.tf
(local.secret_prefix). Hardcoded CEPH (not role_in_hostname) so every
Ceph-project secret grep-matches *_CEPH_* regardless of whether hostnames
use ceph, osd, or mon as the role segment.
Runtime flow
The op CLI is invoked in three distinct patterns across this project:
| Pattern | Used by | What it does |
|---|---|---|
op run --env-file=tf/.env -- |
Every mise run tf:* task |
Resolves op:// references in a dotenv file, injects as env vars into child process (TF: SA token + AWS creds) |
op inject -f -i tpl -o out |
scripts/ansible-play.sh, Hetzner installimage post-install rendering |
Resolves all op:// references in a file template, writes resolved file |
op read "op://..." |
scripts/install-ssh-keys.sh, rotate-ssh-key.yml, post-deploy-capture.yml |
Reads a single field from a single item to stdout |
The ansible-play.sh flow
scripts/ansible-play.sh wraps every ansible-playbook invocation:
- Verifies
op account getsucceeds -- fails closed if 1Password is locked. mktempa0600tmpfile withtrapcleanup onEXIT/INT/TERM.op inject -f -i <cluster>/secrets.yml.tpl -o $tmpfile-- resolves everyop://reference. Exit non-zero if any reference can't be resolved.- Runs
ansible-playbook --extra-vars @$tmpfile ....
Ansible task references stay unchanged -- vault_ops_password etc. are
regular variables populated from extra-vars (highest precedence).
Full wrapper reference: scripts.md.
CI / headless
.github/workflows/infra.yml sets OP_SERVICE_ACCOUNT_TOKEN per job from
the partition's service-account GitHub secrets -- OP_TF_YUCCA_<PARTITION>_ENV
(read, for plan) and OP_TF_YUCCA_<PARTITION>_ENV_WRITE (write, for
apply). op inject / op run pick it up automatically. lint and check
need zero op credentials -- they don't touch secrets -- so they run on every
PR regardless.
Rotating secrets
See docs/runbooks/rotate-secrets.md.
Adding a new secret
- Add the item name to
local.secretsintf/shared/modules/ceph-cluster/main.tf. - Add the matching line to the secrets template
(
templates/secrets.yml.tpl.tftpl). - Add the ansible variable alias in each cluster's
inventories/<partition>-<region>/<cluster>/group_vars/all/vars.yml. - Create the item manually in the target vault (or let TF do it post
service account):
op item create --vault <vault> --category password --title NEW_SECRET_NAME --generate-password=letters,digits,32. tofu apply-- re-renders templates with the new reference.- Playbooks consuming the variable now have it available.