WaxSeal Skill
Go CLI making SealedSecrets GitOps-friendly with Google Secret Manager (GSM) as source of truth.
Core Mental Model
WaxSeal manages three linked artifacts for every secret:
- •GSM secret — plaintext value stored in Google Secret Manager (source of truth)
- •Metadata file —
.waxseal/metadata/<shortName>.yamlmapping keys to GSM resources - •SealedSecret manifest — encrypted YAML in
apps/that ArgoCD deploys
These three must be created together. Never hand-write metadata pointing to GSM secrets that don't exist. Use WaxSeal commands to create them atomically.
Command Reference
WaxSeal has two tiers of commands:
- •Primary — shown in
waxseal --help:edit,rotate,reseal,check,meta,setup - •Advanced — shown via
waxseal advanced:addkey,updatekey,retirekey,discover,gsm bootstrap
The edit command is a TUI wizard (interactive only). The advanced commands (addkey, updatekey, retirekey) are fully scriptable with CLI flags.
Global Flags
| Flag | Default | Purpose |
|---|---|---|
--repo | . | Path to the GitOps repository |
--config | .waxseal/config.yaml | Path to waxseal config file |
--dry-run | false | Show what would be done without making changes |
--yes | false | Skip confirmation prompts |
Creating New Secrets
addkey — create secret atomically (all three artifacts)
bash
# Interactive mode (TUI wizard, no flags needed) waxseal addkey my-app-secrets # Non-interactive with --key flags (prompts only for static key values) waxseal addkey my-app-secrets \ --namespace=default \ --key=username \ --key=password:random \ --manifest-path=apps/my-app/sealed-secret.yaml # All-random keys = fully non-interactive (no prompts at all) waxseal addkey my-app-secrets \ --namespace=default \ --key=api_key:random \ --key=db_password:random \ --random-length=64 \ --manifest-path=apps/my-app/sealed-secret.yaml # Custom secret type (e.g., CNPG basic-auth) waxseal addkey my-pg-creds \ --namespace=default \ --key=username \ --key=password:random \ --type=kubernetes.io/basic-auth \ --manifest-path=apps/my-app/pg-sealed.yaml
Flags:
| Flag | Default | Purpose |
|---|---|---|
--key=name | — | Static key (prompts for value securely, never in shell history) |
--key=name:random | — | Generated random value (mode: generated) |
--key=name:template=PREFIX{{secret}} | — | Templated key with prefix (GSM-backed, rotatable) |
--namespace | — | Required when using --key flags |
--manifest-path | apps/<shortName>/sealed-secret.yaml | Where to write the SealedSecret |
--scope | strict | Sealing scope: strict, namespace-wide, cluster-wide |
--type | Opaque | K8s Secret type (e.g., kubernetes.io/basic-auth, kubernetes.io/tls) |
--generator | randomBase64 | Generator kind: randomBase64, randomHex |
--random-length | 32 | Bytes for generated random values |
Templated key example (Garage S3):
bash
waxseal addkey garage-openobserve-creds \
--namespace=default \
--key=access-key:template=GK{{secret}} \
--key=secret-key:random \
--generator=randomHex \
--random-length=12 \
--manifest-path=apps/infrastructure/garage/garage-openobserve-credentials-sealed.yaml
What addkey does atomically:
- •Creates GSM secrets (via
store.CreateSecretVersion) - •Writes metadata to
.waxseal/metadata/ - •Seals and writes the SealedSecret manifest
edit — interactive TUI wizard
bash
waxseal edit # Pick a secret or create new waxseal edit addkey # Jump directly to add-key flow waxseal edit updatekey # Jump directly to update-key flow waxseal edit retirekey # Jump directly to retire-key flow
Updating Existing Keys
updatekey — update a key's value in-place
bash
waxseal updatekey my-app-secrets api_key waxseal updatekey my-app-secrets api_key --generate-random waxseal updatekey my-app-secrets api_key --generate-random --random-length=64 echo "new-value" | waxseal updatekey my-app-secrets api_key --stdin waxseal updatekey my-app-secrets new_key --create --generate-random
What updatekey does:
- •Creates a new version in GSM with the new value
- •Updates the metadata with the new version number
- •Reseals the SealedSecret manifest
Retiring, Resealing, Rotating, Health Checks
bash
waxseal retirekey my-old-secret --reason "Replaced" --delete-manifest waxseal reseal # All active secrets (re-encrypt, same values) waxseal rotate my-secret password # Rotate one key waxseal rotate my-secret --generated # Batch rotate ALL generated keys waxseal check # Full health check waxseal meta list secrets # List all registered secrets waxseal discover # Scan repo for existing SealedSecrets
Never Do
- •Never hand-write metadata files — use
waxseal addkey - •Never use
gcloud secrets versions addfor managed secrets — usewaxseal addkey - •Never use GSM aliases (
latest) — always pin numeric versions - •Never put plaintext secrets in metadata, YAML, comments, or logs
- •Never hand-edit
spec.encryptedData— usewaxseal reseal
Metadata Schema
yaml
shortName: default-my-app-secrets
manifestPath: apps/my-app/sealed-secret.yaml
sealedSecret:
name: my-app-secrets
namespace: default
scope: strict
type: Opaque
status: active
keys:
- keyName: password
source:
kind: gsm
gsm:
secretResource: projects/PROJECT/secrets/SECRET_ID
version: "1"
rotation:
mode: generated
generator:
kind: randomBase64
bytes: 32
Rotation modes
| Mode | Behavior | Use case |
|---|---|---|
static | Never auto-rotated | Usernames, config values |
generated | Auto-generates new value | Passwords, API keys |
external | Prompts operator | OAuth tokens, third-party creds |
Config Location
- •Config:
.waxseal/config.yaml - •Metadata:
.waxseal/metadata/*.yaml - •Certificate:
keys/pub-cert.pem