140 lines
4.2 KiB
Python
Executable File
140 lines
4.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Check API key age and alert if rotation needed."""
|
|
|
|
import re
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
|
|
def main() -> None:
|
|
"""Check API key age from keys/ metadata directory."""
|
|
print("=" * 70)
|
|
print("AegisGitea MCP - API Key Age Check")
|
|
print("=" * 70)
|
|
print()
|
|
|
|
# Check keys directory
|
|
keys_dir = Path(__file__).parent.parent / "keys"
|
|
|
|
if not keys_dir.exists():
|
|
print("⚠️ No keys/ directory found")
|
|
print()
|
|
print(" Key metadata is not being tracked.")
|
|
print(" Run scripts/generate_api_key.py and save metadata to track key age.")
|
|
print()
|
|
sys.exit(0)
|
|
|
|
# Find all key metadata files
|
|
metadata_files = list(keys_dir.glob("key-*.txt"))
|
|
|
|
if not metadata_files:
|
|
print("⚠️ No key metadata files found in keys/")
|
|
print()
|
|
print(" Run scripts/generate_api_key.py and save metadata to track key age.")
|
|
print()
|
|
sys.exit(0)
|
|
|
|
print(f"Found {len(metadata_files)} key metadata file(s)")
|
|
print()
|
|
print("-" * 70)
|
|
print()
|
|
|
|
now = datetime.now(timezone.utc)
|
|
warnings = []
|
|
critical = []
|
|
|
|
for metadata_file in sorted(metadata_files):
|
|
with open(metadata_file, "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
|
|
# Extract metadata
|
|
key_id_match = re.search(r"Key ID:\s+([^\n]+)", content)
|
|
created_match = re.search(r"Created:\s+([^\n]+)", content)
|
|
expires_match = re.search(r"Expires:\s+([^\n]+)", content)
|
|
|
|
if not all([key_id_match, created_match, expires_match]):
|
|
print(f"⚠️ Could not parse: {metadata_file.name}")
|
|
continue
|
|
|
|
key_id = key_id_match.group(1).strip()
|
|
created_str = created_match.group(1).strip()
|
|
expires_str = expires_match.group(1).strip()
|
|
|
|
# Parse dates
|
|
try:
|
|
created_at = datetime.fromisoformat(created_str.replace("Z", "+00:00"))
|
|
expires_at = datetime.fromisoformat(expires_str.replace("Z", "+00:00"))
|
|
except ValueError as e:
|
|
print(f"⚠️ Could not parse dates in: {metadata_file.name}")
|
|
print(f" Error: {e}")
|
|
continue
|
|
|
|
# Calculate age and time to expiration
|
|
age_days = (now - created_at).days
|
|
days_to_expiration = (expires_at - now).days
|
|
|
|
# Status
|
|
if days_to_expiration < 0:
|
|
status = "❌ EXPIRED"
|
|
critical.append(key_id)
|
|
elif days_to_expiration <= 7:
|
|
status = "⚠️ CRITICAL"
|
|
critical.append(key_id)
|
|
elif days_to_expiration <= 14:
|
|
status = "⚠️ WARNING"
|
|
warnings.append(key_id)
|
|
else:
|
|
status = "✓ OK"
|
|
|
|
print(f"Key: {key_id}")
|
|
print(f" Created: {created_at.strftime('%Y-%m-%d')} ({age_days} days ago)")
|
|
print(f" Expires: {expires_at.strftime('%Y-%m-%d')} (in {days_to_expiration} days)")
|
|
print(f" Status: {status}")
|
|
print()
|
|
|
|
print("-" * 70)
|
|
print()
|
|
|
|
# Summary
|
|
if critical:
|
|
print("❌ CRITICAL: {} key(s) require immediate rotation!".format(len(critical)))
|
|
print()
|
|
print(" Keys:")
|
|
for key_id in critical:
|
|
print(f" • {key_id}")
|
|
print()
|
|
print(" Action: Run scripts/rotate_api_key.py NOW")
|
|
print()
|
|
sys.exit(2)
|
|
elif warnings:
|
|
print("⚠️ WARNING: {} key(s) will expire soon".format(len(warnings)))
|
|
print()
|
|
print(" Keys:")
|
|
for key_id in warnings:
|
|
print(f" • {key_id}")
|
|
print()
|
|
print(" Action: Schedule key rotation in the next few days")
|
|
print(" Run: scripts/rotate_api_key.py")
|
|
print()
|
|
sys.exit(1)
|
|
else:
|
|
print("✓ All keys are valid and within rotation schedule")
|
|
print()
|
|
print(" Next check: Run this script again in 1 week")
|
|
print()
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
except KeyboardInterrupt:
|
|
print("\n\nOperation cancelled.")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f"\n❌ Error: {e}", file=sys.stderr)
|
|
import traceback
|
|
traceback.print_exc()
|
|
sys.exit(1)
|