Pular para o conteúdo principal

Kubernetes — Operator e sidecar cv-agent

Tutorial passo a passo para injetar secrets do CipherVault em workloads Kubernetes de produção. Cobre os dois modos: Operator (gera Secrets nativos) e sidecar cv-agent (entrega via Unix socket sem rolling-restart).

Arquitetura

┌────────────────────────┐
│ api.ciphervault │
│ .com.br │
└───────────▲────────────┘
│ mTLS
┌──────────────┼──────────────┐
│ │ │
┌───────┴──────┐ ┌─────┴──────┐ ┌─────┴────────┐
│ Operator │ │ cv-agent │ │ cv-agent │
│ (cluster) │ │ (sidecar) │ │ (sidecar) │
└──────┬───────┘ └─────┬──────┘ └──────┬───────┘
│ K8s Secrets │ unix sock │ unix sock
┌──────┴───────┐ ┌─────┴──────┐ ┌──────┴───────┐
│ Pod app A │ │ Pod app B │ │ Pod app C │
└──────────────┘ └────────────┘ └──────────────┘

Pré-requisitos

  • Kubernetes 1.25+ com PSA restricted (recomendado)
  • Helm 3.10+
  • Tenant CipherVault com vault: producao criado
  • Permissão para criar AppConnections no tenant

1. Criar AppConnection para o cluster

curl -X POST https://api.ciphervault.com.br/v1/app-connections \
-H "Authorization: Bearer $CIPHERVAULT_TOKEN" \
-d '{
"name": "k8s-prod-saopaulo",
"vault": "producao",
"allowed_paths": ["api/*", "db/*"],
"ip_allowlist": ["203.0.113.0/24"],
"mtls_required": true,
"rotation": {
"client_secret_days": 60,
"certificate_days": 90
}
}'

Anote client_id e client_secret. O client_secret aparece uma vez.

2. Gerar e assinar certificado mTLS

# Em uma máquina segura (não no kubectl exec):
openssl req -new -newkey rsa:4096 -nodes \
-keyout cluster.key \
-out cluster.csr \
-subj "/CN=k8s-prod-saopaulo/O=Acme/C=BR"

curl -X POST https://api.ciphervault.com.br/v1/app-connections/app_.../sign-csr \
-H "X-Client-Id: cv_app_..." \
-H "X-Client-Secret: cs_live_..." \
--data-binary @cluster.csr \
-o cluster.crt

curl https://api.ciphervault.com.br/.well-known/ca-bundle.pem -o ca.pem

3. Criar secret do operator no cluster

kubectl create namespace ciphervault-system

kubectl create secret generic ciphervault-creds \
--namespace ciphervault-system \
--from-literal=client-id='cv_app_...' \
--from-literal=client-secret='cs_live_...' \
--from-file=tls.crt=cluster.crt \
--from-file=tls.key=cluster.key \
--from-file=ca.crt=ca.pem

4. Instalar o operator via Helm

helm repo add ciphervault https://charts.ciphervault.com.br
helm repo update

helm install ciphervault-operator ciphervault/operator \
--namespace ciphervault-system \
--version 1.6.0 \
--set ciphervault.endpoint=https://api.ciphervault.com.br \
--set ciphervault.appConnectionId=app_... \
--set ciphervault.credentialsSecret=ciphervault-creds \
--set metrics.enabled=true \
--set podSecurityContext.runAsNonRoot=true \
--set podSecurityContext.seccompProfile.type=RuntimeDefault

Verifique:

kubectl -n ciphervault-system get pods
kubectl -n ciphervault-system logs deployment/ciphervault-operator

5. Modo Operator: VaultSecret

# billing-vaultsecret.yaml
apiVersion: secrets.ciphervault.com.br/v1
kind: VaultSecret
metadata:
name: billing-api
namespace: billing
spec:
vault: producao
refreshInterval: 5m
rotationStrategy: rolling-restart
target:
type: Opaque
name: billing-api
data:
- key: STRIPE_KEY
path: api/stripe/secret_key
- key: DB_PASSWORD
path: db/postgres/billing-app
rolloutTargets:
- kind: Deployment
name: billing-api
kubectl apply -f billing-vaultsecret.yaml

# Verificar
kubectl -n billing get vaultsecrets
kubectl -n billing get secret billing-api -o yaml

Deployment usa o Secret como envFrom:

spec:
template:
spec:
containers:
- name: api
image: acme/billing-api:1.4.0
envFrom:
- secretRef:
name: billing-api

A cada rotação, o operator atualiza o Secret e adiciona annotation ciphervault.com.br/rotated-at no Deployment, disparando rolling update.

6. Modo sidecar: cv-agent

Para apps sensíveis a restart (websockets, jobs longos), use o sidecar:

apiVersion: apps/v1
kind: Deployment
metadata:
name: chat-api
namespace: chat
spec:
replicas: 3
template:
metadata:
annotations:
ciphervault.com.br/inject-agent: "true"
ciphervault.com.br/agent-paths: "api/openai/*,api/stream/*"
spec:
containers:
- name: api
image: acme/chat-api:2.1.0
env:
- name: CV_SOCKET
value: /run/cv/agent.sock
volumeMounts:
- name: cv-socket
mountPath: /run/cv

# Sidecar é injetado pelo webhook do operator
# quando a annotation `inject-agent: true` está presente.
volumes:
- name: cv-socket
emptyDir:
medium: Memory

A aplicação consulta via SDK:

from ciphervault import Client
from ciphervault.auth import UnixSocketAuth

client = Client(auth=UnixSocketAuth("/run/cv/agent.sock"))
secret = client.secrets.get(vault="producao", path="api/openai/key")
# Sempre versão atual; sidecar renova in-process.

7. NetworkPolicy

Restrinja egress do operator e dos sidecars apenas ao endpoint do CipherVault:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ciphervault-egress
namespace: ciphervault-system
spec:
podSelector: {}
policyTypes: ["Egress"]
egress:
- to:
- ipBlock:
cidr: 200.123.45.0/24 # IPs documentados em /.well-known/egress-ips
ports:
- protocol: TCP
port: 443
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53

8. Observabilidade

Adicione ServiceMonitor (Prometheus Operator):

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: ciphervault-operator
namespace: ciphervault-system
spec:
selector:
matchLabels:
app: ciphervault-operator
endpoints:
- port: metrics
interval: 30s

Dashboard Grafana oficial: ID 21472 (importar via grafana.com).

9. Validação end-to-end

# Trigger rotação manual no CipherVault
curl -X POST https://api.ciphervault.com.br/v1/vaults/producao/secrets/db/postgres/billing-app/rotate \
-H "Authorization: Bearer $CIPHERVAULT_TOKEN" \
-d '{"reason": "validação K8s"}'

# Aguardar até 5min (refreshInterval) e verificar:
kubectl -n billing get deployment billing-api -o yaml | grep rotated-at

# Confirmar pods novos
kubectl -n billing rollout status deployment/billing-api

Troubleshooting

SintomaDiagnóstico
VaultSecret status: Failedkubectl describe vaultsecret … — geralmente policy não permite secrets:read no path.
Operator com 403 IP_NOT_ALLOWEDCluster sai por NAT diferente do ip_allowlist da AppConnection. Atualize a AppConnection.
Sidecar não injetaConfirme webhook do operator: kubectl get mutatingwebhookconfigurations.
Cert mTLS expirandociphervault-cli app-connection rotate-cert --id app_... ou aguardar rotação automática.

Boas práticas

  • AppConnection por cluster. Não compartilhe entre clusters; isole blast radius.
  • PSA restricted. Tanto o operator quanto o sidecar são compatíveis.
  • NetworkPolicy obrigatória. Egress apenas para o CipherVault e DNS.
  • Operator HA. Em produção rode com replicaCount: 3 e leader-election.
  • Backup do tenant. Antes de mudanças grandes, faça ciphervault-cli backup --tenant ....