npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
Overview
Service accounts are non-human identities used by applications, daemons, CI/CD pipelines, and automated processes to authenticate to systems and APIs. These accounts often have elevated privileges and their credentials (passwords, API keys, certificates, tokens) are frequently long-lived and shared across teams, making them prime targets for attackers. Credential rotation is the systematic process of replacing these secrets on a scheduled basis, propagating new credentials to all dependent systems, and verifying service continuity after rotation.
When to Use
- When conducting security assessments that involve performing service account credential rotation
- When following incident response procedures for related security events
- When performing scheduled security testing or auditing activities
- When validating security controls through hands-on testing
Prerequisites
- Inventory of all service accounts across AD, cloud, and applications
- Secrets management platform (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, or CyberArk)
- Service dependency mapping (which services use which credentials)
- Change management process for rotation windows
- Monitoring for service health post-rotation
Core Concepts
Service Account Types
| Type | Platform | Credential | Rotation Method |
|---|---|---|---|
| Active Directory Service Account | Windows/AD | Password | gMSA (automatic) or PAM-managed |
| AWS IAM User | AWS | Access Key/Secret Key | AWS Secrets Manager rotation Lambda |
| GCP Service Account | GCP | JSON key file | Key rotation via IAM API |
| Azure Service Principal | Azure | Client secret/certificate | Key Vault + rotation policy |
| Database Service Account | SQL/Oracle/Postgres | Password | Vault dynamic secrets |
| API Key | SaaS applications | API token | Application-specific API |
Group Managed Service Accounts (gMSA)
Windows gMSAs provide automatic password management by Active Directory:
- AD automatically rotates the password every 30 days
- Password is 240 bytes, cryptographically random
- Multiple servers can use the same gMSA simultaneously
- No administrator knows or manages the password
- Eliminates manual rotation for Windows services
Rotation Architecture
Secrets Manager / Vault
│
├── Rotation Trigger (schedule or on-demand)
│
├── Generate new credential
│
├── Update credential at source (AD, cloud IAM, database)
│
├── Update credential in all consumers:
│ ├── Application configuration
│ ├── CI/CD pipeline secrets
│ ├── Kubernetes secrets
│ └── Other dependent services
│
├── Verify service health
│ ├── Health check endpoints
│ ├── Authentication test
│ └── Functional smoke test
│
└── Revoke old credential (after grace period)Workflow
Step 1: Discover and Inventory Service Accounts
Enumerate all service accounts and their dependencies:
# Active Directory: Find all service accounts
Get-ADServiceAccount -Filter * -Properties *
Get-ADUser -Filter {ServicePrincipalName -ne "$null"} -Properties ServicePrincipalName,PasswordLastSet,LastLogonDate
# Find accounts with passwords older than 90 days
$threshold = (Get-Date).AddDays(-90)
Get-ADUser -Filter {PasswordLastSet -lt $threshold -and Enabled -eq $true} -Properties PasswordLastSet,ServicePrincipalName |
Where-Object {$_.ServicePrincipalName} |
Select-Object Name, PasswordLastSet, ServicePrincipalNameStep 2: Implement gMSA for Windows Services
# Create KDS Root Key (one-time, domain-wide)
Add-KdsRootKey -EffectiveImmediately
# Create the gMSA account
New-ADServiceAccount -Name "svc-webapp-gmsa" `
-DNSHostName "svc-webapp-gmsa.corp.example.com" `
-PrincipalsAllowedToRetrieveManagedPassword "WebServerGroup" `
-KerberosEncryptionType AES128,AES256
# Install on target server
Install-ADServiceAccount -Identity "svc-webapp-gmsa"
# Test the account
Test-ADServiceAccount -Identity "svc-webapp-gmsa"
# Configure IIS Application Pool to use gMSA
# Set identity to: CORP\svc-webapp-gmsa$Step 3: AWS Access Key Rotation with Secrets Manager
import boto3
import json
def rotate_iam_access_key(secret_arn, iam_username):
"""Rotate an IAM user's access key via Secrets Manager."""
iam = boto3.client("iam")
sm = boto3.client("secretsmanager")
# Create new access key
new_key = iam.create_access_key(UserName=iam_username)
new_access_key = new_key["AccessKey"]["AccessKeyId"]
new_secret_key = new_key["AccessKey"]["SecretAccessKey"]
# Store new credentials in Secrets Manager
sm.put_secret_value(
SecretId=secret_arn,
SecretString=json.dumps({
"accessKeyId": new_access_key,
"secretAccessKey": new_secret_key,
"username": iam_username,
})
)
# List old access keys and deactivate them
keys = iam.list_access_keys(UserName=iam_username)
for key in keys["AccessKeyMetadata"]:
if key["AccessKeyId"] != new_access_key and key["Status"] == "Active":
iam.update_access_key(
UserName=iam_username,
AccessKeyId=key["AccessKeyId"],
Status="Inactive"
)
return {"new_key_id": new_access_key, "old_keys_deactivated": True}Step 4: Database Credential Rotation with Vault
import hvac
def configure_vault_database_rotation(vault_url, vault_token, db_config):
"""Configure HashiCorp Vault for automatic database credential rotation."""
client = hvac.Client(url=vault_url, token=vault_token)
# Enable database secrets engine
client.sys.enable_secrets_engine(
backend_type="database",
path="database"
)
# Configure database connection
client.secrets.database.configure(
name=db_config["name"],
plugin_name="postgresql-database-plugin",
connection_url=f"postgresql://{{{{username}}}}:{{{{password}}}}@"
f"{db_config['host']}:{db_config['port']}/{db_config['database']}",
allowed_roles=[db_config["role_name"]],
username=db_config["admin_user"],
password=db_config["admin_password"],
)
# Create a role for dynamic credentials
client.secrets.database.create_role(
name=db_config["role_name"],
db_name=db_config["name"],
creation_statements=[
"CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';",
f"GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{{{name}}}}\";"
],
default_ttl="1h",
max_ttl="24h",
)
return {"status": "configured", "role": db_config["role_name"]}Step 5: Post-Rotation Verification
After every rotation, verify service continuity:
import requests
import time
def verify_service_health(service_endpoints, max_retries=3, delay=10):
"""Check that services are healthy after credential rotation."""
results = []
for endpoint in service_endpoints:
for attempt in range(max_retries):
try:
response = requests.get(
endpoint["health_url"],
timeout=10,
headers=endpoint.get("headers", {})
)
healthy = response.status_code == 200
results.append({
"service": endpoint["name"],
"status": "healthy" if healthy else f"unhealthy ({response.status_code})",
"attempt": attempt + 1,
})
if healthy:
break
except requests.RequestException as e:
results.append({
"service": endpoint["name"],
"status": f"error: {str(e)}",
"attempt": attempt + 1,
})
if attempt < max_retries - 1:
time.sleep(delay)
return resultsValidation Checklist
- Complete inventory of service accounts with dependency mapping
- gMSA implemented for all eligible Windows service accounts
- Cloud access keys rotated via secrets manager (AWS, GCP, Azure)
- Database credentials managed via dynamic secrets (Vault) or rotation policy
- Rotation schedule defined (30-90 days depending on risk level)
- Post-rotation health checks automated
- Alerting configured for rotation failures
- Old credentials revoked after grace period
- Rotation events logged and auditable
- Rollback procedure documented and tested
References
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md2.3 KB
API Reference: Service Account Credential Rotation
AWS IAM CLI Key Rotation
| Command | Description |
|---|---|
aws iam create-access-key --user-name <user> |
Create new access key |
aws iam list-access-keys --user-name <user> |
List existing keys |
aws iam update-access-key --access-key-id <id> --status Inactive |
Deactivate old key |
aws iam delete-access-key --access-key-id <id> --user-name <user> |
Delete old key |
Azure AD CLI Credential Rotation
| Command | Description |
|---|---|
az ad app credential reset --id <app-id> --years 1 |
Generate new client secret |
az ad app credential list --id <app-id> |
List current credentials |
az ad app credential delete --id <app-id> --key-id <key-id> |
Remove old credential |
HashiCorp Vault Database Secrets Engine
| Endpoint | Method | Description |
|---|---|---|
/v1/database/creds/{role} |
GET | Generate dynamic database credentials |
/v1/database/config/{name} |
POST | Configure database connection |
/v1/database/roles/{name} |
POST | Create dynamic credential role |
/v1/sys/leases/revoke |
PUT | Revoke a dynamic credential lease |
Vault Request Headers
| Header | Value |
|---|---|
X-Vault-Token |
Vault authentication token |
Content-Type |
application/json |
GCP Service Account Key Rotation
| Command | Description |
|---|---|
gcloud iam service-accounts keys create |
Create new key |
gcloud iam service-accounts keys list --iam-account <sa> |
List keys |
gcloud iam service-accounts keys delete <key-id> |
Delete old key |
Python Libraries
| Library | Version | Purpose |
|---|---|---|
subprocess |
stdlib | Execute AWS/Azure/GCP CLI commands |
requests |
>=2.28 | HashiCorp Vault HTTP API calls |
hvac |
>=2.1 | HashiCorp Vault Python client |
boto3 |
>=1.26 | AWS IAM programmatic key rotation |
References
- AWS Secrets Manager Rotation: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets.html
- HashiCorp Vault Database Engine: https://developer.hashicorp.com/vault/docs/secrets/databases
- Azure Key Vault Rotation: https://learn.microsoft.com/en-us/azure/key-vault/secrets/tutorial-rotation
- GCP Key Rotation: https://cloud.google.com/iam/docs/key-rotation
standards.md1.9 KB
Service Account Credential Rotation - Standards Reference
Compliance Requirements
NIST SP 800-63B - Digital Identity Guidelines
- Authenticator lifecycle management
- Credential rotation for compromised or expired secrets
- Automated mechanisms for credential management preferred
PCI DSS v4.0
- 8.3.9: Passwords/passphrases for application and system accounts changed periodically (at least every 90 days)
- 8.6.1: Interactive use of system and application accounts managed
- 8.6.3: Passwords for application and system accounts protected against misuse
SOC 2 - CC6.1
- Logical access controls restrict access to information assets
- Credentials are managed throughout their lifecycle
- Service account credentials rotated per policy
CIS Controls v8
- 5.2: Use unique passwords
- 5.4: Restrict admin privileges to dedicated accounts
- 5.5: Establish and maintain inventory of service accounts
Rotation Frequency Standards
| Account Type | NIST Guideline | PCI DSS | CIS Benchmark | Recommended |
|---|---|---|---|---|
| Domain Admin | 60 days | 90 days | 90 days | 30 days |
| Service Account (AD) | gMSA auto | 90 days | 90 days | gMSA (auto 30 days) |
| Cloud Access Keys | 90 days | 90 days | 90 days | 90 days or eliminate |
| Database Admin | 60 days | 90 days | 90 days | Dynamic secrets (per-session) |
| API Keys | 90 days | N/A | 90 days | 90 days or short-lived tokens |
Technology Standards
gMSA (Group Managed Service Accounts)
- Windows Server 2012+ domain functional level
- KDS Root Key required (one per forest)
- 240-byte cryptographically random passwords
- Automatic rotation every 30 days by default
- Multiple hosts can share same gMSA
PKCS#12 / X.509 Certificate Rotation
- Certificate-based authentication preferred over passwords
- Auto-renewal via ACME protocol or internal CA
- Certificate pinning considerations for service-to-service auth
workflows.md3.5 KB
Service Account Credential Rotation - Workflows
Automated Rotation Workflow
Rotation Scheduler (cron/secrets manager)
│
├── Pre-Rotation Checks:
│ ├── Verify service account exists and is active
│ ├── Verify all dependent services are healthy
│ ├── Confirm change window (maintenance window)
│ └── Notify operations team of pending rotation
│
├── Generate New Credential:
│ ├── Create new password/key with required complexity
│ ├── Store new credential in secrets vault
│ └── Maintain old credential as backup
│
├── Update Source System:
│ ├── AD: Set-ADAccountPassword
│ ├── AWS: CreateAccessKey
│ ├── Azure: Add client secret to service principal
│ ├── GCP: Create new service account key
│ └── DB: ALTER USER ... PASSWORD ...
│
├── Propagate to Consumers:
│ ├── Update application config (env vars, config files)
│ ├── Update Kubernetes secrets
│ ├── Update CI/CD pipeline secrets
│ ├── Restart dependent services if required
│ └── Wait for propagation
│
├── Post-Rotation Verification:
│ ├── Health check all dependent services
│ ├── Test authentication with new credentials
│ ├── Verify old credential no longer works
│ └── Log rotation success/failure
│
└── Cleanup:
├── Deactivate old credential after grace period
├── Delete old credential after confirmation
└── Update rotation audit logEmergency Rotation Workflow (Credential Compromise)
Credential compromise detected
│
├── IMMEDIATE (within 15 minutes):
│ ├── Disable compromised credential
│ ├── Generate new credential
│ ├── Update source system
│ └── Notify incident response team
│
├── SHORT-TERM (within 1 hour):
│ ├── Propagate new credential to all consumers
│ ├── Verify service health
│ ├── Review audit logs for unauthorized use
│ └── Assess blast radius of compromise
│
└── FOLLOW-UP (within 24 hours):
├── Complete incident report
├── Review rotation procedures
├── Update dependent service configurations
└── Rotate any related credentialsgMSA Migration Workflow
Identify service accounts eligible for gMSA migration
│
├── For each eligible service account:
│ ├── Create gMSA in Active Directory
│ ├── Add target servers to PrincipalsAllowedToRetrieve
│ ├── Install gMSA on each target server
│ ├── Test gMSA retrieval (Test-ADServiceAccount)
│ │
│ ├── During maintenance window:
│ │ ├── Stop the service
│ │ ├── Change service logon account to gMSA
│ │ ├── Update file/folder permissions if needed
│ │ ├── Start the service
│ │ └── Verify service functionality
│ │
│ └── Post-migration:
│ ├── Disable old service account
│ ├── Monitor service for 2 weeks
│ └── Delete old account after confirmation
│
└── Update service account inventoryScripts 2
agent.py6.9 KB
#!/usr/bin/env python3
"""Agent for automating service account credential rotation.
Rotates credentials for AWS IAM access keys, Azure service principals,
and database accounts via HashiCorp Vault, with post-rotation health
checks and rollback capability.
"""
import json
import sys
import subprocess
import time
import requests
from datetime import datetime
class CredentialRotationAgent:
"""Automates service account credential rotation across platforms."""
def __init__(self, vault_url=None, vault_token=None):
self.vault_url = vault_url
self.vault_token = vault_token
self.rotation_log = []
def _log(self, platform, account, action, status, details=None):
entry = {
"timestamp": datetime.utcnow().isoformat(),
"platform": platform, "account": account,
"action": action, "status": status,
}
if details:
entry["details"] = details
self.rotation_log.append(entry)
def rotate_aws_access_key(self, username):
"""Rotate an AWS IAM user's access key using the AWS CLI."""
try:
create = subprocess.run(
["aws", "iam", "create-access-key",
"--user-name", username, "--output", "json"],
capture_output=True, text=True, timeout=30
)
if create.returncode != 0:
self._log("AWS", username, "create-key", "failed", create.stderr)
return {"error": create.stderr}
new_key = json.loads(create.stdout)["AccessKey"]
new_key_id = new_key["AccessKeyId"]
list_result = subprocess.run(
["aws", "iam", "list-access-keys",
"--user-name", username, "--output", "json"],
capture_output=True, text=True, timeout=30
)
if list_result.returncode == 0:
keys = json.loads(list_result.stdout).get("AccessKeyMetadata", [])
for key in keys:
if key["AccessKeyId"] != new_key_id and key["Status"] == "Active":
subprocess.run(
["aws", "iam", "update-access-key",
"--user-name", username,
"--access-key-id", key["AccessKeyId"],
"--status", "Inactive"],
capture_output=True, text=True, timeout=30
)
self._log("AWS", username, "deactivate-old-key", "success",
{"old_key_id": key["AccessKeyId"]})
self._log("AWS", username, "rotate-key", "success",
{"new_key_id": new_key_id})
return {"new_key_id": new_key_id, "secret_key": new_key["SecretAccessKey"]}
except (subprocess.TimeoutExpired, json.JSONDecodeError) as exc:
self._log("AWS", username, "rotate-key", "error", str(exc))
return {"error": str(exc)}
def rotate_azure_sp_secret(self, app_id, display_name="rotated-secret"):
"""Rotate an Azure AD service principal client secret via az CLI."""
try:
result = subprocess.run(
["az", "ad", "app", "credential", "reset",
"--id", app_id, "--display-name", display_name,
"--years", "1", "--output", "json"],
capture_output=True, text=True, timeout=60
)
if result.returncode == 0:
cred = json.loads(result.stdout)
self._log("Azure", app_id, "rotate-secret", "success")
return {"app_id": cred.get("appId"), "password": cred.get("password"),
"tenant": cred.get("tenant")}
self._log("Azure", app_id, "rotate-secret", "failed", result.stderr)
return {"error": result.stderr}
except (subprocess.TimeoutExpired, json.JSONDecodeError) as exc:
self._log("Azure", app_id, "rotate-secret", "error", str(exc))
return {"error": str(exc)}
def rotate_vault_database_creds(self, role_name):
"""Request new dynamic database credentials from HashiCorp Vault."""
if not self.vault_url or not self.vault_token:
return {"error": "Vault URL and token required"}
try:
resp = requests.get(
f"{self.vault_url}/v1/database/creds/{role_name}",
headers={"X-Vault-Token": self.vault_token}, timeout=15
)
if resp.status_code == 200:
data = resp.json().get("data", {})
self._log("Vault", role_name, "generate-creds", "success",
{"username": data.get("username")})
return {"username": data.get("username"),
"password": data.get("password"),
"lease_duration": resp.json().get("lease_duration")}
self._log("Vault", role_name, "generate-creds", "failed",
{"status": resp.status_code})
return {"error": resp.text}
except requests.RequestException as exc:
self._log("Vault", role_name, "generate-creds", "error", str(exc))
return {"error": str(exc)}
def verify_service_health(self, endpoints):
"""Verify services are healthy after credential rotation."""
results = []
for ep in endpoints:
for attempt in range(3):
try:
resp = requests.get(ep["url"], timeout=10,
headers=ep.get("headers", {}))
healthy = resp.status_code == 200
results.append({"service": ep["name"], "healthy": healthy,
"status_code": resp.status_code, "attempt": attempt + 1})
if healthy:
break
except requests.RequestException as exc:
results.append({"service": ep["name"], "healthy": False,
"error": str(exc), "attempt": attempt + 1})
time.sleep(5)
return results
def generate_report(self):
"""Output the rotation audit log as JSON."""
report = {
"report_date": datetime.utcnow().isoformat(),
"total_rotations": len(self.rotation_log),
"successful": sum(1 for e in self.rotation_log if e["status"] == "success"),
"failed": sum(1 for e in self.rotation_log if e["status"] != "success"),
"log": self.rotation_log,
}
print(json.dumps(report, indent=2, default=str))
return report
def main():
agent = CredentialRotationAgent(
vault_url=sys.argv[1] if len(sys.argv) > 1 else None,
vault_token=sys.argv[2] if len(sys.argv) > 2 else None,
)
if len(sys.argv) > 3:
agent.rotate_aws_access_key(sys.argv[3])
agent.generate_report()
if __name__ == "__main__":
main()
process.py9.7 KB
#!/usr/bin/env python3
"""
Service Account Credential Rotation Automation
Manages credential rotation for service accounts across Active Directory,
AWS IAM, GCP, and databases. Includes dependency tracking, health verification,
and audit logging.
Requirements:
pip install boto3 google-cloud-iam requests
"""
import json
import logging
import secrets
import string
import sys
from datetime import datetime, timezone
from pathlib import Path
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("credential_rotation")
class CredentialRotator:
"""Manages credential rotation across multiple platforms."""
def __init__(self, config_file=None):
self.rotation_log = []
self.service_dependencies = {}
if config_file:
self._load_config(config_file)
def _load_config(self, config_file):
with open(config_file) as f:
config = json.load(f)
self.service_dependencies = config.get("dependencies", {})
def generate_password(self, length=32):
"""Generate a cryptographically secure password."""
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
while True:
password = "".join(secrets.choice(alphabet) for _ in range(length))
has_upper = any(c.isupper() for c in password)
has_lower = any(c.islower() for c in password)
has_digit = any(c.isdigit() for c in password)
has_special = any(c in "!@#$%^&*" for c in password)
if has_upper and has_lower and has_digit and has_special:
return password
def rotate_aws_access_key(self, iam_username, profile_name=None):
"""Rotate AWS IAM user access key."""
try:
import boto3
except ImportError:
logger.error("boto3 not installed")
return {"success": False, "error": "boto3 required"}
session = boto3.Session(profile_name=profile_name)
iam = session.client("iam")
try:
new_key = iam.create_access_key(UserName=iam_username)
new_key_id = new_key["AccessKey"]["AccessKeyId"]
new_secret = new_key["AccessKey"]["SecretAccessKey"]
existing_keys = iam.list_access_keys(UserName=iam_username)
old_keys_deactivated = []
for key in existing_keys["AccessKeyMetadata"]:
if key["AccessKeyId"] != new_key_id and key["Status"] == "Active":
iam.update_access_key(
UserName=iam_username,
AccessKeyId=key["AccessKeyId"],
Status="Inactive"
)
old_keys_deactivated.append(key["AccessKeyId"])
result = {
"success": True,
"platform": "AWS IAM",
"account": iam_username,
"new_key_id": new_key_id,
"old_keys_deactivated": old_keys_deactivated,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
self.rotation_log.append(result)
logger.info(f"AWS key rotated for {iam_username}: {new_key_id}")
return result
except Exception as e:
result = {
"success": False,
"platform": "AWS IAM",
"account": iam_username,
"error": str(e),
"timestamp": datetime.now(timezone.utc).isoformat(),
}
self.rotation_log.append(result)
logger.error(f"AWS key rotation failed for {iam_username}: {e}")
return result
def rotate_gcp_service_account_key(self, service_account_email, project_id):
"""Rotate GCP service account key."""
try:
from google.cloud import iam_v1
except ImportError:
logger.error("google-cloud-iam not installed")
return {"success": False, "error": "google-cloud-iam required"}
try:
client = iam_v1.IAMClient()
resource = f"projects/{project_id}/serviceAccounts/{service_account_email}"
new_key = client.create_service_account_key(
request={"name": resource, "key_algorithm": "KEY_ALG_RSA_2048"}
)
keys = client.list_service_account_keys(request={"name": resource})
old_keys_deleted = []
for key in keys.keys:
if key.name != new_key.name and "user-managed" in str(key.key_origin):
try:
client.delete_service_account_key(request={"name": key.name})
old_keys_deleted.append(key.name)
except Exception:
pass
result = {
"success": True,
"platform": "GCP",
"account": service_account_email,
"new_key_name": new_key.name,
"old_keys_deleted": old_keys_deleted,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
self.rotation_log.append(result)
logger.info(f"GCP key rotated for {service_account_email}")
return result
except Exception as e:
result = {
"success": False,
"platform": "GCP",
"account": service_account_email,
"error": str(e),
"timestamp": datetime.now(timezone.utc).isoformat(),
}
self.rotation_log.append(result)
logger.error(f"GCP key rotation failed: {e}")
return result
def rotate_database_password(self, db_type, host, port, admin_user,
admin_password, target_user):
"""Rotate a database user's password."""
new_password = self.generate_password()
try:
if db_type == "postgresql":
import psycopg2
conn = psycopg2.connect(
host=host, port=port,
user=admin_user, password=admin_password,
dbname="postgres"
)
conn.autocommit = True
cur = conn.cursor()
cur.execute(
f"ALTER USER \"{target_user}\" WITH PASSWORD %s;",
(new_password,)
)
cur.close()
conn.close()
elif db_type == "mysql":
import mysql.connector
conn = mysql.connector.connect(
host=host, port=port,
user=admin_user, password=admin_password
)
cur = conn.cursor()
cur.execute(
f"ALTER USER %s@'%%' IDENTIFIED BY %s;",
(target_user, new_password)
)
cur.execute("FLUSH PRIVILEGES;")
cur.close()
conn.close()
else:
return {"success": False, "error": f"Unsupported db_type: {db_type}"}
result = {
"success": True,
"platform": f"Database ({db_type})",
"account": target_user,
"host": host,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
self.rotation_log.append(result)
logger.info(f"Database password rotated for {target_user}@{host}")
return result
except Exception as e:
result = {
"success": False,
"platform": f"Database ({db_type})",
"account": target_user,
"error": str(e),
"timestamp": datetime.now(timezone.utc).isoformat(),
}
self.rotation_log.append(result)
logger.error(f"Database rotation failed for {target_user}: {e}")
return result
def verify_service_health(self, health_endpoints):
"""Verify services are healthy after credential rotation."""
import requests
results = []
for endpoint in health_endpoints:
try:
resp = requests.get(endpoint["url"], timeout=10)
healthy = resp.status_code == 200
results.append({
"service": endpoint["name"],
"url": endpoint["url"],
"status_code": resp.status_code,
"healthy": healthy,
})
except requests.RequestException as e:
results.append({
"service": endpoint["name"],
"url": endpoint["url"],
"healthy": False,
"error": str(e),
})
return results
def export_rotation_log(self, output_path):
"""Export rotation audit log to JSON."""
log_data = {
"export_date": datetime.now(timezone.utc).isoformat(),
"total_rotations": len(self.rotation_log),
"successful": sum(1 for r in self.rotation_log if r.get("success")),
"failed": sum(1 for r in self.rotation_log if not r.get("success")),
"rotations": self.rotation_log,
}
with open(output_path, "w") as f:
json.dump(log_data, f, indent=2)
logger.info(f"Rotation log exported to {output_path}")
if __name__ == "__main__":
print("=" * 60)
print("Service Account Credential Rotation Tool")
print("=" * 60)
print()
print("Usage:")
print(" rotator = CredentialRotator()")
print(" rotator.rotate_aws_access_key('svc-app-user', profile='prod')")
print(" rotator.rotate_database_password('postgresql', 'db.example.com',")
print(" 5432, 'admin', 'admin_pw', 'svc_app_user')")
print(" rotator.export_rotation_log('rotation_log.json')")