MCPcopy Index your code
hub / github.com/digitalis-io/vals-operator

github.com/digitalis-io/vals-operator @v0.8.3

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.8.3 ↗ · + Follow
194 symbols 504 edges 26 files 110 documented · 57%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README
<img src="https://github.com/digitalis-io/vals-operator/raw/v0.8.3/Vals-Operator-Logo.png" width="30%" align="center" alt="vals-operator">

Vals-Operator

CI GoDoc Go Report Card GitHub GitHub release (latest by date) Artifact Hub

Vals-Operator is a Kubernetes operator that integrates external secret stores with Kubernetes, keeping your secrets in sync.

Here at Digitalis we love vals, it's a tool we use daily to keep secrets stored securely. Inspired by it, we have created an operator to manage Kubernetes secrets. As Digitalis and our sister company AxonOps are data companies, we also added a set of features tailored for running databases.

vals-operator syncs secrets from any secrets store supported by vals into Kubernetes. Also, vals-operator supports database secrets as provider by the HashiCorp Vault Secret Engine.

Demo

You can watch this brief video on how it works:

YouTube

Mirroring secrets

Vals-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.

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

The operator binary accepts the following flags. All flags are optional unless noted.

Flag Type Default Description
-metrics-bind-address string :8080 Address the metrics endpoint binds to.
-health-probe-bind-address string :8081 Address the health probe endpoint binds to.
-reconcile-period duration 5s How often the controller re-queues reconciliation events.
-ttl duration 5m0s How often each secret is checked against the backend store for updates.
-watch-namespaces string "" Comma-separated list of namespaces the operator watches. Empty means all namespaces.
-exclude-namespaces string "" Comma-separated list of namespaces the operator ignores entirely.
-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".
-leader-elect bool false Enables leader election, ensuring only one active controller instance when running multiple replicas.
-disable-namespace-sync bool false Blocks all cross-namespace ref+k8s:// references. See Cross-Namespace Reference Security.
-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

The 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.

Two flags control this behaviour.

-disable-namespace-sync

When 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.

A rejected reference produces the event:

cross-namespace ref+k8s:// is disabled: namespace "tenant-a" cannot reference "tenant-b"

Use this flag in clusters where no cross-namespace secret sharing is required. It is the most restrictive option.

helm upgrade --install vals-operator digitalis/vals-operator \
  --set "extraArgs[0]=-disable-namespace-sync=true"

-allowed-namespaces-for-sync

Provides 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.

A reference targeting a namespace not in the allowlist produces the event:

cross-namespace ref+k8s:// denied: namespace "restricted" is not in the allowed list

When the value is empty (the default), all namespaces are allowed — subject to -disable-namespace-sync.

helm upgrade --install vals-operator digitalis/vals-operator \
  --set "extraArgs[0]=-allowed-namespaces-for-sync=shared-secrets,platform"

Precedence

-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.

-disable-namespace-sync -allowed-namespaces-for-sync Result
false "" (empty) All cross-namespace refs allowed
false "ns-a,ns-b" Only refs targeting ns-a or ns-b allowed
true any value All cross-namespace refs rejected

Same-namespace references are always allowed in every configuration.

Installation

You can use the helm chart to install vals-operator. First of all, add the repository to your helm installation:

helm repo add digitalis https://digitalis-io.github.io/helm-charts

Install via OCI Registry (Helm 3.8+)

The 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.

helm install vals-operator oci://ghcr.io/digitalis-io/helm-charts/vals-operator --version <version>

To upgrade:

helm upgrade vals-operator oci://ghcr.io/digitalis-io/helm-charts/vals-operator --version <version>

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.

Verifying Signatures

Container images and the Helm OCI chart are signed on every release using cosign keyless signing via GitHub Actions OIDC. No long-lived signing key is used — verification trusts only the Sigstore public infrastructure and the workflow identity that produced the artifact.

Install cosign with brew install cosign or grab a binary from the Sigstore releases page.

Important: Always verify by an immutable reference — the version tag (vX.Y.Z) or, preferably, the image digest (@sha256:...). The mutable :latest tag is not a stable signing target.

Verify the container image (substitute the release tag):

cosign verify ghcr.io/digitalis-io/vals-operator:<TAG> \
  --certificate-identity-regexp "^https://github\.com/digitalis-io/vals-operator/\.github/workflows/release\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  | jq .

Verify the Helm OCI chart (substitute the chart version, no leading v):

cosign verify ghcr.io/digitalis-io/helm-charts/vals-operator:<CHART_VERSION> \
  --certificate-identity-regexp "^https://github\.com/digitalis-io/vals-operator/\.github/workflows/release\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  | jq .

Software Bill of Materials

Every released container image ships with an SPDX 2.3 JSON and a CycloneDX 1.5 JSON SBOM. Both are attached as assets to the corresponding GitHub Release, and the SPDX SBOM is additionally recorded as a cosign attestation on the image digest.

Download from the GitHub Release:

https://github.com/digitalis-io/vals-operator/releases/download/<TAG>/vals-operator-<TAG>-sbom.spdx.json
https://github.com/digitalis-io/vals-operator/releases/download/<TAG>/vals-operator-<TAG>-sbom.cdx.json

Verify the SBOM attestation against the image digest:

cosign verify-attestation \
  --type spdxjson \
  --certificate-identity-regexp "^https://github\.com/digitalis-io/vals-operator/\.github/workflows/release\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/digitalis-io/vals-operator@<DIGEST> \
  | jq '.payload | @base64d | fromjson'

Secrets Backend Configuration

vals-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.

Using OpenBao (Recommended for New Deployments)

# Example with OpenBao using Kubernetes auth
helm upgrade --install vals-operator --create-namespace -n vals-operator \
  --set "openbao.enabled=true" \
  --set "openbao.address=http://openbao:8200" \
  --set "openbao.auth.kubernetes.roleId=vals-operator" \
  digitalis/vals-operator

# Example with OpenBao using AppRole auth
helm upgrade --install vals-operator --create-namespace -n vals-operator \
  --set "openbao.enabled=true" \
  --set "openbao.address=http://openbao:8200" \
  --set "openbao.auth.approle.roleId=my-role-id" \
  --set "openbao.auth.approle.secretId=my-secret-id" \
  digitalis/vals-operator

Using HashiCorp Vault (For Existing Deployments)

# Example with Vault using Kubernetes auth
helm upgrade --install vals-operator --create-namespace -n vals-operator \
  --set "vault.enabled=true" \
  --set "vault.address=http://vault:8200" \
  --set "vault.auth.kubernetes.roleId=vals-operator" \
  digitalis/vals-operator

# Example with Vault using environment variables (legacy method - still supported)
helm upgrade --install vals-operator --create-namespace -n vals-operator \
  --set "env[0].name=VAULT_ROLE_ID,env[0].value=vals-operator" \
  --set "env[1].name=VAULT_ADDR,env[1].value=https://vault:8200" \
  digitalis/vals-operator

# Example for AWS using a secret
kubectl create secret generic -n vals-operator aws-creds \
  --from-literal=AWS_ACCESS_KEY_ID=foo \
  --from-literal=AWS_SECRET_ACCESS_KEY=bar \
  --from-literal=AWS_DEFAULT_REGION=us-west-2

helm upgrade --install vals-operator --create-namespace -n vals-operator \
  --set "secretEnv[0].secretRef.name=aws-creds"  \
  digitalis/vals-operator

# Another example using a Google Cloud service account
kubectl create secret generic -n vals-operator google-creds \
  --from-file=credentials.json=/path/to/service_account.json

helm upgrade --install vals-operator --create-namespace -n vals-operator \
  --set "env[0].name=GOOGLE_APPLICATION_CREDENTIALS,env[0].value=/secret/credentials.json" \
  --set "env[1].name=GCP_PROJECT,env[1].value=my_project" \
  --set "volumes[0].name=creds,volumes[0].secret.secretName=google-creds" \
  --set "volumeMounts[0].name=creds,volumeMounts[0].mountPath=/secret" \
  digitalis/vals-operator

:information_source: Check out the documentation for further details and examples including EKS integration.

Dual Backend Support (Vault & OpenBao)

vals-operator now provides seamless dual backend support, allowing you to use either HashiCorp Vault or OpenBao without code changes. This enables:

  • Zero-downtime migration from Vault to OpenBao
  • Backwards compatibility with existing Vault deployments
  • Environment variable fallback - OpenBao variables can fall back to Vault variables

Backend Selection

The operator automatically detects which backend to use: 1. If BAO_ADDR is set → Uses OpenBao 2. If VAULT_ADDR is set (and BAO_ADDR is not) → Uses HashiCorp Vault 3. If both are set → Uses OpenBao with a warning (OpenBao takes precedence) 4. If neither is set → Error

Environment Variable Compatibility

For backwards compatibility, environment variables automatically fall back: - BAO_* variables fall back to VAULT_* if not set - This allows gradual migration without breaking existing configurations

Example:

# These configurations are equivalent:
BAO_ADDR=http://openbao:8200
VAULT_ROLE_ID=my-role  # Will be used for OpenBao if BAO_ROLE_ID is not set

# Or explicitly set both:
BAO_ADDR=http://openbao:8200
BAO_ROLE_ID=my-role

For detailed migration instructions, see OPENBAO.md and DUAL_BACKEND_SUPPORT.md.

Authentication Configuration

OpenBao Authentication

For OpenBao, you can use the following environment variables:

  • BAO_ADDR: URL to the OpenBao server, e.g., http://openbao:8200
  • BAO_ROLE_ID: Required for Kubernetes authentication
  • BAO_LOGIN_USER and BAO_LOGIN_PASSWORD

Extension points exported contracts — how you extend this code

Core symbols most depended-on inside this repo

Shape

Method 114
Function 49
Struct 27
Interface 2
TypeAlias 2

Languages

Go100%

Modules by API surface

vault/vault_client.go22 symbols
vault/openbao_client.go22 symbols
vault/client_interface.go22 symbols
apis/digitalis.io/v1/zz_generated.deepcopy.go18 symbols
controllers/valssecret_controller.go17 symbols
controllers/dbsecret_controller.go16 symbols
apis/digitalis.io/v1beta1/zz_generated.deepcopy.go16 symbols
utils/utils.go10 symbols
vault/vault.go9 symbols
apis/digitalis.io/v1/valssecret_types.go9 symbols
apis/digitalis.io/v1beta1/dbsecret_types.go8 symbols
vault/common.go5 symbols

For agents

$ claude mcp add vals-operator \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page