{"id":13644147,"url":"https://github.com/digitalis-io/vals-operator","last_synced_at":"2026-05-10T22:10:16.269Z","repository":{"id":37476025,"uuid":"423576288","full_name":"digitalis-io/vals-operator","owner":"digitalis-io","description":"Kubernetes Operator to sync secrets between different secret backends and Kubernetes","archived":false,"fork":false,"pushed_at":"2026-04-15T17:14:38.000Z","size":1018,"stargazers_count":166,"open_issues_count":8,"forks_count":8,"subscribers_count":5,"default_branch":"main","last_synced_at":"2026-04-15T18:31:23.133Z","etag":null,"topics":["devops","devsecops","kubernetes","kubernetes-operator","secrets","secrets-management","security","vals"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/digitalis-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","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":null,"dco":null,"cla":null}},"created_at":"2021-11-01T18:37:13.000Z","updated_at":"2026-03-20T08:54:31.000Z","dependencies_parsed_at":"2024-05-28T03:15:01.373Z","dependency_job_id":"ca6e49e3-c8d3-4c44-b263-9e2abdde5060","html_url":"https://github.com/digitalis-io/vals-operator","commit_stats":null,"previous_names":[],"tags_count":49,"template":false,"template_full_name":null,"purl":"pkg:github/digitalis-io/vals-operator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digitalis-io%2Fvals-operator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digitalis-io%2Fvals-operator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digitalis-io%2Fvals-operator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digitalis-io%2Fvals-operator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/digitalis-io","download_url":"https://codeload.github.com/digitalis-io/vals-operator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digitalis-io%2Fvals-operator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32873240,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-10T13:40:02.631Z","status":"ssl_error","status_checked_at":"2026-05-10T13:40:02.145Z","response_time":54,"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":["devops","devsecops","kubernetes","kubernetes-operator","secrets","secrets-management","security","vals"],"created_at":"2024-08-02T01:01:58.140Z","updated_at":"2026-05-10T22:10:16.256Z","avatar_url":"https://github.com/digitalis-io.png","language":"Go","funding_links":[],"categories":["Go","security"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003cimg src=\"Vals-Operator-Logo.png\" width=\"30%\" align=\"center\" alt=\"vals-operator\"\u003e\n\u003c/p\u003e\n\n# Vals-Operator\n\n[![CI](https://github.com/digitalis-io/vals-operator/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/digitalis-io/vals-operator/actions/workflows/pre-commit.yml)\n[![GoDoc](https://godoc.org/github.com/digitalis-io/vals-operator?status.svg)](https://pkg.go.dev/github.com/digitalis-io/vals-operator?tab=doc)\n[![Go Report Card](https://goreportcard.com/badge/github.com/digitalis-io/vals-operator)](https://goreportcard.com/report/github.com/digitalis-io/vals-operator)\n![GitHub](https://img.shields.io/github/license/digitalis-io/vals-operator)\n![GitHub release (latest by date)](https://img.shields.io/github/v/release/digitalis-io/vals-operator)\n\u003ca href=\"https://artifacthub.io/packages/helm/vals-operator/vals-operator\"\u003e\u003cimg alt=\"Artifact Hub\" src=\"https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/vals-operator\" /\u003e\u003c/a\u003e\n\n**Vals-Operator** is a Kubernetes operator that integrates external secret stores with Kubernetes, keeping your secrets in sync.\n\nHere at [Digitalis](https://digitalis.io) we love [vals](https://github.com/helmfile/vals), it's a tool we use daily to keep secrets stored securely. Inspired by it,\nwe have created an operator to manage Kubernetes secrets. As [Digitalis](https://digitalis.io) and our sister company [AxonOps](https://axonops.com) are data companies,\nwe also added a set of features tailored for running databases.\n\n*vals-operator* syncs secrets from any secrets store supported by [vals](https://github.com/helmfile/vals) into Kubernetes. Also, *vals-operator* supports database secrets\nas provider by the [HashiCorp Vault Secret Engine](https://developer.hashicorp.com/vault/docs/secrets/databases).\n\n## Demo\n\nYou can watch this brief video on how it works:\n\n[![YouTube](./youtube-video.png)](https://www.youtube.com/watch?feature=player_embedded\u0026v=wLzkrKdSBT8)\n\n## Mirroring secrets\n\nVals-operator can copy secrets between namespaces using the `ref+k8s://namespace/secret#key` format. This lets a `ValsSecret` in one namespace pull a value from a Kubernetes secret in another namespace and keep it in sync.\n\n\u003e **Warning:** Cross-namespace `ref+k8s://` references allow any namespace with a `ValsSecret` to read secrets from other namespaces, subject only to the operator's RBAC permissions — not the requesting namespace's own permissions. Admins SHOULD restrict this behaviour in multi-tenant clusters using the flags documented in [Operator Flags](#operator-flags).\n\n# Operator Flags\n\nThe operator binary accepts the following flags. All flags are optional unless noted.\n\n| Flag | Type | Default | Description |\n|------|------|---------|-------------|\n| `-metrics-bind-address` | string | `:8080` | Address the metrics endpoint binds to. |\n| `-health-probe-bind-address` | string | `:8081` | Address the health probe endpoint binds to. |\n| `-reconcile-period` | duration | `5s` | How often the controller re-queues reconciliation events. |\n| `-ttl` | duration | `5m0s` | How often each secret is checked against the backend store for updates. |\n| `-watch-namespaces` | string | `\"\"` | Comma-separated list of namespaces the operator watches. Empty means all namespaces. |\n| `-exclude-namespaces` | string | `\"\"` | Comma-separated list of namespaces the operator ignores entirely. |\n| `-record-changes` | bool | `true` | Records each secret update as a Kubernetes Event, visible via `kubectl describe`. Can be overridden per resource with the annotation `vals-operator.digitalis.io/record: \"true\"`. |\n| `-leader-elect` | bool | `false` | Enables leader election, ensuring only one active controller instance when running multiple replicas. |\n| `-disable-namespace-sync` | bool | `false` | Blocks all cross-namespace `ref+k8s://` references. See [Cross-Namespace Reference Security](#cross-namespace-reference-security). |\n| `-allowed-namespaces-for-sync` | string | `\"\"` | Comma-separated allowlist of namespaces that may be referenced via `ref+k8s://`. See [Cross-Namespace Reference Security](#cross-namespace-reference-security). |\n\n## Cross-Namespace Reference Security\n\nThe `ref+k8s://namespace/secret#key` syntax lets a `ValsSecret` read a Kubernetes secret from a different namespace. In multi-tenant clusters this is a privilege escalation vector: a tenant who can create `ValsSecret` resources can read secrets from any namespace the operator has RBAC access to.\n\nTwo flags control this behaviour.\n\n### `-disable-namespace-sync`\n\nWhen set to `true`, the operator rejects any `ref+k8s://` reference where the target namespace differs from the `ValsSecret`'s own namespace. Same-namespace references are never blocked.\n\nA rejected reference produces the event:\n\n```\ncross-namespace ref+k8s:// is disabled: namespace \"tenant-a\" cannot reference \"tenant-b\"\n```\n\nUse this flag in clusters where no cross-namespace secret sharing is required. It is the most restrictive option.\n\n```sh\nhelm upgrade --install vals-operator digitalis/vals-operator \\\n  --set \"extraArgs[0]=-disable-namespace-sync=true\"\n```\n\n### `-allowed-namespaces-for-sync`\n\nProvides a namespace-level allowlist for cross-namespace `ref+k8s://` references. Only namespaces named in the list may be used as the target of a cross-namespace reference. Same-namespace references are always permitted regardless of this list.\n\nA reference targeting a namespace not in the allowlist produces the event:\n\n```\ncross-namespace ref+k8s:// denied: namespace \"restricted\" is not in the allowed list\n```\n\nWhen the value is empty (the default), all namespaces are allowed — subject to `-disable-namespace-sync`.\n\n```sh\nhelm upgrade --install vals-operator digitalis/vals-operator \\\n  --set \"extraArgs[0]=-allowed-namespaces-for-sync=shared-secrets,platform\"\n```\n\n### Precedence\n\n`-disable-namespace-sync` takes precedence over `-allowed-namespaces-for-sync`. When `-disable-namespace-sync=true`, the allowlist is not consulted — all cross-namespace references are rejected regardless of the allowlist contents.\n\n| `-disable-namespace-sync` | `-allowed-namespaces-for-sync` | Result |\n|---------------------------|-------------------------------|--------|\n| `false` | `\"\"` (empty) | All cross-namespace refs allowed |\n| `false` | `\"ns-a,ns-b\"` | Only refs targeting `ns-a` or `ns-b` allowed |\n| `true` | any value | All cross-namespace refs rejected |\n\nSame-namespace references are always allowed in every configuration.\n\n# Installation\n\nYou can use the helm chart to install `vals-operator`. First of all, add the repository to your helm installation:\n\n```sh\nhelm repo add digitalis https://digitalis-io.github.io/helm-charts\n```\n\n### Install via OCI Registry (Helm 3.8+)\n\nThe chart is published as an OCI artifact on every release. This is the RECOMMENDED installation method for Helm 3.8 and later — no `helm repo add` step is required.\n\n```bash\nhelm install vals-operator oci://ghcr.io/digitalis-io/helm-charts/vals-operator --version \u003cversion\u003e\n```\n\nTo upgrade:\n\n```bash\nhelm upgrade vals-operator oci://ghcr.io/digitalis-io/helm-charts/vals-operator --version \u003cversion\u003e\n```\n\n\u003e **Note:** The traditional Helm repository at `https://digitalis-io.github.io/helm-charts` remains available during the transition. Consumers on Helm 3.7 or earlier MUST use the `helm repo add` method above.\n\n## Secrets Backend Configuration\n\nvals-operator now supports **both HashiCorp Vault and OpenBao** as secrets backends. The operator automatically detects which backend to use based on the environment variables you provide.\n\n### Using OpenBao (Recommended for New Deployments)\n\n```sh\n# Example with OpenBao using Kubernetes auth\nhelm upgrade --install vals-operator --create-namespace -n vals-operator \\\n  --set \"openbao.enabled=true\" \\\n  --set \"openbao.address=http://openbao:8200\" \\\n  --set \"openbao.auth.kubernetes.roleId=vals-operator\" \\\n  digitalis/vals-operator\n\n# Example with OpenBao using AppRole auth\nhelm upgrade --install vals-operator --create-namespace -n vals-operator \\\n  --set \"openbao.enabled=true\" \\\n  --set \"openbao.address=http://openbao:8200\" \\\n  --set \"openbao.auth.approle.roleId=my-role-id\" \\\n  --set \"openbao.auth.approle.secretId=my-secret-id\" \\\n  digitalis/vals-operator\n```\n\n### Using HashiCorp Vault (For Existing Deployments)\n\n```sh\n# Example with Vault using Kubernetes auth\nhelm upgrade --install vals-operator --create-namespace -n vals-operator \\\n  --set \"vault.enabled=true\" \\\n  --set \"vault.address=http://vault:8200\" \\\n  --set \"vault.auth.kubernetes.roleId=vals-operator\" \\\n  digitalis/vals-operator\n\n# Example with Vault using environment variables (legacy method - still supported)\nhelm upgrade --install vals-operator --create-namespace -n vals-operator \\\n  --set \"env[0].name=VAULT_ROLE_ID,env[0].value=vals-operator\" \\\n  --set \"env[1].name=VAULT_ADDR,env[1].value=https://vault:8200\" \\\n  digitalis/vals-operator\n\n# Example for AWS using a secret\nkubectl create secret generic -n vals-operator aws-creds \\\n  --from-literal=AWS_ACCESS_KEY_ID=foo \\\n  --from-literal=AWS_SECRET_ACCESS_KEY=bar \\\n  --from-literal=AWS_DEFAULT_REGION=us-west-2\n\nhelm upgrade --install vals-operator --create-namespace -n vals-operator \\\n  --set \"secretEnv[0].secretRef.name=aws-creds\"  \\\n  digitalis/vals-operator\n\n# Another example using a Google Cloud service account\nkubectl create secret generic -n vals-operator google-creds \\\n  --from-file=credentials.json=/path/to/service_account.json\n\nhelm upgrade --install vals-operator --create-namespace -n vals-operator \\\n  --set \"env[0].name=GOOGLE_APPLICATION_CREDENTIALS,env[0].value=/secret/credentials.json\" \\\n  --set \"env[1].name=GCP_PROJECT,env[1].value=my_project\" \\\n  --set \"volumes[0].name=creds,volumes[0].secret.secretName=google-creds\" \\\n  --set \"volumeMounts[0].name=creds,volumeMounts[0].mountPath=/secret\" \\\n  digitalis/vals-operator\n```\n\n\u003e :information_source: Check out the [documentation](./docs/index.md) for further details and examples including EKS integration.\n\n## Dual Backend Support (Vault \u0026 OpenBao)\n\nvals-operator now provides **seamless dual backend support**, allowing you to use either HashiCorp Vault or OpenBao without code changes. This enables:\n\n- **Zero-downtime migration** from Vault to OpenBao\n- **Backwards compatibility** with existing Vault deployments\n- **Environment variable fallback** - OpenBao variables can fall back to Vault variables\n\n### Backend Selection\n\nThe operator automatically detects which backend to use:\n1. If `BAO_ADDR` is set → Uses OpenBao\n2. If `VAULT_ADDR` is set (and `BAO_ADDR` is not) → Uses HashiCorp Vault\n3. If both are set → Uses OpenBao with a warning (OpenBao takes precedence)\n4. If neither is set → Error\n\n### Environment Variable Compatibility\n\nFor backwards compatibility, environment variables automatically fall back:\n- `BAO_*` variables fall back to `VAULT_*` if not set\n- This allows gradual migration without breaking existing configurations\n\nExample:\n```sh\n# These configurations are equivalent:\nBAO_ADDR=http://openbao:8200\nVAULT_ROLE_ID=my-role  # Will be used for OpenBao if BAO_ROLE_ID is not set\n\n# Or explicitly set both:\nBAO_ADDR=http://openbao:8200\nBAO_ROLE_ID=my-role\n```\n\nFor detailed migration instructions, see [OPENBAO.md](OPENBAO.md) and [DUAL_BACKEND_SUPPORT.md](DUAL_BACKEND_SUPPORT.md).\n\n## Authentication Configuration\n\n### OpenBao Authentication\n\nFor OpenBao, you can use the following environment variables:\n\n* **BAO_ADDR**: URL to the OpenBao server, e.g., http://openbao:8200\n* **BAO_ROLE_ID**: Required for Kubernetes authentication\n* **BAO_LOGIN_USER** and **BAO_LOGIN_PASSWORD**: For `userpass` authentication (insecure, not recommended)\n* **BAO_APP_ROLE** and **BAO_SECRET_ID**: For `approle` authentication\n\n### HashiCorp Vault Authentication\n\nFor HashiCorp Vault, you can use the following environment variables:\n\n* **VAULT_ADDR**: URL to the Vault server, e.g., http://vault:8200\n* **VAULT_ROLE_ID**: Required for Kubernetes authentication\n* **VAULT_LOGIN_USER** and **VAULT_LOGIN_PASSWORD**: For `userpass` authentication (insecure, not recommended)\n* **VAULT_APP_ROLE** and **VAULT_SECRET_ID**: For `approle` authentication\n\nFor Kubernetes authentication with either backend, refer to the respective documentation:\n- [OpenBao Kubernetes Auth](https://openbao.org/docs/auth/kubernetes/)\n- [Vault Kubernetes Auth](https://www.vaultproject.io/docs/auth/kubernetes)\n\n# Usage\n\n```yaml\napiVersion: digitalis.io/v1\nkind: ValsSecret\nmetadata:\n  name: vals-secret-sample\n  labels:\n    owner: digitalis.io\nspec:\n  name: my-secret # Optional, default is the resource name\n  ttl: 3600       # Optional, default is 5 minutes. The secret will be checked at every \"reconcile period\". See below.\n  type: Opaque    # Default type, others supported\n  data:\n    username:\n      ref: ref+vault://secret/database/username\n      encoding: text\n    password:\n      ref: ref+vault://secret/database/password\n      encoding: text\n    ssh:\n      ref: ref+vault://secret/database/ssh-private-key\n      encoding: base64\n    aws-user:\n      ref: ref+awssecrets://kube/test#username\n    aws-pass:\n      ref: ref+awssecrets://kube/test#password\n    ns-secret:\n      ref: ref+k8s://namespace/secret#key\n    plain-text:\n      ref: literal_name # this is not processed by any secrets agent but is added to the secret as a literal string\n  template:\n    config.yaml: |\n      # Config generated by Vals-Operator on {{ now | date \"2006-01-02\" }}\n      username: {{.username}}\n      password: {{.password}}\n      {{- if .url }}\n      url: {{ .url | lower }}\n      {{ end }}\n  rollout: # optional: run a `rollout` to make the pods use new secret\n    - kind: Deployment\n      name: myapp\n```\n\nThe example above will create a secret named `my-secret` and get the values from the different sources. The secret will be kept in sync against the backed secrets store.\n\nThe `TTL` is optional and used to decrease the number of times the operator calls the backend secrets store as some of them such as [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/pricing/) will incur a cost.\n\nThe default encoding is `text` but you can change it to `base64` per secret reference. This way you can, for example, base64 encode large configuration files. If you omit the `ref+` prefix `vals-operator` will not process the string and it will be added to the secret as as literal string.\n\nYou may also use GoLang templates to format a secret. You can inject as variables any of the keys referenced in the `data` section to format, for example, a configuration file.\nThe [sprig](https://github.com/Masterminds/sprig/blob/master/docs/index.md) functions are supported.\n\n## Vault/OpenBao database credentials\n\n---\n\u003e **_NOTE:_**  Vault \u003e= 1.10 or OpenBao \u003e= 2.0 is required for this feature to work\n---\n\nA great feature in HashiCorp Vault and OpenBao is the ability to generate [database credentials](https://developer.hashicorp.com/vault/docs/secrets/databases) dynamically.\nThe missing part is you need these credentials in Kubernertes where your applications are. This is why we have added a new resource definition to do just that:\n\n```yaml\napiVersion: digitalis.io/v1beta1\nkind: DbSecret\nmetadata:\n  name: cassandra\nspec:\n  renew: true # this is the default, otherwise a new credential will be generated every time\n  vault:\n    role: readonly\n    mount: cass000\n  template: # optional: change the secret format\n    CASSANDRA_USERNAME: \"{{ .username }}\"\n    CASSANDRA_PASSWORD: \"{{ .password }}\"\n  rollout: # optional: run a `rollout` to make the pods use new credentials\n    - kind: Deployment\n      name: cassandra-client\n    - kind: StatefulSet\n      name: cassandra-client-other\n```\n\n## Advance config: password rotation\n\nIf you're running a database you may want to keep the secrets in sync between your secrets store, Kubernetes and the database. This can be handy for password rotation to ensure the clients don't use the same password all the time. Please be aware your client *must* suppport re-reading the secret and reconnecting whenever it is updated.\n\n_We don't yet support TLS, we'll add it to future releases._\n\n```yaml\n---\napiVersion: digitalis.io/v1\nkind: ValsSecret\nmetadata:\n  name: vals-secret-sample\n  labels:\n    owner: digitalis.io\nspec:\n  name: my-secret # Optional, default is the resource name\n  ttl: 10         # Optional, default is 0. The secret will be checked at every \"reconcile period\". See below.\n  type: Opaque    # Default type, others supported\n  data:\n    username:\n      ref: ref+gcpsecrets://databases/test#username\n      encoding: text\n    password:\n      ref: ref+gcpsecrets://databases/test#password\n      encoding: text\n  databases:\n    - driver: cassandra\n      loginCredentials:\n        secretName: cassandra-creds # secret containing the username and password to access the DB and run the below query\n        usernameKey: username       # in the secret, which key contains the username (default `cassandra`)\n        passwordKey: password       # in the secret, which key contains the password\n      port: 9042\n      usernameKey: username\n      passwordKey: password\n      hosts:                        # list all your cassandra nodes here\n        - cassandra01\n        - cassandra02\n    - driver: postgres\n      loginCredentials:\n        secretName: postgres-creds\n        usernameKey: username\n        passwordKey: password\n      port: 5432\n      usernameKey: username\n      passwordKey: password\n      hosts:\n        - postgres\n    - driver: mysql\n      loginCredentials:\n        secretName: mysql-creds\n        namespace: mysql-server\n        passwordKey: mysql-root-password # if username is omitted it defaults to `mysql`\n      port: 3306\n      usernameKey: username\n      passwordKey: password\n      userHost: \"%\"                     # default\n      hosts:\n        - mysql\n    - driver: elastic\n      loginCredentials:\n        secretName: elastic-creds\n        namespace: elastic-server\n        usernameKey: username           # the username defaults to 'elastic' if not provided\n        passwordKey: password\n      port: 9200\n      usernameKey: username\n      passwordKey: password\n      hosts:\n        - my-elastic                    # this would be converted to http://my-elastic:9200\n        - https://my-other-elastic:9200 # provide full URL instead\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdigitalis-io%2Fvals-operator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdigitalis-io%2Fvals-operator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdigitalis-io%2Fvals-operator/lists"}