Backups contain cluster secrets — storing the credentials carelessly is like locking the door and leaving the key under the mat. This episode covers storing cloud credentials in a Secret in the velero namespace, never committing them to a repo, the IRSA (EKS) and Workload Identity (GKE) patterns, and RBAC restrictions for access to the velero namespace.

Since episode 3 you've been storing cloud credentials in a credentials-velero file and installing them as a Secret. That works — but for production, that alone isn't secure enough. Episode 13 covers the security layer teams often overlook: how credentials are stored, who can read them, and what Velero's service account can do in the cluster.
Think of a backup as the contents of a safe, and the cloud credentials as the safe's key. You've already stored the contents well (bucket, TTL, encryption later). But if the key is left on the table (a file committed to a repo) and anyone is allowed to open it (loose RBAC), the best safe in the world means nothing.
The first and most often violated rule: credential files must not go into a repository. Add them to .gitignore:
credentials-velero
*.pem
*.json
.env*The reason: backup Secrets contain Kubernetes Secrets (passwords, tokens). Anyone with bucket access can read all of them. The bucket credentials are the only gateway — and credentials leaked in a commit mean that gateway is open.
When you run velero install --secret-file ./credentials-velero, the CLI creates a Secret named cloud-credentials in the velero namespace. To update it without reinstalling:
kubectl create secret generic cloud-credentials \
--namespace velero \
--from-file=cloud=./credentials-velero \
--dry-run=client -o yaml | kubectl apply -f -
kubectl delete pod -n velero -l app.kubernetes.io/name=veleroRestart the server pod after the Secret changes so Velero reads the new credentials.
Never give full admin access to your cloud account. Create a Velero-specific IAM policy: put/get/list/delete access to the specific backup bucket, and permission to create snapshots in a specific region. Ideally restrict it to one bucket and one region.
A static access key stored in a Secret: valid for years, never auto-rotated, and can leak. The modern cloud-native pattern is workload identity: the pod gets its IAM role directly from the provider, with no stored key.
IRSA (IAM Roles for Service Accounts) connects a Kubernetes Service Account to an IAM role. Attach an annotation to the velero service account:
apiVersion: v1
kind: ServiceAccount
metadata:
name: velero
namespace: velero
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/velero-backupsThen install Velero without a secret file:
velero install \
--provider aws \
--plugins velero/velero-plugin-for-aws:v1.14.0 \
--bucket velero-backups \
--no-secret \
--backup-location-config region=us-east-1 \
--snapshot-location-config region=us-east-1The same pattern on GCP: give an IAM binding between a Google service account and a Kubernetes service account, then annotate:
gcloud iam service-accounts add-iam-policy-binding velero-sa@proj.iam.gserviceaccount.com \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:proj.svc.id.goog[velero/velero]"
kubectl annotate sa velero -n velero \
iam.gke.io/gcp-service-account=velero-sa@proj.iam.gserviceaccount.comThe advantages of this pattern: no keys at all, inherits IAM permissions automatically, and there's no Secret to steal.
Tip
If you use IRSA/Workload Identity, install with --no-secret and make sure each BSL/VSL is configured with the appropriate role/SA. The GCP plugin docs also mention a serviceAccount=<email> BSL config for multi-account cases.
Velero is installed with a broad ClusterRole — it needs to read every resource in the cluster for backup and write resources during restore. What's often missed: anyone who can access the velero namespace practically controls backups of the entire cluster — including deleting them.
Give regular users minimal access — read-only viewing of backup status:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: velero-viewer
namespace: velero
rules:
- apiGroups: ["velero.io"]
resources: ["backups", "restores", "schedules", "backupstoragelocations"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: velero-viewer
namespace: velero
subjects:
- kind: User
name: devops@example.com
roleRef:
kind: Role
name: velero-viewer
apiGroup: rbac.authorization.k8s.ioOperators allowed to run backups need a separate Role with the create verb on backups, schedules, restores — not a ClusterRole admin.
kubectl auth can-i get secrets -n velero --as devops@example.comKey takeaways:
kubectl create secret ... --dry-run=client | kubectl apply then restart the pod.--no-secret.velero-viewer).kubectl auth can-i.In episode 14 next, we secure the stored data: Backup Encryption & Object Storage Security — SSE/KMS on the object store, snapshot encryption, TLS to the S3 endpoint, and NetworkPolicies for Velero components.