Automate Secret Rotation with Python
Automate secret rotation with Python scripts — Kubernetes for Python Developers.
Focus: automate secret rotation with python scripts
Let’s face it: manually rotating secrets in Kubernetes is a ticking time bomb. You log into the cluster, edit a Secret YAML, pray you didn’t miss a pod, and then hope the application picks up the new value on the next restart. It’s error-prone, slow, and frankly one of the last things you want to do at 2 AM during an incident. In this lesson, you’ll learn how to automate secret rotation with Python scripts — turning that fragile, manual process into a repeatable, verifiable pipeline that runs in seconds, not hours.
The problem this lesson solves
Secrets leak. Credentials expire. Rotation is not a nice-to-have — it’s a security requirement. But doing it by hand in Kubernetes is painful:
- You have to update multiple places — the Secret object, the Deployment, and sometimes the application’s config.
- Restart timing is unpredictable — even after you
kubectl apply, pods keep running with the old secret until they’re restarted. - Errors are silent — a typo in the Secret data is only discovered when your app starts failing authentication.
- Audit trails are thin — who rotated what, when, and why? Manual changes rarely answer that.
These are exactly the problems automation solves. A Python script gives you a single, idempotent path to rotate secrets — with validation, logging, and rollback — that you can run locally, in CI, or as a CronJob inside the cluster.
Core concept / mental model
Think of secret rotation like changing the locks on your house. You wouldn’t just swap the deadbolt without checking every door and window — you’d plan the sequence, verify each lock works, and keep a spare key in case something goes wrong. Your Python script is that lock-changing plan: it knows what to change, when to change it, and how to verify the change took effect.
In Kubernetes terms, the mental model is three layers:
- The Secret object — the source of truth for credentials.
- The workload (Deployment, StatefulSet) — the consumer of the secret.
- The runtime state — the pods that actually hold the old value in memory.
Your script must update layer 1, force layer 3 to refresh (usually by restarting pods), and confirm layer 2 is still happy — all while keeping the system available.
How it works step by step
Here is the logical sequence a robust rotation script follows, regardless of the secret type (API key, DB password, TLS cert):
- Fetch the current secret — read the existing data to know what you’re replacing and to keep metadata like labels.
- Generate or retrieve the new value — from a vault, a random generator, or an external API.
- Create the updated Secret — either by patching the existing object or creating a new one with the same name. Patching is usually safer because it preserves RBAC and references.
- Restart the affected workloads — use
kubectl rollout restartor the Kubernetes API to trigger a rolling update. - Wait for rollout to complete — poll the Deployment status until all replicas are updated and ready.
- Verify the new secret works — run a health check or test endpoint that uses the secret, if available.
- Log and clean up — record the rotation time, the old value’s fingerprint, and optionally delete the previous Secret version if you used a versioned naming scheme.
Pro tip: Always generate secrets with enough entropy.
secrets.token_urlsafe(32)gives you 256 bits of randomness — far better than a hardcoded string.
Hands-on walkthrough
Let’s get practical. We’ll write two Python scripts: one that rotates a password for a Deployment, and another that does the same but as an in-cluster CronJob.
Prerequisites
Make sure you have:
- Python 3.10+
kubectlconfigured to your cluster (minikube or kind for testing)- The
kubernetesandsecretspackages installed:
pip install kubernetes secrets
Example 1: Rotate a password and restart a Deployment
import secrets
from kubernetes import client, config
from kubernetes.client.rest import ApiException
import time
# Load kubeconfig (from ~/.kube/config)
config.load_kubeconfig()
v1 = client.CoreV1Api()
apps_v1 = client.AppsV1Api()
NAMESPACE = "default"
SECRET_NAME = "db-password"
DEPLOYMENT_NAME = "my-api"
# 1. Fetch the current secret to preserve labels/annotations
try:
current_secret = v1.read_namespaced_secret(SECRET_NAME, NAMESPACE)
print(f"Current secret data keys: {list(current_secret.data.keys())}")
except ApiException as e:
if e.status == 404:
print("Secret not found, creating a fresh one.")
else:
raise
# 2. Generate a new password
new_password = secrets.token_urlsafe(24)
print(f"Generated new password (first 8 chars): {new_password[:8]}...")
# 3. Build the updated Secret body
secret_patch = {
"data": {
"password": v1.api_client.sanitize_for_serialization(new_password)
}
}
# 4. Apply the patch (for existing secret) or create if missing
try:
v1.patch_namespaced_secret(SECRET_NAME, NAMESPACE, secret_patch)
print("Secret updated.")
except ApiException:
# If it doesn't exist, create it
secret_manifest = {
"apiVersion": "v1",
"kind": "Secret",
"metadata": {"name": SECRET_NAME, "namespace": NAMESPACE},
"data": {"password": v1.api_client.sanitize_for_serialization(new_password)}
}
v1.create_namespaced_secret(NAMESPACE, secret_manifest)
print("Secret created.")
# 5. Restart the deployment to pick up the new secret
body = {"spec": {"template": {"metadata": {"annotations": {"kubectl.kubernetes.io/restartedAt": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())}}}}}
apps_v1.patch_namespaced_deployment(DEPLOYMENT_NAME, NAMESPACE, body)
print(f"Deployment {DEPLOYMENT_NAME} restarted.")
# 6. Wait for rollout to complete
while True:
deployment = apps_v1.read_namespaced_deployment(DEPLOYMENT_NAME, NAMESPACE)
if deployment.status.ready_replicas == deployment.spec.replicas:
print("Rollout complete!")
break
time.sleep(2)
print("Process finished. Verify app health.")
Expected output (abridged):
Current secret data keys: ['password']
Generated new password (first 8 chars): Tc8zY...
Secret updated.
Deployment my-api restarted.
Rollout complete!
Process finished. Verify app health.
Example 2: In-cluster CronJob for scheduled rotation
To run rotation automatically every night, deploy a CronJob that runs a Python image with the same logic. Here’s a quick script that uses in-cluster config (no kubeconfig needed):
# rotate_secret_job.py
import secrets
from kubernetes import client, config
import os
config.load_incluster_config() # Works when running inside a pod
v1 = client.CoreV1Api()
apps_v1 = client.AppsV1Api()
namespace = os.getenv("POD_NAMESPACE", "default")
secret_name = os.getenv("SECRET_NAME", "api-key")
deployment_name = os.getenv("DEPLOYMENT_NAME", "backend")
new_key = secrets.token_hex(16)
# Patch the secret
secret_patch = {"data": {"key": v1.api_client.sanitize_for_serialization(new_key)}}
v1.patch_namespaced_secret(secret_name, namespace, secret_patch)
# Restart the deployment
body = {"spec": {"template": {"metadata": {"annotations": {"rotatedAt": secrets.token_hex(4)}}}}}
apps_v1.patch_namespaced_deployment(deployment_name, namespace, body)
print("Secret rotated and deployment restarted.")
Then wrap it in a CronJob manifest:
apiVersion: batch/v1
kind: CronJob
metadata:
name: secret-rotator
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
serviceAccountName: secret-rotator-sa
containers:
- name: rotator
image: python:3.12
command: ["python", "-c", "exec(open('/scripts/rotate_secret_job.py').read())"]
volumeMounts:
- name: script
mountPath: /scripts
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
restartPolicy: OnFailure
volumes:
- name: script
configMap:
name: rotation-script
Make sure the ServiceAccount has RBAC permissions to patch secrets and deployments.
Compare options / when to choose what
| Option | Pros | Cons | Best for |
|---|---|---|---|
| Vanilla Python + kubectl | Simple, uses existing tools, easy to debug | Slower, requires kubeconfig context, not API-native | Quick one-off scripts, local testing |
| Python Kubernetes client | Direct API access, robust error handling, configurable | More code, needs Python packages | Production automation, complex logic |
| Helm + hooks | Native to Kubernetes, versioned releases | Limited logic, not Python-friendly | Managing secrets as part of chart upgrades |
| External secrets operators (e.g., External Secrets, Sealed Secrets) | Centralized secret management, auto-sync | Adds cluster components, learning curve | Enterprises with Vault or AWS Secrets Manager |
For a Python-driven approach, the official Kubernetes client is the sweet spot — it gives you fine-grained control without the overhead of writing raw HTTP calls.
Troubleshooting & edge cases
Here are the most common pitfalls and how to fix them:
- Pods don’t restart after Secret update — Kubernetes only restarts pods if the Secret volume is marked as changed or you explicitly trigger a rollout. Always patch the Deployment template (e.g., add a timestamp annotation) after updating the Secret.
- Secret data is base64-encoded —
v1_api_client.sanitize_for_serialization()doesn’t encode it for you. You must base64-encode the value before putting it indata, or usestringDatafor plain text. - In-cluster config fails — If you’re not running inside a pod,
load_incluster_config()throws an exception. Fall back toload_kubeconfig()or handle the exception. - RBAC permission denied — Even with the client, your ServiceAccount needs
get,patch, andliston secrets and deployments. Use a dedicated Role and RoleBinding. - Long-running secrets in environment variables — If your app uses
envinstead of mounted volumes, a Secret update won’t be picked up without a full pod recreation. Force a rolling update or switch to mounted Secrets.
Pro tip: Always test the rotation script in a non-production namespace first. Use a small script like this to verify your logic before touching real secrets.
What you learned & what's next
Congratulations! You now know how to automate secret rotation with Python scripts in Kubernetes. You’ve learned:
- The pain points of manual rotation and why automation is essential.
- A mental model of Secret → Deployment → Pod lifecycle.
- How to write a Python script using the Kubernetes client to update a Secret and restart a workload.
- How to run the same logic as an in-cluster CronJob.
- How to compare Python client vs. other options and troubleshoot common issues.
From here, you’re ready to extend this pattern to managing ConfigMaps or building a full CI/CD pipeline for secret rollout. Next in the track, we’ll dive into Managing Kubernetes Ingress with Python — but if you want to go deeper, explore adding health checks and rollback logic to your rotation script.
Keep your secrets fresh, and your deployments healthy!
Practice recap
Try extending Example 1 to add a health check after rotation: write a script that curls your application’s /health endpoint and only consider the rotation successful if it returns 200. If it fails, roll back the Secret to the previous value and restart the Deployment again. This will make your automation production-ready.
Common mistakes
- Forgetting to base64-encode secret values when writing to the Kubernetes API — always use
base64.b64encode()or rely onstringData. - Assuming pods automatically pick up updated Secrets — they don’t for environment variables, and mounted volumes only update after a rollout.
- Running
load_incluster_config()outside a pod without a try/except, causing the script to crash. - Not granting the right RBAC permissions to the ServiceAccount running the CronJob, leading to 403 Forbidden errors.
- Using a weak random generator like
random.choice()for secrets — usesecrets.token_urlsafe()ortoken_hex().
Variations
- Use
kubectl apply -f -from within a Python subprocess instead of the Kubernetes client for a lightweight approach. - Implement a versioned secret naming scheme (e.g.,
db-password-v2) and update Deployments to reference the new name for zero-downtime rotation. - Leverage an external secrets operator like External Secrets or Sealed Secrets to sync secrets from a central vault, reducing the need for custom scripts.
Real-world use cases
- Rotating database credentials nightly for a Django app running on Kubernetes, reducing the risk of a leaked password being replayed.
- Automating TLS certificate renewal for a Python microservices mesh by updating a Secret and triggering rolling restarts before expiry.
- Rotating API keys for multiple third-party integrations across a fleet of Deployments with a single Python script scheduled as a CronJob.
Key takeaways
- Manual secret rotation is error-prone and slow; automation with Python brings repeatability, speed, and auditability.
- A rotation script must update the Secret, restart the workload, and verify rollout completion to be effective.
- Python’s
secretsmodule provides cryptographically strong random values ideal for passwords and keys. - The Kubernetes Python client gives you full control, while
kubectlsubprocesses are simpler but less robust. - Always handle RBAC permissions and base64 encoding issues — they are the most common causes of failed automation.
- In-cluster CronJobs are a natural fit for scheduled rotation without external orchestrators.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.