Implements comprehensive Bearer token authentication to ensure only authorized ChatGPT workspaces can access the MCP server. Core Features: - API key validation with constant-time comparison - Multi-key support for rotation grace periods - Rate limiting (5 failures per IP per 5 min) - Comprehensive audit logging of all auth attempts - IP-based failed attempt tracking Key Management: - generate_api_key.py: Create secure 64-char keys - rotate_api_key.py: Guided key rotation with backup - check_key_age.py: Automated expiration monitoring Infrastructure: - Traefik labels for HTTPS and rate limiting - Security headers (HSTS, CSP, X-Frame-Options) - Environment-based configuration - Docker secrets support Documentation: - AUTH_SETUP.md: Complete authentication setup guide - CHATGPT_SETUP.md: ChatGPT Business integration guide - KEY_ROTATION.md: Key rotation procedures and automation Security: - Read-only operations enforced - No write access to Gitea possible - All auth attempts logged with correlation IDs - Failed attempts trigger IP rate limits - Keys never logged in full (only hints) Breaking Changes: - AUTH_ENABLED defaults to true - MCP_API_KEYS environment variable now required - Minimum key length: 32 characters (64 recommended) Migration: 1. Generate API key: make generate-key 2. Add to .env: MCP_API_KEYS=<generated-key> 3. Restart: docker-compose restart aegis-mcp 4. Configure ChatGPT with Authorization header Closes requirements for ChatGPT Business exclusive access.
114 lines
3.6 KiB
Python
Executable File
114 lines
3.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Generate a cryptographically secure API key for AegisGitea MCP."""
|
|
|
|
import sys
|
|
from datetime import datetime, timedelta, timezone
|
|
from pathlib import Path
|
|
|
|
# Add src to path for imports
|
|
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
|
|
from aegis_gitea_mcp.auth import generate_api_key
|
|
|
|
|
|
def main() -> None:
|
|
"""Generate and display a new API key."""
|
|
print("=" * 70)
|
|
print("AegisGitea MCP - API Key Generator")
|
|
print("=" * 70)
|
|
print()
|
|
|
|
# Get optional description
|
|
description = input("Enter description for this key (e.g., 'ChatGPT Business'): ").strip()
|
|
if not description:
|
|
description = "Generated key"
|
|
|
|
# Generate key
|
|
api_key = generate_api_key(length=64)
|
|
|
|
# Calculate expiration date (90 days from now)
|
|
created_at = datetime.now(timezone.utc)
|
|
expires_at = created_at + timedelta(days=90)
|
|
|
|
print()
|
|
print("✓ API Key Generated Successfully!")
|
|
print()
|
|
print("-" * 70)
|
|
print(f"Description: {description}")
|
|
print(f"Created: {created_at.strftime('%Y-%m-%d %H:%M:%S UTC')}")
|
|
print(f"Expires: {expires_at.strftime('%Y-%m-%d %H:%M:%S UTC')} (90 days)")
|
|
print("-" * 70)
|
|
print()
|
|
print("API KEY:")
|
|
print(f" {api_key}")
|
|
print()
|
|
print("-" * 70)
|
|
print()
|
|
print("📋 Next Steps:")
|
|
print()
|
|
print("1. Add this key to your .env file:")
|
|
print()
|
|
print(f" MCP_API_KEYS={api_key}")
|
|
print()
|
|
print(" (If you have multiple keys, separate them with commas)")
|
|
print()
|
|
print("2. Restart the MCP server:")
|
|
print()
|
|
print(" docker-compose restart aegis-mcp")
|
|
print()
|
|
print("3. Configure ChatGPT Business:")
|
|
print()
|
|
print(" - Go to ChatGPT Settings > MCP Servers")
|
|
print(" - Add custom header:")
|
|
print(f" Authorization: Bearer {api_key}")
|
|
print()
|
|
print("4. Test the connection:")
|
|
print()
|
|
print(" Ask ChatGPT: 'List my Gitea repositories'")
|
|
print()
|
|
print("-" * 70)
|
|
print()
|
|
print("⚠️ IMPORTANT:")
|
|
print()
|
|
print(" • Store this key securely - it won't be shown again")
|
|
print(" • This key should be rotated in 90 days")
|
|
print(" • Set a reminder to rotate before expiration")
|
|
print(" • Never commit this key to version control")
|
|
print()
|
|
print("=" * 70)
|
|
|
|
# Offer to save metadata to file
|
|
save = input("\nSave key metadata to keys/ directory? [y/N]: ").strip().lower()
|
|
if save == "y":
|
|
keys_dir = Path(__file__).parent.parent / "keys"
|
|
keys_dir.mkdir(exist_ok=True)
|
|
|
|
# Create metadata file
|
|
key_id = api_key[:12]
|
|
metadata_file = keys_dir / f"key-{key_id}-{created_at.strftime('%Y%m%d')}.txt"
|
|
|
|
with open(metadata_file, "w") as f:
|
|
f.write(f"API Key Metadata\n")
|
|
f.write(f"================\n\n")
|
|
f.write(f"Key ID: {key_id}...\n")
|
|
f.write(f"Description: {description}\n")
|
|
f.write(f"Created: {created_at.isoformat()}\n")
|
|
f.write(f"Expires: {expires_at.isoformat()}\n")
|
|
f.write(f"\n")
|
|
f.write(f"NOTE: The actual API key is NOT stored in this file for security.\n")
|
|
f.write(f" Only metadata is saved for reference.\n")
|
|
|
|
print(f"\n✓ Metadata saved to: {metadata_file}")
|
|
print(f"\n (The actual key is NOT saved - only you have it)")
|
|
|
|
|
|
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)
|
|
sys.exit(1)
|