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

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


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:

  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

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

  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:

# 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

  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

# 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 OK
  • 1 = 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)

  1. 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
    
  2. 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
    
  3. Generate new key

    make generate-key
    
  4. Update .env with new key

    MCP_API_KEYS=new-secure-key
    AUTH_ENABLED=true
    
    docker-compose restart aegis-mcp
    
  5. 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)

  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:

# 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:

  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:

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


Set a reminder now: Rotate your keys 90 days from today! 🔑