Firstlume Docs firstlume.io ENPT PT Talk to us

Vault secrets

Firstlume doesn't store your Vault secrets. The executor reads Vault inside the run's pod, and the values don't pass through the server or the database. For this, Firstlume issues a JWT for each phase (ES256, valid for 15 minutes) that says which stack is running, and the executor exchanges it for a Vault token. The token is revoked when the phase ends.

1. In Firstlume

Set the organization's connection with PUT /api/v1/orgs/{org}/vault:

{"address": "https://vault.example.com:8200"}

Optional fields are ca_cert, namespace, auth_path (default jwt) and audience (default vault).

Set the role, inherited like the other settings, on a folder or on the stack:

{"vault_role": "infra-dev"}

FIRSTLUME_PUBLIC_URL (in Helm, controlPlane.publicURL, which defaults to the ingress host) is the iss of the JWTs.

2. In Vault

Enable the jwt method trusting Firstlume, with discovery or with the public key:

vault auth enable jwt
vault write auth/jwt/config bound_issuer=https://firstlume.example.com \
  oidc_discovery_url=https://firstlume.example.com      # or jwt_validation_pubkeys=@firstlume.pem
vault policy write infra-dev - <<'EOF'
path "secret/data/infra/dev/*" { capabilities = ["read"] }
EOF
vault write auth/jwt/role/infra-dev role_type=jwt user_claim=sub bound_audiences=vault \
  bound_claims_type=glob bound_claims='{"stack_path": "infra/dev/*"}' \
  token_policies=infra-dev token_ttl=15m

The public key is at GET /api/v1/orgs/{org}/vault/public-key (PEM) and at /.well-known/jwks.json.

3. In the repository

Use {vault: secret/infra/dev/${name}, field: ip} in any value of the declaration. The path is the same as in vault kv get. KV v1 and v2 are detected, and other engines work too.

JWT claims

Claim Value
sub stack:<id>
org the organization
stack_id, stack_name the stack
stack_path folders and name, such as infra/dev/machines
folder_ids the folder ids
run_id, run_type the run
phase plan, apply or drift

With bound_claims, a dev stack can't read prod secrets, and a role with phase = apply doesn't work for the plan. Prefer folder_ids to stack_path if renaming folders could change access.

How values are handled

  • Everything from Vault is sensitive: masked in the log, and files get 0600 permissions.
  • Plan and apply: the apply reads Vault again and checks it against the plan, with a keyed hash and no stored values. If a secret changed between the two, the apply fails with the secret in <path>#<field> changed since the plan: plan again.
  • Errors name the declaration item, the path and the field, never the value: no connection, no role, login refused, permission denied, missing secret or field, Vault down.

Test without running anything

POST /api/v1/orgs/{org}/stacks/{id}/vault/check logs in as the stack and says, per path, whether the role can read, without reading any value:

{"paths": ["secret/infra/dev/n01"]}