{"id":43575174,"url":"https://github.com/shermanhuman/waxseal","last_synced_at":"2026-02-10T02:07:56.921Z","repository":{"id":336116739,"uuid":"1148331893","full_name":"shermanhuman/waxseal","owner":"shermanhuman","description":"🦭 CLI to store your k8s secrets in GSM, write to SealedSecrets.  Mostly automated rotations.  Now with Google calendar tasks reminders!","archived":false,"fork":false,"pushed_at":"2026-02-03T06:04:02.000Z","size":12453,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-03T10:58:28.945Z","etag":null,"topics":["argocd","gitops","golang","google-secret-manager","kubernetes","rotation","sealed-secrets","secret-management","secrets","security"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shermanhuman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-02T20:59:50.000Z","updated_at":"2026-02-03T06:04:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/shermanhuman/waxseal","commit_stats":null,"previous_names":["shermanhuman/waxseal"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/shermanhuman/waxseal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shermanhuman%2Fwaxseal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shermanhuman%2Fwaxseal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shermanhuman%2Fwaxseal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shermanhuman%2Fwaxseal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shermanhuman","download_url":"https://codeload.github.com/shermanhuman/waxseal/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shermanhuman%2Fwaxseal/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29060624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-03T22:28:58.191Z","status":"ssl_error","status_checked_at":"2026-02-03T22:28:56.515Z","response_time":96,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["argocd","gitops","golang","google-secret-manager","kubernetes","rotation","sealed-secrets","secret-management","secrets","security"],"created_at":"2026-02-03T23:00:55.642Z","updated_at":"2026-02-10T02:07:56.916Z","avatar_url":"https://github.com/shermanhuman.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# WaxSeal\n\n![waxseal-logo](https://github.com/user-attachments/assets/a914aa45-7945-429e-ac22-723654557e4e)\n\n\u003e GitOps-friendly SealedSecrets management with Google Secret Manager as the source of truth.\n\nWaxSeal keeps plaintext out of Git by synchronizing Kubernetes SealedSecrets with Google Secret Manager (GSM). All secret values live in GSM; Git stores only encrypted ciphertext and metadata.\n\n## Installation\n\n```bash\ngo install github.com/shermanhuman/waxseal/cmd/waxseal@latest\n```\n\nOr build from source:\n\n```bash\ngit clone https://github.com/shermanhuman/waxseal.git\ncd waxseal\ngo build -o waxseal ./cmd/waxseal\n```\n\n## Prerequisites\n\nBefore using waxseal, ensure you have:\n\n- **gcloud CLI** - Authenticated with `gcloud auth application-default login`\n- **kubeseal CLI** - Available on PATH (used for encryption)\n- **kubectl** - Configured to access your cluster\n- **A Kubernetes cluster** with [SealedSecrets controller](https://github.com/bitnami-labs/sealed-secrets) installed\n- **A GitOps repository** with existing SealedSecret manifests (or starting fresh)\n\n## Quick Start\n\n### 1. Initialize in your GitOps repo\n\n```bash\ncd my-infra-repo\nwaxseal setup\n```\n\nThe interactive wizard will:\n\n- Create/configure your GCP project for secret storage\n- Enable required APIs (Secret Manager)\n- Set up billing if needed\n- Fetch the sealing certificate from your cluster\n- Create configuration files\n\nThis creates:\n\n- `.waxseal/config.yaml` - Configuration file\n- `.waxseal/metadata/` - Directory for secret metadata\n- `keys/pub-cert.pem` - Controller certificate (fetched from cluster)\n\n### 2. Discover existing SealedSecrets\n\n```bash\nwaxseal discover\n```\n\nThis finds SealedSecret manifests and creates metadata stubs in `.waxseal/metadata/`.\n\n### 3. Bootstrap secrets to GSM\n\n```bash\n# Push existing cluster secret values to GSM\nwaxseal bootstrap my-app-secrets\n```\n\n### 4. Reseal secrets\n\n```bash\n# Reseal a single secret\nwaxseal reseal my-app-secrets\n\n# Reseal all active secrets (default when no shortName given)\nwaxseal reseal\n\n# Dry run to see what would be done\nwaxseal reseal --dry-run\n```\n\n## Commands\n\n| Command           | Description                                          |\n| ----------------- | ---------------------------------------------------- |\n| `setup`           | Interactive setup wizard for a GitOps repository     |\n| `discover`        | Find SealedSecrets and create metadata stubs         |\n| `add`             | Create a new secret (GSM + metadata + manifest)      |\n| `update`          | Update a secret key's value                          |\n| `list`            | List registered secrets with status and expiry       |\n| `validate`        | Validate repo structure and metadata (CI-friendly)   |\n| `check`           | Check operational health (cert expiry, rotation due) |\n| `reseal`          | Reseal secrets from GSM to SealedSecret manifests    |\n| `rotate`          | Rotate secret values and reseal                      |\n| `retire`          | Mark a secret as retired and optionally delete       |\n| `bootstrap`       | Push existing cluster secrets to GSM                 |\n| `gcp bootstrap`   | Interactive GCP infrastructure setup                 |\n| `reminders sync`  | Sync expiry reminders to calendar/tasks              |\n| `reminders clear` | Remove reminders for a secret                        |\n| `reminders list`  | List secrets with upcoming expiry                    |\n| `reminders setup` | Configure reminder settings                          |\n\n### Global Flags\n\n| Flag        | Description                                           |\n| ----------- | ----------------------------------------------------- |\n| `--repo`    | Path to GitOps repository (default: `.`)              |\n| `--config`  | Path to config file (default: `.waxseal/config.yaml`) |\n| `--dry-run` | Preview changes without writing                       |\n| `--yes`     | Skip confirmation prompts                             |\n\n## Configuration\n\n`.waxseal/config.yaml`:\n\n```yaml\nversion: \"1\"\n\nstore:\n  kind: gsm\n  projectId: my-gcp-project\n\ncontroller:\n  namespace: kube-system\n  serviceName: sealed-secrets\n\ncert:\n  repoCertPath: keys/pub-cert.pem\n  verifyAgainstCluster: true\n\ndiscovery:\n  includeGlobs:\n    - \"apps/**/*.yaml\"\n  excludeGlobs:\n    - \"**/kustomization.yaml\"\n\n# Optional: Expiry reminders (tasks, calendar, both, none)\nreminders:\n  enabled: true\n  provider: tasks\n  # tasklistId: \"@default\"    # Optional for tasks provider\n  # calendarId: primary       # Only needed for calendar/both provider\n  leadTimeDays: [30, 7, 1]\n  auth:\n    kind: adc\n```\n\n## Metadata Schema\n\nEach secret has a metadata file in `.waxseal/metadata/\u003cshortName\u003e.yaml`:\n\n```yaml\nshortName: my-app-secrets\nmanifestPath: apps/my-app/sealed-secret.yaml\nsealedSecret:\n  name: my-app-secrets\n  namespace: my-app\n  scope: strict\n  type: Opaque\nstatus: active\n\nkeys:\n  # GSM-backed key with auto-rotation\n  - keyName: api_key\n    source:\n      kind: gsm\n    gsm:\n      secretResource: projects/my-project/secrets/my-app-api-key\n      version: \"3\"\n    rotation:\n      mode: generated\n      generator:\n        kind: randomBase64\n        bytes: 32\n\n  # External credential (OAuth, third-party API, etc.)\n  - keyName: oauth_secret\n    source:\n      kind: gsm\n    gsm:\n      secretResource: projects/my-project/secrets/my-app-oauth\n      version: \"1\"\n    rotation:\n      mode: external\n    expiry:\n      expiresAt: \"2026-06-15T00:00:00Z\"\n\n  # Computed key (DATABASE_URL pattern)\n  - keyName: DATABASE_URL\n    source:\n      kind: computed\n    computed:\n      kind: template\n      template: \"postgresql://{{user}}:{{pass}}@{{host}}:5432/{{db}}\"\n      inputs:\n        - var: user\n          ref:\n            keyName: db_username\n        - var: pass\n          ref:\n            keyName: db_password\n      params:\n        host: \"db.example.com\"\n        db: \"myapp\"\n```\n\n## Rotation Modes\n\n| Mode        | Description                         | Use Case                          |\n| ----------- | ----------------------------------- | --------------------------------- |\n| `generated` | Auto-generate new value on rotate   | API keys, passwords, tokens       |\n| `external`  | Manual update, waxseal reseals      | OAuth secrets, third-party tokens |\n| `static`    | Operator provides value at rotation | Legacy systems, shared secrets    |\n\n### Rotating Secrets\n\n```bash\n# Rotate a specific key (auto-generates if mode=generated)\nwaxseal rotate my-app-secrets api_key\n\n# Rotate all generated keys in a secret\nwaxseal rotate my-app-secrets --generated\n```\n\n## Computed Keys\n\nComputed keys are derived from other keys using templates. Common use case: `DATABASE_URL` from individual credentials.\n\nTemplate syntax: `{{variable_name}}`\n\n```yaml\n- keyName: DATABASE_URL\n  source:\n    kind: computed\n  computed:\n    kind: template\n    template: \"postgresql://{{user}}:{{pass}}@{{host}}:{{port}}/{{db}}\"\n    inputs:\n      - var: user\n        ref:\n          keyName: db_username # Same secret\n      - var: pass\n        ref:\n          keyName: db_password\n    params:\n      host: \"db.example.com\"\n      port: \"5432\"\n      db: \"myapp\"\n```\n\n## Expiry and Reminders\n\nTrack secret expiration and get calendar reminders:\n\n```yaml\n- keyName: tls_cert\n  expiry:\n    expiresAt: \"2026-03-01T00:00:00Z\"\n    source: \"cert-notAfter\"\n```\n\nSync to Google Calendar:\n\n```bash\nwaxseal reminders sync\n```\n\nThis creates events at 30, 7, and 1 days before expiry.\n\n## Retiring Secrets\n\nWhen a secret is no longer needed, retire it instead of deleting:\n\n```bash\n# Mark as retired\nwaxseal retire my-app-secrets --reason \"Migrated to new service\"\n\n# Retire and delete the manifest file\nwaxseal retire my-app-secrets --delete-manifest\n\n# Retire and link to replacement\nwaxseal retire old-secret --replaced-by new-secret\n```\n\nRetired secrets are skipped during `reseal --all` operations.\n\n## Re-encrypting After Cert Rotation\n\nWhen the SealedSecrets controller certificate rotates, `reseal --all` detects the\nchange automatically:\n\n```bash\n# Reseal all secrets (auto-detects cert rotation)\nwaxseal reseal\n\n# Skip cert check for offline/CI use\nwaxseal reseal --skip-cert-check\n\n# Preview what would be done\nwaxseal reseal --dry-run\n```\n\n## Bootstrapping Existing Secrets\n\nImport existing Kubernetes secrets to GSM:\n\n```bash\n# Push a discovered secret's values to GSM\nwaxseal bootstrap my-app-secrets\n\n# Preview without making changes\nwaxseal bootstrap my-app-secrets --dry-run\n```\n\nThis reads the secret from the cluster and pushes values to GSM.\n\n## Health Checks\n\nMonitor certificate and secret expiration:\n\n```bash\n# Check both cert and secret expiry\nwaxseal check\n\n# Check only certificate expiry\nwaxseal check --cert\n\n# Check only secret expiration\nwaxseal check --expiry\n\n# Warn if anything expires within 90 days\nwaxseal check --warn-days 90\n\n# Fail in CI if warnings exist\nwaxseal check --fail-on-warning\n```\n\nExit codes:\n\n- `0` - All checks passed\n- `1` - Expired certificate or secrets\n- `2` - Expiring soon (with `--fail-on-warning`)\n\n## GCP Infrastructure Setup\n\nSet up GCP project for WaxSeal:\n\n```bash\n# Interactive wizard (prompts for project, billing, service account, etc.)\nwaxseal gcp bootstrap\n\n# Preview what would be done\nwaxseal gcp bootstrap --dry-run\n```\n\nThe wizard walks through:\n\n- Creating or selecting a GCP project\n- Enabling Secret Manager API\n- Setting up billing\n- Creating a service account\n- Optionally enabling Calendar API for reminders\n- Optionally configuring Workload Identity for GitHub Actions\n\n## Operator Hints\n\nProvide guidance for manual rotation:\n\n```yaml\n- keyName: stripe_key\n  operatorHints:\n    provider: stripe\n    rotationUrl: https://dashboard.stripe.com/apikeys\n    docUrl: https://stripe.com/docs/keys\n    contact: platform-team@company.com\n    notes: \"Regenerate in Stripe Dashboard, then update GSM\"\n```\n\nDuring `waxseal rotate`, hints are displayed to guide operators.\n\n## CI/CD Integration\n\n### Validation\n\n```yaml\n# GitHub Actions example\n- name: Validate waxseal structure\n  run: waxseal validate\n\n- name: Check expiration health\n  run: waxseal check --fail-on-warning --warn-days=30\n```\n\nExit codes:\n\n- `0` - Success\n- `2` - Validation failed\n\n### Automated Reseal\n\n```yaml\n- name: Reseal all secrets\n  run: waxseal reseal\n  env:\n    GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GCP_SA_KEY }}\n```\n\n## Security\n\n**Critical invariants enforced by waxseal:**\n\n1. **No plaintext on disk** - Secrets are never written unencrypted\n2. **No secrets in logs** - The `Redacted` type prevents accidental logging\n3. **Numeric GSM versions only** - Aliases like `latest` are rejected to ensure reproducibility\n4. **Atomic writes** - Files are written to temp then renamed, preventing corruption\n5. **Validation before write** - Output is validated before replacing files\n6. **Controller-compatible encryption** - Uses `kubeseal` binary for encryption to guarantee compatibility\n\n## Authentication\n\nWaxSeal uses Application Default Credentials (ADC) for GCP authentication:\n\n```bash\n# Development\ngcloud auth application-default login\n\n# Production (Service Account)\nexport GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa-key.json\n\n# GKE Workload Identity\n# Automatic when running in GKE with configured Workload Identity\n```\n\nRequired IAM roles:\n\n- `roles/secretmanager.secretAccessor` - Read secret values\n- `roles/secretmanager.secretVersionAdder` - Add new versions (for rotation)\n\n## Development\n\n```bash\n# Build\ngo build ./...\n\n# Unit tests\ngo test ./...\n\n# E2E tests (requires Docker Desktop only)\ndocker compose -f docker-compose.e2e.yaml up --build\n\n# Lint\ngolangci-lint run ./...\n\n# Release builds\nGOOS=linux go build -o waxseal-linux ./cmd/waxseal\n```\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshermanhuman%2Fwaxseal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshermanhuman%2Fwaxseal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshermanhuman%2Fwaxseal/lists"}