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.
549 lines
11 KiB
Markdown
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! 🔑
|