Velero Security Testing: Backup Credential Exposure, Restore Privilege Escalation, and Backup Location Abuse

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.

Table of Contents

  1. BackupStorageLocation Credential Audit
  2. S3 Backup Bucket Access Testing
  3. Restore Privilege Escalation
  4. Volume Snapshot Credential Exposure
  5. Backup Encryption Gap Analysis
  6. Velero RBAC and Access Control Testing
  7. Malicious Backup Storage Location Injection
  8. Velero Security Hardening

BackupStorageLocation Credential Audit

# 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

S3 Backup Bucket Access Testing

# 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

Restore Privilege Escalation

# 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
"

Backup Encryption Gap Analysis

# 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

Velero Security Hardening

Velero Security Hardening Checklist:
Security TestMethodRisk
S3 backup bucket publicly readableaws s3 ls s3://BUCKET --no-sign-requestCritical
Backup not encrypted at restCheck BSL config for serverSideEncryptionCritical
Developer SA can trigger Velero restoreskubectl auth can-i create restores -n veleroHigh
BSL credential has over-broad S3 permissionsCheck IAM policy on cloud-credentials secretHigh
Backup contains cluster-admin RBAC bindingsInspect backup tar for clusterrolebindingsHigh
No bucket versioning (backup tampering possible)aws s3api get-bucket-versioningMedium

Automate Velero Security Testing

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