Kubernetes

Use SikkerKey secrets in Kubernetes pods.

Approach

Store the machine identity as a Kubernetes Secret, mount it into your pods, and use either the CLI or an SDK to fetch secrets at runtime.

SikkerKey secrets are fetched on demand, not synced to Kubernetes Secrets. This means your secrets are never stored in etcd and are always up to date.

Setup

1. Bootstrap a Kubernetes machine

From the dashboard, go to Machines > + Validate and select the Kubernetes tab. Run the command:

curl -sSL https://api.sikkerkey.com/v1/bootstrap/YOUR_TOKEN/k8s | sh

This generates a keypair locally, registers the machine as k8s-{hostname}, and outputs a ready-to-use kubectl create secret command:

kubectl create secret generic sikkerkey-identity \
  --from-literal=vault-id=vault_abc123 \
  --from-literal=machine-id=9daae125-... \
  --from-literal=api-url=https://api.sikkerkey.com \
  --from-literal=machine-name=k8s-myhost \
  --from-literal=private-key="-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIHN...
-----END PRIVATE KEY-----"

2. Create the Kubernetes Secret

Copy and run the kubectl create secret command output by the bootstrap script in your cluster.

3. Approve and grant access

Approve the machine from the dashboard, add it to your project, and grant it access to the secrets your pods need.

4. Create a ConfigMap for the project

kubectl create configmap sikkerkey-config \
  --from-literal=project-id=proj_xyz789

Init Container Pattern

Use an init container to fetch secrets and write them to a shared volume. Your application reads from the volume.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      volumes:
        - name: secrets
          emptyDir:
            medium: Memory

      initContainers:
        - name: fetch-secrets
          image: node:20-slim
          command:
            - sh
            - -c
            - |
              npm install -g @sikkerkey/cli
              VAULT_DIR="$HOME/.sikkerkey/vaults/$VAULT_ID"
              mkdir -p "$VAULT_DIR"
              echo "$PRIVATE_KEY" > "$VAULT_DIR/private.pem"
              chmod 600 "$VAULT_DIR/private.pem"
              cat > "$VAULT_DIR/identity.json" <<EOF
              {"machineId":"$MACHINE_ID","machineName":"$MACHINE_NAME","vaultId":"$VAULT_ID","apiUrl":"$API_URL","privateKeyPath":"$VAULT_DIR/private.pem"}
              EOF
              sikkerkey connect "$VAULT_ID"
              sikkerkey unlock "$PROJECT_ID"
              sikkerkey export --format dotenv > /secrets/.env
          env:
            - name: VAULT_ID
              valueFrom:
                secretKeyRef:
                  name: sikkerkey-identity
                  key: vault-id
            - name: MACHINE_ID
              valueFrom:
                secretKeyRef:
                  name: sikkerkey-identity
                  key: machine-id
            - name: MACHINE_NAME
              valueFrom:
                secretKeyRef:
                  name: sikkerkey-identity
                  key: machine-name
            - name: API_URL
              valueFrom:
                secretKeyRef:
                  name: sikkerkey-identity
                  key: api-url
            - name: PRIVATE_KEY
              valueFrom:
                secretKeyRef:
                  name: sikkerkey-identity
                  key: private-key
            - name: PROJECT_ID
              valueFrom:
                configMapKeyRef:
                  name: sikkerkey-config
                  key: project-id
          volumeMounts:
            - name: secrets
              mountPath: /secrets

      containers:
        - name: app
          image: myapp:latest
          command: ["sh", "-c", "source /secrets/.env && exec node server.js"]
          volumeMounts:
            - name: secrets
              mountPath: /secrets
              readOnly: true

The secrets volume uses emptyDir with medium: Memory so secrets are never written to disk on the node.

Sidecar Pattern

For applications that need fresh secrets (e.g. secrets with automatic rotation), run the CLI as a sidecar that periodically refreshes:

containers:
  - name: app
    image: myapp:latest
    command: ["sh", "-c", "source /secrets/.env && exec node server.js"]
    volumeMounts:
      - name: secrets
        mountPath: /secrets
        readOnly: true

  - name: sikkerkey-sidecar
    image: node:20-slim
    command:
      - sh
      - -c
      - |
        npm install -g @sikkerkey/cli
        VAULT_DIR="$HOME/.sikkerkey/vaults/$VAULT_ID"
        mkdir -p "$VAULT_DIR"
        echo "$PRIVATE_KEY" > "$VAULT_DIR/private.pem"
        chmod 600 "$VAULT_DIR/private.pem"
        cat > "$VAULT_DIR/identity.json" <<EOF
        {"machineId":"$MACHINE_ID","machineName":"$MACHINE_NAME","vaultId":"$VAULT_ID","apiUrl":"$API_URL","privateKeyPath":"$VAULT_DIR/private.pem"}
        EOF
        sikkerkey connect "$VAULT_ID"
        sikkerkey unlock "$PROJECT_ID"
        while true; do
          sikkerkey export --format dotenv > /secrets/.env
          sleep 60
        done
    env:
      - name: VAULT_ID
        valueFrom:
          secretKeyRef:
            name: sikkerkey-identity
            key: vault-id
      - name: MACHINE_ID
        valueFrom:
          secretKeyRef:
            name: sikkerkey-identity
            key: machine-id
      - name: MACHINE_NAME
        valueFrom:
          secretKeyRef:
            name: sikkerkey-identity
            key: machine-name
      - name: API_URL
        valueFrom:
          secretKeyRef:
            name: sikkerkey-identity
            key: api-url
      - name: PRIVATE_KEY
        valueFrom:
          secretKeyRef:
            name: sikkerkey-identity
            key: private-key
      - name: PROJECT_ID
        valueFrom:
          configMapKeyRef:
            name: sikkerkey-config
            key: project-id
    volumeMounts:
      - name: secrets
        mountPath: /secrets

SDK in Application

If you prefer, use an SDK directly in your application. Pass the identity values as environment variables from the Kubernetes secret and assemble the identity at application startup:

import os, json

# Write identity from env vars at startup
vault_id = os.environ["VAULT_ID"]
vault_dir = os.path.expanduser(f"~/.sikkerkey/vaults/{vault_id}")
os.makedirs(vault_dir, exist_ok=True)

with open(f"{vault_dir}/private.pem", "w") as f:
    f.write(os.environ["PRIVATE_KEY"])
os.chmod(f"{vault_dir}/private.pem", 0o600)

with open(f"{vault_dir}/identity.json", "w") as f:
    json.dump({
        "machineId": os.environ["MACHINE_ID"],
        "machineName": os.environ["MACHINE_NAME"],
        "vaultId": vault_id,
        "apiUrl": os.environ["API_URL"],
        "privateKeyPath": f"{vault_dir}/private.pem",
    }, f)

# Now use the SDK
from sikkerkey import SikkerKey

sk = SikkerKey(vault_id)
db_password = sk.get_field("sk_db_prod", "password")

Security Notes

  • The Kubernetes Secret containing the private key should be restricted via RBAC to only the namespaces and service accounts that need it
  • Use emptyDir with medium: Memory for the secrets volume to avoid writing to node disk
  • Mount the identity as read-only
  • Each pod using the same machine identity appears as the same machine in audit logs. For per-pod attribution, bootstrap a separate machine per deployment
  • Secrets are fetched from SikkerKey on demand and never stored in etcd