""" Check AWS Secrets Manager secrets containing M2M_JWT_ACCESS_TOKEN pattern and validate their rotation schedule. poetry install awsume prod poetry run python3 scripts/check_secret_rotation.py """ from datetime import UTC, datetime, timedelta from typing import Any import boto3 from mypy_boto3_secretsmanager import SecretsManagerClient def format_datetime(dt: datetime | None) -> str: """Format datetime object to readable string.""" if dt is None: return "N/A" return dt.strftime("%Y-%m-%d %H:%M:%S") def is_it_expired(last_rotated: datetime | None) -> bool: """ Check if the secret has expired (last rotation was more than 8 hours ago). Returns True if expired, False if not expired. """ if last_rotated is None: return False # Make last_rotated timezone-aware if it isn't already if last_rotated.tzinfo is None: last_rotated = last_rotated.replace(tzinfo=UTC) now = datetime.now(last_rotated.tzinfo) time_since_rotation = now - last_rotated return time_since_rotation > timedelta(hours=8) def is_valid_rotation_schedule( last_rotated: datetime | None, next_rotation: datetime | None ) -> str: """ Check if next rotation is scheduled 8 hours after last rotation. Returns 'Yes', 'No', or 'N/A'. """ if last_rotated is None or next_rotation is None: return "N/A" expected_next = last_rotated + timedelta(hours=8) # Allow 1 minute tolerance diff = abs((next_rotation - expected_next).total_seconds()) return "Yes" if diff <= 3600 else "No" def get_secret_rotation_info( client: SecretsManagerClient, secret_name: str ) -> dict[str, Any]: """Get rotation information for a specific secret.""" try: response = client.describe_secret(SecretId=secret_name) return { "name": secret_name, "arn": response.get("ARN", "N/A"), "last_rotated": response.get("LastRotatedDate"), "next_rotation": response.get("NextRotationDate"), } except Exception as e: print(f"Error getting details for {secret_name}: {e}") # noqa return { "name": secret_name, "arn": "N/A", "last_rotated": None, "next_rotation": None, } def main() -> None: # Initialize AWS Secrets Manager client client = boto3.client("secretsmanager") # List all secrets paginator = client.get_paginator("list_secrets") secrets = [] print( # noqa: T201 "ARN,Secret Name,Last Rotated Date,Next Rotated Date,Is Secret Considered Expired aka > 8 hours since last rotation,Is Schedule Valid?" ) for page in paginator.paginate( Filters=[{"Key": "all", "Values": ["M2M_JWT_ACCESS_TOKEN"]}] ): for secret in page["SecretList"]: secrets.append(secret["Name"]) info = get_secret_rotation_info(client, secret["Name"]) arn = info["arn"] last_rotated = info["last_rotated"] next_rotation = info["next_rotation"] is_expired = is_it_expired(last_rotated) is_valid = is_valid_rotation_schedule(last_rotated, next_rotation) print( # noqa: T201 f"{arn},{secret['Name']},{format_datetime(last_rotated)},{format_datetime(next_rotation)},{is_expired},{is_valid}" ) if __name__ == "__main__": main()