# 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`](/docs/guides/run),
   which resolves and decrypts into the process environment. Nothing is written
   to the cluster. This is the [CI/CD guide's Kubernetes pattern](/docs/guides/ci-cd#kubernetes).
2. **Sync into a Kubernetes `Secret`** — declaratively, with the
   [External Secrets Operator](https://external-secrets.io) (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:

```bash
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](/docs/guides/service-tokens)):

```bash
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

```bash
helm install seekrit-eso oci://registry-1.docker.io/seekritdev/seekrit-eso \
  --version 0.2.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:

```yaml
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: storefront
  namespace: seekrit-system
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
```

> **Note:** The generated `SecretStore` is namespaced (`secretStore.kind` defaults to `SecretStore`), so any `ExternalSecret` referencing it must live in the **same namespace you installed the chart into** — `seekrit-system` above. To reference it from other namespaces (e.g. your app's own namespace) instead, install with `--set secretStore.kind=ClusterSecretStore`.

ESO creates a `storefront-secrets` Secret in that namespace and keeps it in
sync. Consume it like any other Secret (from a pod in the same namespace):

```yaml
envFrom:
  - secretRef:
      name: storefront-secrets
```

Check status:

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

### Secret references

`${OTHER_SECRET}` [references](/docs/guides/references) are expanded by the
sidecar, over the merged environment, before ESO ever sees a value — so the
`Secret` in the cluster holds the assembled string.

### 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 `ExternalSecret`s.

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

## Surviving a seekrit outage

The sidecar's **first** resolve is fail-closed: if the seekrit API is unreachable
when it starts, it refuses to bind. That is the right default for a component
that hands out plaintext — but it means a pod rescheduled during an outage takes
every `ExternalSecret` it backs down with it. (Once running, it is already
resilient: a failed *refresh* keeps serving the last-good snapshot in memory.)

Turn on the last-known-good cache to close that gap:

```yaml
cache:
  enabled: true
  maxAge: 24h
  # emptyDir survives a container restart but not a reschedule.
  # Use a PVC to survive both:
  # volume:
  #   persistentVolumeClaim:
  #     claimName: seekrit-lkg-cache
```

The sidecar then writes each successful resolve — the **encrypted** response
only — to the volume, and a restart that cannot reach the API starts from that
copy instead of failing. It keeps retrying on a short backoff and switches to
live secrets as soon as the API answers.

Decrypting the cached copy still requires the service token, so the volume is no
more sensitive than the token `Secret` already mounted into the pod. The
trade-off is the one described in the
[CLI reference](/docs/reference/cli#last-known-good-cache): while the API is
unreachable, a revoked token keeps working until `maxAge` elapses. A *reachable*
API that refuses the token deletes the entry immediately.

## Security posture

> **Warning:** 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](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/)**
  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:
  ```bash
  --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

- Chart values and options: the [`seekrit-eso` chart](https://github.com/seekritdev/helm-charts/tree/main/charts/seekrit-eso).
- The sidecar's source: [`seekrit-sdk-server`](https://github.com/seekritdev/seekrit-sdk-server).
- Minting and scoping tokens: [Service tokens](/docs/guides/service-tokens).
- Runtime injection instead of syncing: [`seekrit run`](/docs/guides/run) and
  [CI/CD & containers](/docs/guides/ci-cd#kubernetes).
