Files
AegisGitea-MCP/KEY_ROTATION.md
latte eeaad748a6 feat: add API key authentication system for ChatGPT Business
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.
2026-01-29 20:05:49 +01:00

549 lines
11 KiB
Markdown

# 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-<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](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! 🔑