# 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 ```bash 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 ```bash make rotate-key ``` This script will: 1. Show current keys 2. Generate new 64-character key 3. Offer rotation strategies (replace or grace period) 4. Backup old `.env` file 5. Update `.env` with new key 6. 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 ```bash docker-compose restart aegis-mcp ``` Verify both keys work: ```bash # 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 1. Go to ChatGPT Settings > MCP Servers 2. Edit "AegisGitea MCP" 3. Update Authorization header: ``` Authorization: Bearer NEW_KEY_HERE ``` 4. Save ### Step 5: Verify & Clean Up Test ChatGPT connection: ``` List my Gitea repositories ``` If successful, remove old key: ```bash # 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: ```bash 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 ```bash make generate-key ``` Save the output: ``` API KEY: b9c8d7e6f5g4h3i2... ``` ### 2. Add to .env (Grace Period) ```bash nano .env # Before: MCP_API_KEYS=a1b2c3d4e5f6g7h8... # After (both keys work): MCP_API_KEYS=a1b2c3d4e5f6g7h8...,b9c8d7e6f5g4h3i2... ``` ### 3. Restart & Verify ```bash 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: ```bash 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 1. Generate new key 2. Replace old key in `.env` 3. Restart server 4. 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 1. Generate new key 2. Add new key, keep old key (both work) 3. Restart server 4. Update ChatGPT at your convenience 5. 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 1. Announce maintenance window (e.g., Saturday 2 AM) 2. Generate new key 3. During window: - Replace key in `.env` - Restart server - Update all team members' ChatGPT configs 4. 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 ```bash # 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 ```bash # 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 ```bash # 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 OK - `1` = Warning (rotation recommended in 14 days) - `2` = Critical (rotation needed NOW) ### Email Notifications ```bash #!/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 ```bash #!/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) 1. **Revoke compromised key immediately** ```bash # 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 ``` 2. **Check audit logs for unauthorized access** ```bash docker-compose exec aegis-mcp grep "compromised_key_hint" /var/log/aegis-mcp/audit.log # Look for suspicious IPs, unusual times, unexpected repositories ``` 3. **Generate new key** ```bash make generate-key ``` 4. **Update .env with new key** ```bash MCP_API_KEYS=new-secure-key AUTH_ENABLED=true docker-compose restart aegis-mcp ``` 5. **Update ChatGPT config** ### Investigation (< 30 minutes) ```bash # 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) 1. Document timeline of compromise 2. Review and improve key storage practices 3. Consider additional security measures (IP allowlisting, MFA) 4. 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:** ```bash # 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:** ```bash # 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:** ```bash # 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:** ```bash # 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: 1. Generate new key 2. Update `.env` 3. Update ChatGPT 4. 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 `.env` with 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: ```bash make generate-key # Choose "y" when prompted to save metadata ``` This creates `keys/key--.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](AUTH_SETUP.md) - Initial setup guide - [ChatGPT Configuration](CHATGPT_SETUP.md) - Update ChatGPT after rotation - [Security Policy](SECURITY.md) - Overall security best practices --- **Set a reminder now:** Rotate your keys 90 days from today! 🔑