identity access management

Performing Service Account Credential Rotation

Automate credential rotation for service accounts across Active Directory, cloud platforms, and application databases to eliminate stale secrets and reduce compromise risk.

automationcredential-rotationpamsecrets-managementservice-accountsvault
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

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, ServicePrincipalName

Step 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 results

Validation 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

Source materials

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

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 log

Emergency 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 credentials

gMSA 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 inventory

Scripts 2

agent.py6.9 KB
Display-only source. This catalog never executes bundled scripts.
#!/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
Display-only source. This catalog never executes bundled scripts.
#!/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')")

Assets 1

template.mdtext/markdown · 1.1 KB
Keep exploring