seekrit
Docs/Kubernetes (ESO)

Kubernetes (External Secrets Operator)

There are two ways to get seekrit secrets into a Kubernetes workload:

  1. Inject at runtime — run your process through seekrit run, which resolves and decrypts into the process environment. Nothing is written to the cluster. This is the CI/CD guide's Kubernetes pattern.
  2. Sync into a Kubernetes Secret — declaratively, with the External Secrets Operator (ESO), so your pods consume a normal Secret via envFrom/valueFrom and nothing in them knows about seekrit. That's this guide.

How it works

seekrit is zero-knowledge: GET /v1/resolve returns only ciphertext plus a data key wrapped to your token's public key, and decryption happens client-side. ESO, which expects to pull plaintext from a provider, therefore can't read from the seekrit API directly.

The seekrit-eso Helm chart bridges that gap. It deploys a small in-cluster sidecar (seekrit-sdk-server) that holds a service token, resolves and decrypts locally, caches the result, and serves it over a tiny authed HTTP API — then generates a webhook SecretStore that points ESO at the sidecar. You run stock, unmodified ESO and write only ExternalSecret resources.

ExternalSecret ─▶ ESO (stock) ──webhook──▶ seekrit-sdk-server ──/v1/resolve──▶ seekrit API
 (you write)      (unmodified)             (the chart)                         (ciphertext only)
note

The seekrit API still never sees plaintext — decryption stays inside your cluster, exactly as it does for the CLI and seekrit-run. The sidecar is simply "another principal" holding a grant.

Prerequisites

1. External Secrets Operator, installed once per cluster:

helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
  -n external-secrets --create-namespace

2. A seekrit service token bound to the app environment you want to sync. Mint one with the CLI (see Service tokens):

seekrit token create --name eso --app storefront --env production
# prints:  skt_XXXXXXXX_…    (save it now)

The token is bound to that one environment and auto-granted its keys (plus any composed group slices), so it can read exactly that environment and nothing else.

Install the chart

helm install seekrit-eso oci://registry-1.docker.io/seekritdev/seekrit-eso \
  --version 0.1.0 \
  -n seekrit-system --create-namespace \
  --set seekrit.token=skt_XXXXXXXX_…

That deploys the sidecar and creates a SecretStore named seekrit. The chart also generates the sidecar's API key automatically and preserves it across helm upgrade.

tip

Managing secrets with GitOps? Don't put the token in --set. Create a Secret yourself (sealed-secrets, SOPS, etc.) and point the chart at it with --set seekrit.existingSecret=my-token-secret.

Write an ExternalSecret

Now the payoff — the only thing your team writes per app:

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: storefront
  namespace: default
spec:
  refreshInterval: 1m
  secretStoreRef:
    name: seekrit
    kind: SecretStore
  target:
    name: storefront-secrets      # the Kubernetes Secret ESO creates/manages
  data:
    - secretKey: DATABASE_URL      # key in the resulting Secret
      remoteRef:
        key: DATABASE_URL          # secret name in your seekrit environment
    - secretKey: STRIPE_API_KEY
      remoteRef:
        key: STRIPE_API_KEY

ESO creates a storefront-secrets Secret and keeps it in sync. Consume it like any other Secret:

envFrom:
  - secretRef:
      name: storefront-secrets

Check status:

kubectl get externalsecret storefront
kubectl -n seekrit-system logs deploy/seekrit-eso

Pulling a whole environment

ESO's webhook provider fetches one key at a time, so list the keys you want in data[] (the sidecar caches the decrypted environment, so each fetch is cheap). To pull everything without enumerating keys, template against the sidecar's whole-environment map with dataFrom + target.template — the sidecar exposes GET /v1/secrets returning {"data": {NAME: value, …}}.

note

A native seekrit ESO provider (first-class provider: {seekrit:} with built-in dataFrom support) is planned. It reuses this same sidecar, so when it lands the chart swaps its generated SecretStore with no change to your ExternalSecrets.

Rotation & refresh timing

Two intervals stack. The sidecar re-resolves from seekrit every refreshInterval (chart value, default 60s), and ESO re-reads the sidecar every spec.refreshInterval on the ExternalSecret. So a rotated secret reaches your pods within roughly the sum of the two. Lower either for faster propagation.

Security posture

caution

ESO writes decrypted values into Kubernetes Secret objects (base64 in etcd). That is inherent to how ESO works — seekrit's control plane stays zero-knowledge, but the cluster becomes a trusted decryption endpoint, the same as running seekrit-run on a CI box.

Harden accordingly:

  • Enable etcd encryption-at-rest so the synced Secrets aren't stored in plaintext.
  • Restrict who can reach the sidecar. It decrypts everything the token can read, gated by an API key. Turn on the chart's NetworkPolicy to allow ingress only from your ESO pods:
    --set networkPolicy.enabled=true \
    --set 'networkPolicy.from[0].namespaceSelector.matchLabels.kubernetes\.io/metadata\.name=external-secrets'
    
  • One token, one environment. The token's scope is the sidecar's blast radius. To sync another app or environment, install a second release with its own token and secretStore.name — don't broaden one token.

Reference