Velero backs up and restores Kubernetes cluster resources and persistent volumes. Because backups contain complete cluster state — secrets, configmaps, RBAC configurations, and persistent volume data — a Velero compromise has uniquely severe consequences. Backup storage credentials grant read access to all backed-up secrets, the ability to create a BackupStorageLocation pointing to attacker-controlled storage enables malicious cluster restoration, and restoring a backup from an unverified source can re-introduce privileged workloads into a cluster that has since been hardened. This guide covers systematic Velero security assessment.
# Velero BackupStorageLocations reference credential secrets for cloud object storage
kubectl get backupstoragelocations --all-namespaces -o json | python3 -c "
import json,sys
bsls = json.load(sys.stdin)['items']
for b in bsls:
meta = b['metadata']
spec = b.get('spec',{})
cred = spec.get('credential',{})
config = spec.get('config',{})
print(f\"BSL: {meta['namespace']}/{meta['name']}\")
print(f\" Provider: {spec.get('provider','?')}\")
print(f\" Bucket: {config.get('bucket','?')} region: {config.get('region','?')}\")
print(f\" Credential: {cred.get('name','default (velero SA)')} key: {cred.get('key','?')}\")
if spec.get('accessMode','ReadWrite') == 'ReadWrite':
print(' Access mode: ReadWrite (can write new backups)')
"
# Extract BSL credential secret
kubectl get secret cloud-credentials -n velero -o json | python3 -c "
import json,sys,base64
s = json.load(sys.stdin)
for k,v in s.get('data',{}).items():
decoded = base64.b64decode(v).decode()
print(f'=== {k} ===')
print(decoded)
"
# cloud = AWS credentials file format:
# [default]
# aws_access_key_id=AKIA...
# aws_secret_access_key=...
# With these credentials: s3:GetObject on entire backup bucket = read all backed-up secrets
# The S3 backup bucket contains ALL cluster state including Kubernetes secrets
# Test bucket access controls and encryption
# Check if backup bucket is publicly accessible
BUCKET=$(kubectl get backupstoragelocations -n velero -o json | python3 -c "
import json,sys
b = json.load(sys.stdin)['items'][0]
print(b['spec']['config'].get('bucket',''))
" 2>/dev/null)
# Test public read access
aws s3 ls s3://$BUCKET --no-sign-request 2>/dev/null && echo "[CRITICAL] Bucket is PUBLICLY READABLE"
# Check bucket encryption
aws s3api get-bucket-encryption --bucket $BUCKET 2>/dev/null || echo "No default encryption"
# Test bucket versioning (helps detect backup tampering)
aws s3api get-bucket-versioning --bucket $BUCKET
# Examine backup contents using credential
aws s3 ls s3://$BUCKET/velero/backups/ | head -10
# Download a backup and extract secrets
BACKUP_NAME=$(aws s3 ls s3://$BUCKET/velero/backups/ | awk '{print $NF}' | head -1 | tr -d '/')
aws s3 cp s3://$BUCKET/velero/backups/$BACKUP_NAME/$BACKUP_NAME.tar.gz /tmp/backup.tar.gz
tar -xzf /tmp/backup.tar.gz -C /tmp/backup/
# Find Kubernetes Secret objects in backup
find /tmp/backup/ -name "*.json" -exec grep -l '"kind":"Secret"' {} \; | head -5
# cat /tmp/backup/resources/secrets/namespaces/production/*.json | python3 -c "
# import json,sys,base64
# for line in sys.stdin: ...
# " — reveals all production secrets from backup
# Velero restore applies backed-up Kubernetes objects to the cluster
# If a backup contains privileged pods, DaemonSets with hostPath mounts, or
# elevated ClusterRoleBindings, restoring it re-introduces those security issues
# Check what a backup contains before restoring
velero backup describe BACKUP_NAME --details 2>/dev/null | grep -E "Resource|Kind" | head -20
# Test: can a developer trigger a restore?
kubectl auth can-i create restores \
--as=system:serviceaccount:developer:developer-sa \
-n velero
# Test: restore into a different namespace (namespace mapping)
kubectl apply -f - </dev/null | \
python3 -c "
import json,sys
for line in sys.stdin:
try:
obj = json.loads(line)
if obj.get('roleRef',{}).get('name') == 'cluster-admin':
subjects = obj.get('subjects',[])
print(f'cluster-admin binding: {subjects}')
except: pass
"
# Velero does not encrypt backup content by default
# Backups contain raw Kubernetes secrets (base64-encoded, not encrypted)
# Check if Velero BSP plugin encrypts backups (e.g., velero-plugin-for-aws with SSE)
kubectl get backupstoragelocations -n velero -o json | python3 -c "
import json,sys
bsls = json.load(sys.stdin)['items']
for b in bsls:
config = b.get('spec',{}).get('config',{})
sse = config.get('serverSideEncryption','')
kms_key = config.get('kmsKeyId','')
print(f\"BSL: {b['metadata']['name']}\")
print(f\" SSE: {sse if sse else 'NONE (backups unencrypted at rest)'}\")
print(f\" KMS key: {kms_key if kms_key else 'none'}\")
"
# Check VolumeSnapshotLocation encryption
kubectl get volumesnapshotlocations -n velero -o json | python3 -c "
import json,sys
vsls = json.load(sys.stdin)['items']
for v in vsls:
config = v.get('spec',{}).get('config',{})
print(f\"VSL: {v['metadata']['name']}\")
print(f\" Encrypted: {config.get('encrypted','false')}\")
"
# Verify a backup does NOT contain plaintext secrets (check for base64 in backup)
aws s3 cp s3://$BUCKET/velero/backups/$BACKUP_NAME/$BACKUP_NAME.tar.gz - 2>/dev/null | \
tar -xzO --wildcards "*/namespaces/production/secrets*" 2>/dev/null | \
python3 -m json.tool | grep -c '"data"'
# Any count > 0 means unencrypted secret data is in the backup
serverSideEncryption: aws:kmsrestore create RBAC to cluster admins — not developers or application SAsaccessMode: ReadOnly for disaster-recovery read replicas| Security Test | Method | Risk |
|---|---|---|
| S3 backup bucket publicly readable | aws s3 ls s3://BUCKET --no-sign-request | Critical |
| Backup not encrypted at rest | Check BSL config for serverSideEncryption | Critical |
| Developer SA can trigger Velero restores | kubectl auth can-i create restores -n velero | High |
| BSL credential has over-broad S3 permissions | Check IAM policy on cloud-credentials secret | High |
| Backup contains cluster-admin RBAC bindings | Inspect backup tar for clusterrolebindings | High |
| No bucket versioning (backup tampering possible) | aws s3api get-bucket-versioning | Medium |
Ironimo tests Velero deployments for backup storage credential exposure, S3 bucket public access and encryption gaps, restore privilege escalation paths, volume snapshot credential theft, and malicious BackupStorageLocation injection — ensuring your disaster recovery tooling doesn't become a disaster itself.
Start free scan