Kubernetes operator

Updated

Declare a KrypticSecret and the operator keeps a native Kubernetes Secret in sync with a Kryptic project environment. No init containers, no sidecars, and no secrets in your manifests.

Source: github.com/dev-kryptic/Kryptic.K8s.Operator (Apache 2.0). Install from the latest GitHub Release so the image tag matches a published build. The operator decrypts inside the cluster with Kryptic.Encryption.Go: the platform serves ciphertext, the machine client secret unwraps the operator's private key, that key opens the org key, and the org key opens each envelope.

Install

kubectl apply -f https://github.com/dev-kryptic/Kryptic.K8s.Operator/releases/latest/download/crd.yaml
kubectl apply -f https://github.com/dev-kryptic/Kryptic.K8s.Operator/releases/latest/download/operator.yaml

The operator image is ghcr.io/dev-kryptic/kryptic-operator. After the first release, that GHCR package must be public or cluster pulls will 401.

Create a machine identity in the dashboard (vault unlocked), grant it the organization key on Approvals if it is still waiting, then store its credentials in the namespace where your KrypticSecrets will live. That per-namespace Secret is the recommended production path. Settings → Encryption is only for initializing the org key and the Emergency Kit.

kubectl create secret generic kryptic-machine-credentials \
  --from-literal=clientId=kmi_xxxxxxxxxxxxxxxx \
  --from-literal=clientSecret=<the one-time secret>

Self-hosted platforms add --from-literal=apiUrl=https://pipelines.kryptic.example.com.

Optional cluster machine identity

For non-production clusters you can set one machine identity on the operator and omit spec.auth on every KrypticSecret. Create the Secret once in kryptic-system and wire it into the operator Deployment as KRYPTIC_CLIENT_ID, KRYPTIC_CLIENT_SECRET, and optional KRYPTIC_API_URL (the commented block in operator.yaml).

The cluster identity is opt-in per namespace: list the namespaces that may use it in KRYPTIC_CLUSTER_NAMESPACES (comma-separated; "*" allows all; empty or unset allows none). A KrypticSecret without spec.auth in a namespace outside the list fails with a status message. apiUrl values must be https; for local development only, KRYPTIC_ALLOW_INSECURE_API_URL=true permits http.

kubectl create secret generic kryptic-machine-credentials \
  --namespace kryptic-system \
  --from-literal=clientId=kmi_xxxxxxxxxxxxxxxx \
  --from-literal=clientSecret=<the one-time secret>
apiVersion: kryptic.dev/v1
kind: KrypticSecret
metadata:
  name: backend-secrets
spec:
  projectId: proj_a1b2c3d4e5f6
  environment: development
  secretName: backend-env

Use this only on non-production clusters. One identity for the whole cluster means every KrypticSecret shares the same blast radius. Prefer a Secret in each application namespace for production. If spec.auth.secretRef is set, that Secret always wins and a missing name does not fall back to the cluster identity.

Declare a secret

apiVersion: kryptic.dev/v1
kind: KrypticSecret
metadata:
  name: backend-secrets
spec:
  projectId: proj_a1b2c3d4e5f6
  environment: production
  secretName: backend-env
  refreshInterval: 5m
  auth:
    secretRef:
      name: kryptic-machine-credentials

Consume it like any other Secret:

envFrom:
  - secretRef:
      name: backend-env

Checking status

kubectl get krypticsecrets
NAME              PROJECT             ENVIRONMENT   SECRET        KEYS   READY   AGE
backend-secrets   proj_a1b2c3d4e5f6   production    backend-env   3      True    2m

When READY is False, the Ready condition carries the reason:

kubectl get krypticsecret backend-secrets -o jsonpath='{.status.conditions[0]}'

Behavior you can rely on

SituationWhat happens
You delete the KrypticSecretThe Secret is garbage-collected with it (owner reference)
A key is deleted in KrypticIt disappears from the Secret on the next sync
The platform is unreachableThe Secret keeps its last known good values. A running workload is never emptied by an outage
Bad credentials or unknown projectNot Ready with ConfigurationError, backing off 10 minutes instead of hammering the API
A Secret with that name already existsThe operator refuses to overwrite it and reports why

Kubernetes Secrets are base64-encoded, not encrypted, unless you have enabled encryption at rest in etcd. The operator delivers your secrets into the cluster's own storage model - harden etcd accordingly.

Options

FieldDefaultNotes
spec.projectIdrequiredProject public id from kryptic.json
spec.environmentrequiredEnvironment slug
spec.secretNameresource nameTarget Kubernetes Secret
spec.refreshInterval5mValues below 30s are ignored
spec.auth.secretRefcluster env, if setPer-namespace credentials. Recommended in production.
spec.keysallRestrict which keys are synced
spec.template.typeOpaqueType of the produced Secret
spec.template.labels / .annotations-Merged onto the produced Secret

The operator watches every namespace by default. Set WATCH_NAMESPACE on the deployment to scope it to one.