Files
managedsecret-operator-public/examples/example-2-registry-rotation-simple.yaml
admin 1bc8aadb85 Initial public release v1.0.7
- Streamlined README focused on quick start
- Complete examples for all major use cases
- Decision tree for choosing right pattern
- Comprehensive troubleshooting guide
2025-10-26 14:53:01 +01:00

287 lines
7.5 KiB
YAML

# Example 2: Container Registry with Rotation + Reloader
#
# Use case: Docker registry with basic auth that rotates every 90 days
#
# Characteristics:
# - Password rotates every 90 days
# - Registry doesn't need old password to accept new one
# - InitContainer regenerates htpasswd on pod restart
# - Reloader automatically restarts pod when secret changes
# - No keepPreviousVersion needed
---
apiVersion: v1
kind: Namespace
metadata:
name: registry
---
# ManagedSecret generates and rotates registry credentials
apiVersion: secrets.c5ai.ch/v1alpha1
kind: ManagedSecret
metadata:
name: registry-auth
namespace: registry
spec:
vault:
address: "http://openbao.openbao.svc.cluster.local:8200"
authMethod: kubernetes
role: managedsecret-operator
kvVersion: v2
mount: secret
path: registry/auth
fields:
- name: username
type: static
value: "admin"
- name: password
type: generated
generator:
type: password
length: 32
minDigits: 5
minSymbols: 5
minLowercase: 5
minUppercase: 5
symbolCharacters: "!@#$%^&*"
allowRepeat: false
destination:
name: registry-credentials
type: Opaque
# No need for previous version - htpasswd is regenerated fresh
rotation:
enabled: true
schedule: 2160h # 90 days
rotateGeneratedOnly: true # Only rotate password, keep username
---
# PersistentVolumeClaim for registry storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: registry-pvc
namespace: registry
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: longhorn
---
# Registry Deployment with Reloader integration
apiVersion: apps/v1
kind: Deployment
metadata:
name: registry
namespace: registry
labels:
app: registry
spec:
replicas: 1
selector:
matchLabels:
app: registry
template:
metadata:
labels:
app: registry
annotations:
# Reloader watches registry-credentials and restarts pod on change
reloader.stakater.com/auto: "true"
spec:
securityContext:
runAsNonRoot: true
runAsUser: 65534
fsGroup: 65534
seccompProfile:
type: RuntimeDefault
initContainers:
# Generate htpasswd file from current credentials at pod startup
- name: generate-htpasswd
image: httpd:2.4-alpine
command:
- sh
- -c
- |
USERNAME=$(cat /credentials/username)
PASSWORD=$(cat /credentials/password)
htpasswd -Bbn "$USERNAME" "$PASSWORD" > /auth/htpasswd
chmod 644 /auth/htpasswd
echo "htpasswd generated with user: $USERNAME"
volumeMounts:
- name: credentials
mountPath: /credentials
readOnly: true
- name: auth
mountPath: /auth
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 65534
capabilities:
drop:
- ALL
containers:
- name: registry
image: registry:2
ports:
- containerPort: 5000
name: http
env:
- name: REGISTRY_STORAGE_DELETE_ENABLED
value: "true"
- name: REGISTRY_HTTP_ADDR
value: "0.0.0.0:5000"
# Enable HTTP basic auth
- name: REGISTRY_AUTH
value: "htpasswd"
- name: REGISTRY_AUTH_HTPASSWD_REALM
value: "Registry Realm"
- name: REGISTRY_AUTH_HTPASSWD_PATH
value: "/auth/htpasswd"
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 65534
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: registry-storage
mountPath: /var/lib/registry
- name: auth
mountPath: /auth
readOnly: true
livenessProbe:
httpGet:
path: /
port: 5000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 5000
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: registry-storage
persistentVolumeClaim:
claimName: registry-pvc
- name: credentials
secret:
secretName: registry-credentials
- name: auth
emptyDir: {}
---
# Service for internal access
apiVersion: v1
kind: Service
metadata:
name: registry
namespace: registry
spec:
type: ClusterIP
selector:
app: registry
ports:
- port: 5000
targetPort: 5000
protocol: TCP
name: http
---
# Ingress for external access with TLS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: registry
namespace: registry
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
spec:
ingressClassName: nginx
tls:
- hosts:
- registry.c5ai.ch
secretName: registry-ingress-tls
rules:
- host: registry.c5ai.ch
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: registry
port:
number: 5000
---
# How the rotation flow works:
#
# 1. After 90 days, ManagedSecret operator:
# - Generates new password
# - Updates Vault
# - Updates registry-credentials Secret
#
# 2. Reloader detects Secret change:
# - Sees resourceVersion changed
# - Triggers rolling restart of registry Deployment
#
# 3. Pod restart with new credentials:
# - InitContainer runs
# - Reads NEW password from registry-credentials
# - Generates fresh htpasswd file
# - Registry container starts with new htpasswd
#
# 4. Result:
# - Old clients with old password: FAIL (401 Unauthorized)
# - New clients with new password: SUCCESS
# - Downtime: ~1-2 minutes during rolling restart
---
# Testing commands:
#
# # Get current credentials
# kubectl get secret registry-credentials -n registry -o jsonpath='{.data.username}' | base64 -d
# kubectl get secret registry-credentials -n registry -o jsonpath='{.data.password}' | base64 -d
#
# # Test without auth (should fail)
# curl -i https://registry.c5ai.ch/v2/
#
# # Test with auth (should succeed)
# REGISTRY_USER=$(kubectl get secret registry-credentials -n registry -o jsonpath='{.data.username}' | base64 -d)
# REGISTRY_PASS=$(kubectl get secret registry-credentials -n registry -o jsonpath='{.data.password}' | base64 -d)
# curl -u $REGISTRY_USER:$REGISTRY_PASS https://registry.c5ai.ch/v2/_catalog
#
# # Docker login
# docker login registry.c5ai.ch -u $REGISTRY_USER -p $REGISTRY_PASS
#
# # Force rotation test
# kubectl annotate managedsecret registry-auth -n registry reconcile="$(date +%s)" --overwrite
# kubectl get pods -n registry -w # Watch pod restart