{"id":50885401,"url":"https://github.com/ferrlabs/ferrvault","last_synced_at":"2026-06-15T17:00:27.447Z","repository":{"id":360389355,"uuid":"1212835722","full_name":"FerrLabs/FerrVault","owner":"FerrLabs","description":"Kubernetes operator that syncs FerrFlow secrets into native Kubernetes Secrets","archived":false,"fork":false,"pushed_at":"2026-06-12T21:29:05.000Z","size":294,"stargazers_count":0,"open_issues_count":41,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-12T23:11:24.135Z","etag":null,"topics":["controller","crd","devtools","external-secrets","ferrflow","go","helm","kubernetes","kubernetes-operator","secrets-management"],"latest_commit_sha":null,"homepage":"https://ferrflow.com","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FerrLabs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"BryanFRD"}},"created_at":"2026-04-16T19:27:55.000Z","updated_at":"2026-06-12T21:29:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/FerrLabs/FerrVault","commit_stats":null,"previous_names":["ferrlabs/ferrvault"],"tags_count":30,"template":false,"template_full_name":null,"purl":"pkg:github/FerrLabs/FerrVault","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FerrLabs%2FFerrVault","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FerrLabs%2FFerrVault/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FerrLabs%2FFerrVault/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FerrLabs%2FFerrVault/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FerrLabs","download_url":"https://codeload.github.com/FerrLabs/FerrVault/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FerrLabs%2FFerrVault/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34372130,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["controller","crd","devtools","external-secrets","ferrflow","go","helm","kubernetes","kubernetes-operator","secrets-management"],"created_at":"2026-06-15T17:00:18.317Z","updated_at":"2026-06-15T17:00:27.442Z","avatar_url":"https://github.com/FerrLabs.png","language":"Go","funding_links":["https://github.com/sponsors/BryanFRD"],"categories":[],"sub_categories":[],"readme":"# ferrflow-operator\n\n[![Latest release](https://img.shields.io/github/v/release/FerrLabs/FerrVault)](https://github.com/FerrLabs/FerrVault/releases/latest)\n[![Quality Gate](https://sonar.ferrlabs.com/api/project_badges/measure?project=FerrVault\u0026metric=alert_status\u0026token=sqb_d3cde9fc7a86f70e3652d22ce13b5a39522212c8)](https://sonar.ferrlabs.com/dashboard?id=FerrVault)\n[![Coverage](https://sonar.ferrlabs.com/api/project_badges/measure?project=FerrVault\u0026metric=coverage\u0026token=sqb_d3cde9fc7a86f70e3652d22ce13b5a39522212c8)](https://sonar.ferrlabs.com/dashboard?id=FerrVault)\n[![License](https://img.shields.io/github/license/FerrLabs/FerrVault)](LICENSE)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/FerrLabs/FerrVault/badge)](https://scorecard.dev/viewer/?uri=github.com/FerrLabs/FerrVault)\n\nKubernetes operator that syncs secrets stored in [FerrFlow](https://ferrflow.com) into native Kubernetes `Secret` resources.\n\n\u003e **Status: alpha.** The MVP reconciler is in place — it reads secrets from a FerrFlow vault via the bulk-reveal API and materialises them into a Kubernetes Secret, with owner-ref GC and status conditions. Rolling restarts, Helm chart, and integration tests are tracked in [issue #1](https://github.com/FerrLabs/FerrFlow-Operator/issues/1).\n\n## Custom resources\n\nTwo CRDs under `ferrflow.io/v1alpha1`:\n\n### `FerrFlowConnection` (shortname `ffc`)\n\nDeclares how to reach a FerrFlow instance. One per (namespace, org). Shared by every `FerrFlowSecret` in that namespace that targets the same organization.\n\n```yaml\napiVersion: ferrflow.io/v1alpha1\nkind: FerrFlowConnection\nmetadata:\n  name: prod\nspec:\n  url: https://ferrflow.example.com\n  organization: acme\n  tokenSecretRef:\n    name: ferrflow-api-token\n    key: token\n```\n\nThe referenced Secret must hold a FerrFlow API token (`fft_...`) with at least the `secrets:read` scope.\n\n### `FerrFlowSecret` (shortname `ffs`)\n\nDeclares a sync from a vault to a Kubernetes Secret.\n\n```yaml\napiVersion: ferrflow.io/v1alpha1\nkind: FerrFlowSecret\nmetadata:\n  name: web-env\nspec:\n  connectionRef: { name: prod }\n  project: web\n  vault: production          # FerrFlow vault name (often the environment)\n  selector:\n    names: [DATABASE_URL, STRIPE_KEY]   # omit to sync every key in the vault\n  target:\n    name: web-env            # target Secret name; defaults to metadata.name\n    type: Opaque\n  refreshInterval: 30m       # Go time.Duration; 0s disables scheduled refresh\n```\n\nOn reconciliation the operator calls `GET /api/v1/orgs/:org/projects/:project/vaults/by-name/:vault/secrets/reveal` once, writes the returned `{name: value}` map into `spec.target.name`, and sets the CR's `Ready` condition based on whether any requested keys were missing upstream.\n\nThe generated Secret is owned by the CR — deleting the CR garbage-collects the Secret.\n\n#### Value transforms\n\nRevealed values can be reshaped before they land in the target Secret via `spec.transforms`. Transforms are applied in order; each one sees the output of the previous step.\n\n```yaml\nspec:\n  connectionRef: { name: prod }\n  project: web\n  vault: production\n  selector:\n    names: [DATABASE_URL, STRIPE_KEY, CONFIG_JSON]\n  transforms:\n    - type: rename\n      from: DATABASE_URL\n      to: DB_URL\n    - type: base64Decode\n      keys: [STRIPE_KEY]          # omit `keys` to decode every value\n    - type: jsonExpand\n      key: CONFIG_JSON            # {\"db\":{\"host\":\"pg\"}} → CONFIG_JSON_DB_HOST=pg\n    - type: prefix\n      value: APP_                 # stamps APP_ on every remaining key\n```\n\nSupported types:\n\n| `type`         | Fields              | Effect                                                       |\n| -------------- | ------------------- | ------------------------------------------------------------ |\n| `prefix`       | `value`             | Prepends `value` to every key.                               |\n| `suffix`       | `value`             | Appends `value` to every key.                                |\n| `rename`       | `from`, `to`        | Projects one key. Missing `from` is a no-op; collisions fail.|\n| `base64Decode` | `keys` (optional)   | Decodes listed keys (or all when empty) from base64.         |\n| `jsonExpand`   | `key`               | Flattens a JSON object under `\u003cKEY\u003e_\u003cSUB\u003e`. Drops the source.|\n\nMalformed transforms (unknown type, invalid base64, non-object JSON, destination-key collisions) leave the CR in `Ready=False` with `Reason=TransformError` and increment `ferrflow_secret_sync_errors_total{reason=\"TransformError\"}`. The target Secret is not written on failure — workloads keep the last known-good value.\n\n## Running\n\n### Helm (recommended)\n\n```bash\nhelm install ferrflow-operator oci://ghcr.io/ferrlabs/charts/ferrflow-operator \\\n  --namespace ferrflow-operator-system --create-namespace\n```\n\nUpgrade: `helm upgrade` against the same release. CRDs carry `helm.sh/resource-policy: keep` so they survive uninstall (protects your CRs + managed Secrets). See [`charts/ferrflow-operator/README.md`](charts/ferrflow-operator/README.md) for the full `values.yaml` reference.\n\n### Locally against a cluster\n\n```bash\nmake install-crds   # CRDs only\nmake run            # runs the manager as your user, not as a Pod\n```\n\n### Raw manifests (without Helm at runtime)\n\nThe Helm chart is the single source of truth for all manifests (CRDs, RBAC,\nServiceAccount, Deployment). If your cluster policy forbids running Helm at\ndeploy time, render once and commit/apply the plain YAML:\n\n```bash\nkubectl create namespace ferrflow-operator-system\nhelm template ferrflow-operator charts/ferrflow-operator \\\n  --namespace ferrflow-operator-system \\\n  \u003e manager.yaml\nkubectl apply -f manager.yaml\n```\n\nNo duplicate `config/rbac/` or `config/crd/` lives in the repo — anything\nrendered from the chart *is* the canonical version.\n\n## Prerequisites in FerrFlow\n\nThe operator relies on endpoints in [`FerrLabs/FerrVault-Cloud`](https://github.com/FerrLabs/FerrVault-Cloud) that shipped in `api@v4.0.0`:\n\n- API token auth (`Authorization: Bearer fft_...`) with granular scopes — #268\n- `secrets:read` scope enforcement on all secrets routes — #268\n- Bulk reveal endpoint — #277\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md). Code of conduct in [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Vulnerability reports via [SECURITY.md](SECURITY.md).\n\n## License\n\n[MPL-2.0](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fferrlabs%2Fferrvault","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fferrlabs%2Fferrvault","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fferrlabs%2Fferrvault/lists"}