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.
11 KiB
API Key Rotation Guide
Comprehensive guide for rotating API keys in AegisGitea MCP with zero downtime.
Why Rotate Keys?
- Security hygiene: Regular rotation limits exposure window
- Compliance: Many security policies require quarterly rotation
- Compromise mitigation: If a key leaks, rotation limits damage
- Audit trail: New keys = clear timeline of when access changed
Recommended schedule: Every 90 days
Automated Rotation (Recommended)
Step 1: Check Key Age
make check-key-age
Output:
Key: a1b2c3d4...
Created: 2026-01-29 (85 days ago)
Expires: 2026-04-29 (in 5 days)
Status: ⚠️ WARNING
Step 2: Rotate Key
make rotate-key
This script will:
- Show current keys
- Generate new 64-character key
- Offer rotation strategies (replace or grace period)
- Backup old
.envfile - Update
.envwith new key - Provide next steps
Example:
Found 1 existing key(s)
1. a1b2c3d4...e1f2
✓ New API key generated!
Rotation Strategy:
1. Replace all keys with new key (recommended)
2. Add new key, keep old keys (grace period)
3. Cancel
Choose option [1/2/3]: 2
✓ New key will be added (total: 2 keys)
⚠️ IMPORTANT: Remove old keys manually after updating ChatGPT config
Step 3: Restart Server
docker-compose restart aegis-mcp
Verify both keys work:
# Test old key (should still work if using grace period)
curl -H "Authorization: Bearer OLD_KEY" \
https://mcp.yourdomain.com/mcp/tools
# Test new key (should work)
curl -H "Authorization: Bearer NEW_KEY" \
https://mcp.yourdomain.com/mcp/tools
Step 4: Update ChatGPT
- Go to ChatGPT Settings > MCP Servers
- Edit "AegisGitea MCP"
- Update Authorization header:
Authorization: Bearer NEW_KEY_HERE - Save
Step 5: Verify & Clean Up
Test ChatGPT connection:
List my Gitea repositories
If successful, remove old key:
# Edit .env - remove old key, keep only new key
nano .env
# MCP_API_KEYS=old-key,new-key ← Remove old-key
# MCP_API_KEYS=new-key ← Keep only new key
# Restart
docker-compose restart aegis-mcp
Verify old key no longer works:
curl -H "Authorization: Bearer OLD_KEY" \
https://mcp.yourdomain.com/mcp/tools
# Should return 401 Unauthorized
Manual Rotation (Step-by-Step)
If you prefer manual control:
1. Generate New Key
make generate-key
Save the output:
API KEY: b9c8d7e6f5g4h3i2...
2. Add to .env (Grace Period)
nano .env
# Before:
MCP_API_KEYS=a1b2c3d4e5f6g7h8...
# After (both keys work):
MCP_API_KEYS=a1b2c3d4e5f6g7h8...,b9c8d7e6f5g4h3i2...
3. Restart & Verify
docker-compose restart aegis-mcp
# Check logs
docker-compose logs aegis-mcp | grep "authentication"
# Should show: "API key authentication ENABLED (2 key(s) configured)"
4. Update ChatGPT
Update Authorization header with new key.
5. Remove Old Key
After confirming ChatGPT works with new key:
nano .env
# Remove old key
MCP_API_KEYS=b9c8d7e6f5g4h3i2...
docker-compose restart aegis-mcp
Rotation Strategies
Strategy 1: Immediate Replacement (No Downtime Risk)
Use when: You can update ChatGPT config immediately
- Generate new key
- Replace old key in
.env - Restart server
- Update ChatGPT within minutes
Pros: Clean, only one key at a time Cons: Must update ChatGPT immediately or service breaks
Strategy 2: Grace Period (Zero Downtime)
Use when: You need time to update ChatGPT config
- Generate new key
- Add new key, keep old key (both work)
- Restart server
- Update ChatGPT at your convenience
- Remove old key after verification
Pros: Zero downtime, test thoroughly Cons: Temporarily allows two keys
Recommended grace period: 24-48 hours max
Strategy 3: Scheduled Maintenance Window
Use when: You want to coordinate with team
- Announce maintenance window (e.g., Saturday 2 AM)
- Generate new key
- During window:
- Replace key in
.env - Restart server
- Update all team members' ChatGPT configs
- Replace key in
- Verify all users can access
Pros: Coordinated, everyone updates at once Cons: Requires coordination
Multi-User Rotation
If multiple team members use different keys:
Option A: Rotate One Key at a Time
# Current state
MCP_API_KEYS=alice-key,bob-key,charlie-key
# Rotate Alice's key
MCP_API_KEYS=alice-new-key,bob-key,charlie-key
# Alice updates her ChatGPT, verify, continue
Option B: Rotate All Keys Simultaneously
# Generate 3 new keys
make generate-key # Save as alice-new
make generate-key # Save as bob-new
make generate-key # Save as charlie-new
# Grace period
MCP_API_KEYS=alice-old,alice-new,bob-old,bob-new,charlie-old,charlie-new
# Each user updates
# After all verified, remove old keys
MCP_API_KEYS=alice-new,bob-new,charlie-new
Automated Rotation Schedule
Setup Cron Job
# Edit crontab
crontab -e
# Check key age weekly
0 9 * * MON /path/to/AegisGitea-MCP/scripts/check_key_age.py || echo "Keys need rotation"
# Or check daily at 2 AM
0 2 * * * cd /path/to/AegisGitea-MCP && make check-key-age
Exit codes:
0= All keys OK1= Warning (rotation recommended in 14 days)2= Critical (rotation needed NOW)
Email Notifications
#!/bin/bash
# Save as scripts/check_and_notify.sh
cd /path/to/AegisGitea-MCP
# Run check
make check-key-age > /tmp/key_status.txt 2>&1
STATUS=$?
if [ $STATUS -eq 2 ]; then
# Critical - send email
mail -s "[CRITICAL] AegisGitea API Key Expired" admin@example.com < /tmp/key_status.txt
elif [ $STATUS -eq 1 ]; then
# Warning - send email
mail -s "[WARNING] AegisGitea API Key Expiring Soon" admin@example.com < /tmp/key_status.txt
fi
Discord/Slack Webhook
#!/bin/bash
# Save as scripts/notify_discord.sh
WEBHOOK_URL="https://discord.com/api/webhooks/..."
MESSAGE="⚠️ AegisGitea MCP: API key expires in 5 days. Run \`make rotate-key\`"
curl -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"content\": \"$MESSAGE\"}"
Emergency Rotation (Compromised Key)
If you suspect a key has been compromised:
Immediate Actions (< 5 minutes)
-
Revoke compromised key immediately
# Edit .env - remove compromised key nano .env # Before MCP_API_KEYS=compromised-key # After MCP_API_KEYS= # Or temporarily disable auth AUTH_ENABLED=false docker-compose restart aegis-mcp -
Check audit logs for unauthorized access
docker-compose exec aegis-mcp grep "compromised_key_hint" /var/log/aegis-mcp/audit.log # Look for suspicious IPs, unusual times, unexpected repositories -
Generate new key
make generate-key -
Update .env with new key
MCP_API_KEYS=new-secure-key AUTH_ENABLED=true docker-compose restart aegis-mcp -
Update ChatGPT config
Investigation (< 30 minutes)
# When was compromised key last used?
docker-compose exec aegis-mcp grep "key_hint: compromised..." /var/log/aegis-mcp/audit.log | tail -1
# What repositories were accessed?
docker-compose exec aegis-mcp grep "compromised..." /var/log/aegis-mcp/audit.log | grep "repository"
# From which IPs?
docker-compose exec aegis-mcp grep "compromised..." /var/log/aegis-mcp/audit.log | grep "client_ip"
Post-Incident (< 24 hours)
- Document timeline of compromise
- Review and improve key storage practices
- Consider additional security measures (IP allowlisting, MFA)
- Notify team if multi-user setup
Troubleshooting Rotation
Issue: Both old and new keys fail after rotation
Cause: Server not restarted or .env syntax error
Fix:
# Check .env syntax
cat .env | grep MCP_API_KEYS
# Should be: MCP_API_KEYS=key1,key2
# No spaces around =
# No quotes around keys
# Comma-separated, no spaces
# Restart
docker-compose restart aegis-mcp
# Check logs
docker-compose logs aegis-mcp | tail -20
Issue: "No API keys configured" after rotation
Cause: Empty MCP_API_KEYS in .env
Fix:
# Verify .env has key
grep MCP_API_KEYS .env
# Should NOT be empty
# If empty, add key
echo "MCP_API_KEYS=your-key-here" >> .env
docker-compose restart aegis-mcp
Issue: Old key still works after removal
Cause: Server not restarted or cached config
Fix:
# Force restart (not just reload)
docker-compose down
docker-compose up -d
# Verify old key fails
curl -H "Authorization: Bearer OLD_KEY" \
https://mcp.yourdomain.com/mcp/tools
# Should return 401
Issue: Lost .env.backup file
Cause: Backup not created during rotation
Prevention:
# Always backup before manual edits
cp .env .env.backup-$(date +%Y%m%d-%H%M%S)
# The rotate script does this automatically
Recovery: If you lost your key and no backup:
- Generate new key
- Update
.env - Update ChatGPT
- The old key is lost but system still works
Best Practices Checklist
Before rotation:
- Check current key age:
make check-key-age - Backup
.env:cp .env .env.backup - Notify team if multi-user
- Schedule maintenance window if needed
During rotation:
- Generate new key:
make generate-key - Save key securely (password manager)
- Update
.envwith new key - Restart server:
docker-compose restart - Verify new key works (curl test)
- Update ChatGPT config
- Test ChatGPT connection
After rotation:
- Remove old key from
.env(if using grace period) - Verify old key fails (curl test)
- Check audit logs for successful auth with new key
- Save key metadata: Save metadata when generating key
- Set calendar reminder for next rotation (90 days)
- Document rotation in changelog/runbook
Key Metadata Tracking
When generating keys, save metadata:
make generate-key
# Choose "y" when prompted to save metadata
This creates keys/key-<id>-<date>.txt:
API Key Metadata
================
Key ID: a1b2c3d4...
Description: Production ChatGPT Key
Created: 2026-01-29T12:00:00+00:00
Expires: 2026-04-29T12:00:00+00:00
NOTE: The actual API key is NOT stored in this file for security.
Only metadata is saved for reference.
Benefits:
- Track key age automatically
- Audit key lifecycle
- Identify which key is which (if multiple)
- Automated expiration warnings
FAQ
Q: What's the grace period for rotation?
A: 24-48 hours is recommended. Keep both keys working just long enough to update ChatGPT, then remove the old key.
Q: Can I rotate keys without downtime?
A: Yes! Use Strategy 2 (Grace Period). Both old and new keys work simultaneously during the transition.
Q: How do I know which key is oldest?
A: Use make check-key-age if you saved metadata. Otherwise, check audit logs for last usage.
Q: Should I rotate after a team member leaves?
A: Yes, if they had access to the shared key. Or better: use per-user keys and just remove their key.
Q: Can I automate the entire rotation?
A: Partially. You can automate generation and notification, but ChatGPT config update requires manual action.
Next Steps
- Authentication Setup - Initial setup guide
- ChatGPT Configuration - Update ChatGPT after rotation
- Security Policy - Overall security best practices
Set a reminder now: Rotate your keys 90 days from today! 🔑