update
This commit is contained in:
34
AGENTS.md
Normal file
34
AGENTS.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
Core application code lives in `src/aegis_gitea_mcp/`:
|
||||
- `server.py` contains FastAPI routes and MCP/SSE endpoints.
|
||||
- `auth.py`, `config.py`, `audit.py`, and `gitea_client.py` handle security, settings, logging, and Gitea API access.
|
||||
- `tools/` contains MCP tool implementations (for example `tools/repository.py`).
|
||||
|
||||
Tests are in `tests/` and follow module-level coverage for auth, config, server, and integration flows. Utility scripts (key generation/rotation checks) are in `scripts/`. Container assets are in `docker/` with runtime orchestration in `docker-compose.yml`.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- `make install`: install runtime dependencies.
|
||||
- `make install-dev`: install dev dependencies and pre-commit hooks.
|
||||
- `make run`: run the server locally (`python -m aegis_gitea_mcp.server`).
|
||||
- `make test`: run pytest with coverage output.
|
||||
- `make lint`: run `ruff` + `mypy`.
|
||||
- `make format`: run `black` and auto-fix lint issues.
|
||||
- `make docker-up` / `make docker-down`: start/stop local container stack.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
Use Python 3.10+ with 4-space indentation and type hints for production code. Keep lines within 100 chars (Black/Ruff setting). Modules and functions use `snake_case`; classes use `PascalCase`; constants use `UPPER_SNAKE_CASE`. Prefer explicit exceptions and preserve exception chaining (`raise ... from exc`) when wrapping errors.
|
||||
|
||||
## Testing Guidelines
|
||||
Framework: `pytest` with `pytest-asyncio` and coverage (`--cov=aegis_gitea_mcp`). Place tests under `tests/` using `test_*.py` naming and `test_*` function names. Add or update tests for behavior changes, especially around authentication, API error paths, and MCP tool responses.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
Prefer Conventional Commit style used in history (`feat:`, `fix:`, `docs:`, `test:`). Keep subjects imperative and specific (avoid messages like `update` or `quick fix`). PRs should include:
|
||||
- what changed and why,
|
||||
- linked issue(s) if available,
|
||||
- test/lint evidence (`make test`, `make lint`),
|
||||
- notes for config/security impact when touching auth, keys, or `.env` behavior.
|
||||
|
||||
## Security & Configuration Tips
|
||||
Never commit secrets. Use `.env` (see `.env.example`) for `GITEA_TOKEN` and `MCP_API_KEYS`. Use `scripts/generate_api_key.py`, `scripts/rotate_api_key.py`, and `scripts/check_key_age.py` for API key lifecycle management.
|
||||
456
ARCHITECTURE.md
456
ARCHITECTURE.md
@@ -1,456 +0,0 @@
|
||||
# AegisGitea MCP - Architecture Documentation
|
||||
|
||||
---
|
||||
|
||||
## System Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ ChatGPT Business │
|
||||
│ (AI Assistant Interface) │
|
||||
│ │
|
||||
│ User: "Show me the files in my-repo" │
|
||||
└────────────────────────────┬────────────────────────────────────────┘
|
||||
│ HTTPS (MCP over SSE)
|
||||
│ Tool: get_file_tree(owner, repo)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Reverse Proxy (Traefik/Nginx) │
|
||||
│ TLS Termination │
|
||||
└────────────────────────────┬────────────────────────────────────────┘
|
||||
│ HTTP
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ AegisGitea MCP Server (Docker) │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ FastAPI Application │ │
|
||||
│ │ │ │
|
||||
│ │ Endpoints: │ │
|
||||
│ │ - GET /health (Health check) │ │
|
||||
│ │ - GET /mcp/tools (List available tools) │ │
|
||||
│ │ - POST /mcp/tool/call (Execute tool) │ │
|
||||
│ │ - GET /mcp/sse (Server-sent events) │ │
|
||||
│ └───────────────────────┬───────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────────────────┴───────────────────────────────────────┐ │
|
||||
│ │ MCP Protocol Handler │ │
|
||||
│ │ - Tool validation │ │
|
||||
│ │ - Request/response mapping │ │
|
||||
│ │ - Correlation ID management │ │
|
||||
│ └───────────────────────┬───────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────────────────┴───────────────────────────────────────┐ │
|
||||
│ │ Tool Implementations │ │
|
||||
│ │ │ │
|
||||
│ │ - list_repositories() - get_repository_info() │ │
|
||||
│ │ - get_file_tree() - get_file_contents() │ │
|
||||
│ └───────────────────────┬───────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────┬────────┴────────┬─────────────────────────────┐ │
|
||||
│ │ │ │ │ │
|
||||
│ │ ┌───────────▼───────┐ ┌─────▼──────┐ ┌────────────────┐ │ │
|
||||
│ │ │ Gitea Client │ │ Config │ │ Audit Logger │ │ │
|
||||
│ │ │ - Auth │ │ Manager │ │ - Structured │ │ │
|
||||
│ │ │ - API calls │ │ - Env vars│ │ - JSON logs │ │ │
|
||||
│ │ │ - Error handling│ │ - Defaults│ │ - Correlation │ │ │
|
||||
│ │ └───────────┬───────┘ └────────────┘ └────────┬───────┘ │ │
|
||||
│ │ │ │ │ │
|
||||
│ └──────────────┼────────────────────────────────────┼─────────┘ │
|
||||
│ │ │ │
|
||||
└─────────────────┼────────────────────────────────────┼───────────┘
|
||||
│ Gitea API │
|
||||
│ (Authorization: token XXX) │ Audit Logs
|
||||
▼ ▼
|
||||
┌─────────────────────────────────────┐ ┌──────────────────────────┐
|
||||
│ Gitea Instance │ │ Persistent Volume │
|
||||
│ (Self-hosted VCS) │ │ /var/log/aegis-mcp/ │
|
||||
│ │ │ audit.log │
|
||||
│ Repositories: │ └──────────────────────────┘
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ org/repo-1 (bot has access)│ │
|
||||
│ │ org/repo-2 (bot has access)│ │
|
||||
│ │ org/private (NO ACCESS) │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ │
|
||||
│ Bot User: aegis-bot │
|
||||
│ Permissions: Read-only │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Responsibilities
|
||||
|
||||
### 1. ChatGPT (External)
|
||||
**Responsibility**: Initiate explicit tool calls based on user requests
|
||||
|
||||
- Receives MCP tool definitions
|
||||
- Constructs tool call requests
|
||||
- Presents results to user
|
||||
- Human-in-the-loop decision making
|
||||
|
||||
### 2. Reverse Proxy
|
||||
**Responsibility**: TLS termination and routing
|
||||
|
||||
- Terminates HTTPS connections
|
||||
- Routes to MCP server container
|
||||
- Handles SSL certificates
|
||||
- Optional: IP filtering, rate limiting
|
||||
|
||||
### 3. AegisGitea MCP Server (Core)
|
||||
**Responsibility**: MCP protocol implementation and policy enforcement
|
||||
|
||||
#### 3a. FastAPI Application
|
||||
- HTTP server with async support
|
||||
- Server-Sent Events endpoint
|
||||
- Health and status endpoints
|
||||
- Request routing
|
||||
|
||||
#### 3b. MCP Protocol Handler
|
||||
- Tool definition management
|
||||
- Request validation
|
||||
- Response formatting
|
||||
- Correlation ID tracking
|
||||
|
||||
#### 3c. Tool Implementations
|
||||
- Repository discovery
|
||||
- File tree navigation
|
||||
- File content retrieval
|
||||
- Bounded, single-purpose operations
|
||||
|
||||
#### 3d. Gitea Client
|
||||
- Async HTTP client for Gitea API
|
||||
- Bot user authentication
|
||||
- Error handling and retries
|
||||
- Response parsing
|
||||
|
||||
#### 3e. Config Manager
|
||||
- Environment variable loading
|
||||
- Validation with Pydantic
|
||||
- Default values
|
||||
- Type safety
|
||||
|
||||
#### 3f. Audit Logger
|
||||
- Structured JSON logging
|
||||
- Correlation ID tracking
|
||||
- Timestamp (UTC)
|
||||
- Append-only logs
|
||||
|
||||
### 4. Gitea Instance
|
||||
**Responsibility**: Authorization and data storage
|
||||
|
||||
- Source of truth for permissions
|
||||
- Repository data storage
|
||||
- Bot user management
|
||||
- Access control enforcement
|
||||
|
||||
### 5. Persistent Volume
|
||||
**Responsibility**: Audit log storage
|
||||
|
||||
- Durable storage for audit logs
|
||||
- Survives container restarts
|
||||
- Accessible for review/analysis
|
||||
|
||||
---
|
||||
|
||||
## Data Flow: Tool Invocation
|
||||
|
||||
```
|
||||
1. User Request
|
||||
├─> "Show me files in org/my-repo"
|
||||
└─> ChatGPT decides to call: get_file_tree(owner="org", repo="my-repo")
|
||||
|
||||
2. MCP Request
|
||||
├─> POST /mcp/tool/call
|
||||
├─> Body: {"tool": "get_file_tree", "arguments": {"owner": "org", "repo": "my-repo"}}
|
||||
└─> Generate correlation_id: uuid4()
|
||||
|
||||
3. Audit Log (Entry)
|
||||
├─> Log: tool_invocation
|
||||
├─> tool_name: "get_file_tree"
|
||||
├─> repository: "org/my-repo"
|
||||
└─> status: "pending"
|
||||
|
||||
4. Gitea API Call
|
||||
├─> GET /api/v1/repos/org/my-repo/git/trees/main
|
||||
├─> Header: Authorization: token XXX
|
||||
└─> Response: {"tree": [...files...]}
|
||||
|
||||
5. Authorization Check
|
||||
├─> 200 OK → Bot has access
|
||||
├─> 403 Forbidden → Log access_denied, raise error
|
||||
└─> 404 Not Found → Repository doesn't exist or no access
|
||||
|
||||
6. Response Processing
|
||||
├─> Extract file tree
|
||||
├─> Transform to simplified format
|
||||
└─> Apply size/count limits
|
||||
|
||||
7. Audit Log (Success)
|
||||
├─> Log: tool_invocation
|
||||
├─> status: "success"
|
||||
└─> params: {"count": 42}
|
||||
|
||||
8. MCP Response
|
||||
├─> 200 OK
|
||||
├─> Body: {"success": true, "result": {...files...}}
|
||||
└─> correlation_id: same as request
|
||||
|
||||
9. ChatGPT Processing
|
||||
├─> Receive file tree data
|
||||
├─> Format for user presentation
|
||||
└─> "Here are the files in org/my-repo: ..."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Boundaries
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ Trust Boundary 1 │
|
||||
│ (Internet ↔ MCP Server) │
|
||||
│ │
|
||||
│ Controls: │
|
||||
│ - HTTPS/TLS encryption │
|
||||
│ - Reverse proxy authentication (optional) │
|
||||
│ - Rate limiting │
|
||||
│ - Firewall rules │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ Trust Boundary 2 │
|
||||
│ (MCP Server ↔ Gitea API) │
|
||||
│ │
|
||||
│ Controls: │
|
||||
│ - Bot user token authentication │
|
||||
│ - Gitea's access control (authoritative) │
|
||||
│ - API request timeouts │
|
||||
│ - Input validation │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ Trust Boundary 3 │
|
||||
│ (Container ↔ Host System) │
|
||||
│ │
|
||||
│ Controls: │
|
||||
│ - Non-root container user │
|
||||
│ - Resource limits (CPU, memory) │
|
||||
│ - No new privileges │
|
||||
│ - Read-only filesystem (where possible) │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Authorization Flow
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ AI requests access to "org/private-repo" │
|
||||
└────────────────────────┬─────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ MCP Server: Forward to Gitea API │
|
||||
│ with bot user token │
|
||||
└───────────────┬───────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ Gitea: Check bot user permissions │
|
||||
│ for "org/private-repo" │
|
||||
└───────────────┬───────────────────────┘
|
||||
│
|
||||
┌───────┴────────┐
|
||||
│ │
|
||||
Bot is collaborator? │
|
||||
│ │
|
||||
┌────────▼─────┐ ┌──────▼──────┐
|
||||
│ YES │ │ NO │
|
||||
│ (Read access)│ │ (No access) │
|
||||
└────────┬─────┘ └──────┬──────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌───────────────┐ ┌─────────────────┐
|
||||
│ Return data │ │ Return 403 │
|
||||
│ Log: success │ │ Log: denied │
|
||||
└───────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
**Key Insight**: The MCP server never makes authorization decisions - it only forwards requests and respects Gitea's response.
|
||||
|
||||
---
|
||||
|
||||
## Failure Modes & Handling
|
||||
|
||||
### 1. Gitea Unavailable
|
||||
- **Detection**: HTTP connection error
|
||||
- **Response**: Return error to ChatGPT
|
||||
- **Logging**: Log connection failure
|
||||
- **Recovery**: Automatic retry on next request
|
||||
|
||||
### 2. Invalid Bot Token
|
||||
- **Detection**: 401 Unauthorized from Gitea
|
||||
- **Response**: Log security event, return auth error
|
||||
- **Logging**: High-severity security log
|
||||
- **Recovery**: Operator must rotate token
|
||||
|
||||
### 3. Bot Lacks Permission
|
||||
- **Detection**: 403 Forbidden from Gitea
|
||||
- **Response**: Return authorization error
|
||||
- **Logging**: Access denied event
|
||||
- **Recovery**: Grant permission in Gitea UI
|
||||
|
||||
### 4. File Too Large
|
||||
- **Detection**: File size exceeds MAX_FILE_SIZE_BYTES
|
||||
- **Response**: Return size limit error
|
||||
- **Logging**: Security event (potential abuse)
|
||||
- **Recovery**: Increase limit or reject request
|
||||
|
||||
### 5. Network Timeout
|
||||
- **Detection**: Request exceeds REQUEST_TIMEOUT_SECONDS
|
||||
- **Response**: Return timeout error
|
||||
- **Logging**: Log timeout event
|
||||
- **Recovery**: Automatic retry possible
|
||||
|
||||
### 6. Rate Limit Exceeded
|
||||
- **Detection**: Too many requests per minute
|
||||
- **Response**: Return 429 Too Many Requests
|
||||
- **Logging**: Log rate limit event
|
||||
- **Recovery**: Wait and retry
|
||||
|
||||
---
|
||||
|
||||
## Scaling Considerations
|
||||
|
||||
### Vertical Scaling (Single Instance)
|
||||
- **Current**: 128-512 MB RAM, minimal CPU
|
||||
- **Bottleneck**: Gitea API response time
|
||||
- **Max throughput**: ~100-200 requests/second
|
||||
|
||||
### Horizontal Scaling (Multiple Instances)
|
||||
- **Stateless design**: Each instance independent
|
||||
- **Load balancing**: Standard HTTP load balancer
|
||||
- **Shared state**: None (all state in Gitea)
|
||||
- **Audit logs**: Each instance writes to own log (or use centralized logging)
|
||||
|
||||
### Performance Optimization (Future)
|
||||
- Add Redis caching layer
|
||||
- Implement connection pooling
|
||||
- Use HTTP/2 for Gitea API
|
||||
- Batch multiple file reads
|
||||
|
||||
---
|
||||
|
||||
## Observability
|
||||
|
||||
### Metrics to Monitor
|
||||
1. **Request rate**: Requests per minute
|
||||
2. **Error rate**: Failed requests / total requests
|
||||
3. **Response time**: P50, P95, P99 latency
|
||||
4. **Gitea API health**: Success rate to Gitea
|
||||
5. **Auth failures**: 401/403 responses
|
||||
|
||||
### Logs to Track
|
||||
1. **Audit logs**: Every tool invocation
|
||||
2. **Access denied**: Permission violations
|
||||
3. **Security events**: Rate limits, size limits
|
||||
4. **Errors**: Exceptions and failures
|
||||
|
||||
### Alerts to Configure
|
||||
1. **High error rate**: > 5% errors
|
||||
2. **Auth failures**: Any 401 responses
|
||||
3. **Gitea unreachable**: Connection failures
|
||||
4. **Disk space**: Audit logs filling disk
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Phase 2: Extended Context
|
||||
```
|
||||
New Tools:
|
||||
├── get_commits(owner, repo, limit)
|
||||
├── get_commit_diff(owner, repo, sha)
|
||||
├── list_issues(owner, repo)
|
||||
├── get_issue(owner, repo, number)
|
||||
├── list_pull_requests(owner, repo)
|
||||
└── get_pull_request(owner, repo, number)
|
||||
```
|
||||
|
||||
### Phase 3: Advanced Features
|
||||
```
|
||||
Capabilities:
|
||||
├── Caching layer (Redis)
|
||||
├── Webhook support for real-time updates
|
||||
├── OAuth2 flow instead of static tokens
|
||||
├── Per-client rate limiting
|
||||
├── Multi-tenant support (multiple bot users)
|
||||
└── GraphQL API for more efficient queries
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment Patterns
|
||||
|
||||
### Pattern 1: Single Homelab Instance
|
||||
```
|
||||
[Homelab Server]
|
||||
├── Gitea container
|
||||
├── AegisGitea MCP container
|
||||
└── Caddy reverse proxy
|
||||
└── Exposes HTTPS endpoint
|
||||
```
|
||||
|
||||
### Pattern 2: Kubernetes Deployment
|
||||
```
|
||||
[Kubernetes Cluster]
|
||||
├── Namespace: aegis-mcp
|
||||
├── Deployment: aegis-mcp (3 replicas)
|
||||
├── Service: ClusterIP
|
||||
├── Ingress: HTTPS with cert-manager
|
||||
└── PersistentVolume: Audit logs
|
||||
```
|
||||
|
||||
### Pattern 3: Cloud Deployment
|
||||
```
|
||||
[AWS/GCP/Azure]
|
||||
├── Container service (ECS/Cloud Run/ACI)
|
||||
├── Load balancer (ALB/Cloud Load Balancing)
|
||||
├── Secrets manager (Secrets Manager/Secret Manager/Key Vault)
|
||||
└── Log aggregation (CloudWatch/Cloud Logging/Monitor)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- Configuration loading
|
||||
- Gitea client methods
|
||||
- Tool implementations
|
||||
- Audit logging
|
||||
|
||||
### Integration Tests
|
||||
- Full MCP protocol flow
|
||||
- Gitea API interactions (mocked)
|
||||
- Error handling paths
|
||||
|
||||
### End-to-End Tests
|
||||
- Real Gitea instance
|
||||
- Real bot user
|
||||
- Real tool invocations
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
This architecture prioritizes:
|
||||
1. **Security**: Read-only, auditable, fail-safe
|
||||
2. **Simplicity**: Straightforward data flow
|
||||
3. **Maintainability**: Clear separation of concerns
|
||||
4. **Observability**: Comprehensive logging
|
||||
|
||||
The design is intentionally boring and predictable - perfect for a security-critical system.
|
||||
@@ -1,408 +0,0 @@
|
||||
# Authentication System Implementation Summary
|
||||
|
||||
**Branch:** `feature/authentication-system`
|
||||
**Status:** ✅ Complete and Pushed
|
||||
**Commit:** `eeaad74`
|
||||
|
||||
---
|
||||
|
||||
## 🎉 What Was Built
|
||||
|
||||
A complete API key authentication system that ensures only YOUR ChatGPT Business workspace can access your self-hosted Gitea MCP server.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Files Created
|
||||
|
||||
### Core Authentication Module
|
||||
- **`src/aegis_gitea_mcp/auth.py`** (215 lines)
|
||||
- `APIKeyValidator` class with constant-time comparison
|
||||
- Rate limiting (5 failures per IP per 5 minutes)
|
||||
- Failed attempt tracking
|
||||
- Bearer token extraction and validation
|
||||
- `generate_api_key()` function for secure key generation
|
||||
|
||||
### Key Management Scripts
|
||||
- **`scripts/generate_api_key.py`** (125 lines)
|
||||
- Interactive key generation wizard
|
||||
- Metadata tracking (description, creation date, expiration)
|
||||
- .env configuration snippet output
|
||||
- Optional metadata file storage
|
||||
|
||||
- **`scripts/rotate_api_key.py`** (155 lines)
|
||||
- Guided key rotation with automatic backup
|
||||
- Grace period support (multi-key transition)
|
||||
- Old .env backup before changes
|
||||
- Step-by-step instructions
|
||||
|
||||
- **`scripts/check_key_age.py`** (130 lines)
|
||||
- Automated expiration monitoring
|
||||
- Warning system (14 days, 7 days, expired)
|
||||
- Cron-ready exit codes
|
||||
- Metadata file parsing
|
||||
|
||||
### Documentation
|
||||
- **`AUTH_SETUP.md`** (620 lines)
|
||||
- Complete authentication setup guide
|
||||
- Security features overview
|
||||
- Troubleshooting guide
|
||||
- Monitoring and best practices
|
||||
|
||||
- **`CHATGPT_SETUP.md`** (520 lines)
|
||||
- ChatGPT Business integration guide
|
||||
- Step-by-step configuration
|
||||
- Example commands to try
|
||||
- Multi-user setup instructions
|
||||
|
||||
- **`KEY_ROTATION.md`** (600 lines)
|
||||
- Automated rotation procedures
|
||||
- Manual rotation step-by-step
|
||||
- Emergency rotation (compromised key)
|
||||
- Multi-user rotation strategies
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Files Modified
|
||||
|
||||
### Configuration
|
||||
- **`src/aegis_gitea_mcp/config.py`**
|
||||
- Added `auth_enabled: bool` (default: True)
|
||||
- Added `mcp_api_keys: List[str]` (comma-separated parsing)
|
||||
- Added `max_auth_failures: int` (default: 5)
|
||||
- Added `auth_failure_window: int` (default: 300 seconds)
|
||||
- Validation: Keys must be at least 32 characters
|
||||
- Validation: At least one key required if auth enabled
|
||||
|
||||
### Server
|
||||
- **`src/aegis_gitea_mcp/server.py`**
|
||||
- Added authentication middleware
|
||||
- Validates all `/mcp/*` endpoints (excludes `/health` and `/`)
|
||||
- Extracts Bearer token from Authorization header
|
||||
- Returns 401 with helpful error messages on auth failure
|
||||
- Logs authentication status on startup
|
||||
|
||||
### Infrastructure
|
||||
- **`docker-compose.yml`**
|
||||
- Added Traefik labels for automatic HTTPS
|
||||
- Added rate limiting middleware (60 req/min per IP)
|
||||
- Added security headers (HSTS, CSP, X-Frame-Options)
|
||||
- Connected to external Traefik network
|
||||
- Added `MCP_DOMAIN` environment variable support
|
||||
|
||||
- **`.env.example`**
|
||||
- Added `AUTH_ENABLED` (default: true)
|
||||
- Added `MCP_API_KEYS` (required if auth enabled)
|
||||
- Added `MCP_DOMAIN` (for Traefik routing)
|
||||
- Added `MAX_AUTH_FAILURES` (default: 5)
|
||||
- Added `AUTH_FAILURE_WINDOW` (default: 300)
|
||||
- Documented multi-key configuration
|
||||
|
||||
- **`.gitignore`**
|
||||
- Added `keys/` (metadata storage)
|
||||
- Added `.env.backup-*` (rotation backups)
|
||||
- Added `*.key` (key files)
|
||||
|
||||
- **`Makefile`**
|
||||
- Added `make generate-key` command
|
||||
- Added `make rotate-key` command
|
||||
- Added `make check-key-age` command
|
||||
- Updated help text
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security Features Implemented
|
||||
|
||||
### 1. Authentication
|
||||
- ✅ Bearer token validation
|
||||
- ✅ Constant-time key comparison (prevents timing attacks)
|
||||
- ✅ Multi-key support (rotation grace periods)
|
||||
- ✅ Minimum 32-character keys (64 recommended)
|
||||
- ✅ No authentication on health checks (monitoring-friendly)
|
||||
|
||||
### 2. Rate Limiting
|
||||
- ✅ 5 failed attempts per IP before blocking
|
||||
- ✅ 5-minute time window (configurable)
|
||||
- ✅ In-memory tracking (resets on restart)
|
||||
- ✅ High-severity security events logged
|
||||
|
||||
### 3. Audit Logging
|
||||
- ✅ All auth attempts logged (success and failure)
|
||||
- ✅ Client IP and user agent captured
|
||||
- ✅ Key hints logged (first 8 + last 4 chars only)
|
||||
- ✅ Correlation IDs for request tracking
|
||||
- ✅ Timestamps in UTC
|
||||
|
||||
### 4. Network Security
|
||||
- ✅ Traefik labels for automatic HTTPS
|
||||
- ✅ Security headers (HSTS, X-Frame-Options, CSP)
|
||||
- ✅ Rate limiting at proxy level (60/min per IP)
|
||||
- ✅ External network isolation
|
||||
|
||||
---
|
||||
|
||||
## 📊 Statistics
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| **Total Lines Added** | ~2,263 |
|
||||
| **Python Code** | ~900 lines |
|
||||
| **Documentation** | ~1,740 lines |
|
||||
| **Scripts** | 3 |
|
||||
| **Docs** | 3 |
|
||||
| **Config Changes** | 5 files |
|
||||
| **New Modules** | 1 (auth.py) |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start (For Users)
|
||||
|
||||
### Step 1: Generate API Key
|
||||
```bash
|
||||
make generate-key
|
||||
```
|
||||
|
||||
### Step 2: Add to .env
|
||||
```bash
|
||||
echo "MCP_API_KEYS=<your-generated-key>" >> .env
|
||||
```
|
||||
|
||||
### Step 3: Restart Server
|
||||
```bash
|
||||
docker-compose restart aegis-mcp
|
||||
```
|
||||
|
||||
### Step 4: Configure ChatGPT Business
|
||||
- Go to ChatGPT Settings > MCP Servers
|
||||
- Add custom header:
|
||||
```
|
||||
Authorization: Bearer <your-generated-key>
|
||||
```
|
||||
|
||||
### Step 5: Test
|
||||
```
|
||||
Ask ChatGPT: "List my Gitea repositories"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Checklist
|
||||
|
||||
### Manual Testing Required
|
||||
|
||||
Before merging to main:
|
||||
|
||||
- [ ] Generate API key with `make generate-key`
|
||||
- [ ] Add key to `.env` file
|
||||
- [ ] Start server: `docker-compose up -d`
|
||||
- [ ] Test without key (should return 401):
|
||||
```bash
|
||||
curl https://mcp.yourdomain.com/mcp/tools
|
||||
```
|
||||
- [ ] Test with invalid key (should return 401):
|
||||
```bash
|
||||
curl -H "Authorization: Bearer invalid-key" https://mcp.yourdomain.com/mcp/tools
|
||||
```
|
||||
- [ ] Test with valid key (should return 200):
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <valid-key>" https://mcp.yourdomain.com/mcp/tools
|
||||
```
|
||||
- [ ] Test rate limiting (6 failed attempts, should block)
|
||||
- [ ] Test key rotation with `make rotate-key`
|
||||
- [ ] Test key age check with `make check-key-age`
|
||||
- [ ] Configure ChatGPT and test actual usage
|
||||
- [ ] Check audit logs show auth events:
|
||||
```bash
|
||||
docker-compose exec aegis-mcp cat /var/log/aegis-mcp/audit.log | grep auth
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Migration Guide (For Existing Installations)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
1. **Authentication now enabled by default**
|
||||
- Old installations without `MCP_API_KEYS` will fail to start
|
||||
- Must generate and configure API key
|
||||
|
||||
2. **Environment variable required**
|
||||
- `MCP_API_KEYS` must be set if `AUTH_ENABLED=true` (default)
|
||||
- Minimum 32 characters, 64 recommended
|
||||
|
||||
### Migration Steps
|
||||
|
||||
```bash
|
||||
# 1. Pull latest code
|
||||
git pull origin feature/authentication-system
|
||||
|
||||
# 2. Generate API key
|
||||
make generate-key
|
||||
|
||||
# 3. Update .env
|
||||
echo "MCP_API_KEYS=<generated-key>" >> .env
|
||||
|
||||
# 4. Restart
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
|
||||
# 5. Update ChatGPT configuration
|
||||
# Add Authorization header in ChatGPT settings
|
||||
|
||||
# 6. Verify
|
||||
curl -H "Authorization: Bearer <key>" https://mcp.yourdomain.com/mcp/tools
|
||||
```
|
||||
|
||||
### Rollback Plan
|
||||
|
||||
If issues arise:
|
||||
|
||||
```bash
|
||||
# Temporarily disable authentication
|
||||
echo "AUTH_ENABLED=false" >> .env
|
||||
docker-compose restart aegis-mcp
|
||||
|
||||
# Server will log warning but allow all requests
|
||||
# Fix configuration, then re-enable auth
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Code Review Notes
|
||||
|
||||
### Architecture Decisions
|
||||
|
||||
1. **Bearer Token over OAuth2**
|
||||
- Simpler for single-user/small team
|
||||
- ChatGPT Business compatible
|
||||
- Easy to rotate and manage
|
||||
|
||||
2. **In-Memory Rate Limiting**
|
||||
- Sufficient for single-instance deployment
|
||||
- Resets on restart (acceptable tradeoff)
|
||||
- Can be upgraded to Redis if needed
|
||||
|
||||
3. **Plaintext Keys in .env**
|
||||
- Current: Keys stored in plaintext in `.env`
|
||||
- Future: Can add hashing with minimal refactoring
|
||||
- Mitigation: File permissions, container isolation
|
||||
|
||||
4. **No Database for Key Metadata**
|
||||
- Optional metadata files in `keys/` directory
|
||||
- Simple, no additional dependencies
|
||||
- Easy to backup and version control (metadata only)
|
||||
|
||||
### Security Considerations
|
||||
|
||||
- ✅ Constant-time comparison prevents timing attacks
|
||||
- ✅ Keys never logged in full (only hints)
|
||||
- ✅ Rate limiting prevents brute force
|
||||
- ✅ Audit logging for accountability
|
||||
- ⚠️ Keys in `.env` require filesystem protection
|
||||
- ⚠️ In-memory rate limits reset on restart
|
||||
|
||||
### Performance Impact
|
||||
|
||||
- Negligible (< 1ms per request for auth check)
|
||||
- Constant-time comparison slightly slower than `==` but necessary
|
||||
- No database queries needed
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
All objectives met:
|
||||
|
||||
- ✅ Only authorized ChatGPT workspaces can access MCP server
|
||||
- ✅ API keys easy to generate, rotate, and manage
|
||||
- ✅ Comprehensive audit logging of all auth attempts
|
||||
- ✅ Zero downtime key rotation support
|
||||
- ✅ Rate limiting prevents abuse
|
||||
- ✅ Complete documentation for setup and usage
|
||||
- ✅ Traefik integration with HTTPS and security headers
|
||||
- ✅ Multi-key support for team environments
|
||||
- ✅ Emergency rotation procedures documented
|
||||
|
||||
---
|
||||
|
||||
## 🚦 Next Steps
|
||||
|
||||
### Immediate (Before Merge)
|
||||
1. Manual testing of all features
|
||||
2. Verify documentation accuracy
|
||||
3. Test on clean installation
|
||||
4. Update main README.md to reference AUTH_SETUP.md
|
||||
|
||||
### Future Enhancements (Not in This PR)
|
||||
1. Discord/Slack webhooks for alerts (`alerts.py`)
|
||||
2. Prometheus metrics endpoint (`metrics.py`)
|
||||
3. Automated tests for authentication (`tests/test_auth.py`)
|
||||
4. Key hashing instead of plaintext storage
|
||||
5. Redis-based rate limiting for multi-instance
|
||||
6. OAuth2 flow for enterprise environments
|
||||
7. Web dashboard for key management
|
||||
|
||||
---
|
||||
|
||||
## 📞 Pull Request Link
|
||||
|
||||
Create PR at:
|
||||
```
|
||||
https://git.hiddenden.cafe/Hiddenden/AegisGitea-MCP/pulls/new/feature/authentication-system
|
||||
```
|
||||
|
||||
**Suggested PR Title:**
|
||||
```
|
||||
feat: Add API key authentication for ChatGPT Business exclusive access
|
||||
```
|
||||
|
||||
**Suggested PR Description:**
|
||||
```
|
||||
Implements comprehensive Bearer token authentication to ensure only
|
||||
authorized ChatGPT workspaces can access the self-hosted Gitea MCP server.
|
||||
|
||||
## Features
|
||||
- API key validation with rate limiting
|
||||
- Key management scripts (generate, rotate, check age)
|
||||
- Traefik integration with HTTPS and security headers
|
||||
- Comprehensive documentation (AUTH_SETUP.md, CHATGPT_SETUP.md, KEY_ROTATION.md)
|
||||
- Multi-key support for rotation grace periods
|
||||
|
||||
## Security
|
||||
- Constant-time key comparison
|
||||
- Failed attempt tracking (5 per IP per 5 min)
|
||||
- Comprehensive audit logging
|
||||
- No keys logged in full
|
||||
|
||||
## Breaking Changes
|
||||
- `MCP_API_KEYS` environment variable now required
|
||||
- Authentication enabled by default
|
||||
|
||||
## Migration
|
||||
See AUTHENTICATION_IMPLEMENTATION_SUMMARY.md for complete migration guide.
|
||||
|
||||
## Testing
|
||||
Manual testing checklist provided in summary document.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Acknowledgments
|
||||
|
||||
Built based on requirements for:
|
||||
- ChatGPT Business workspace exclusive access
|
||||
- Self-hosted Gitea private instance
|
||||
- Traefik (Pangolin) reverse proxy
|
||||
- Security-first, audit-focused design
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ Ready for Review and Testing
|
||||
|
||||
**Branch:** `feature/authentication-system`
|
||||
**Commit:** `eeaad74`
|
||||
**Files Changed:** 13 (8 added, 5 modified)
|
||||
**Lines Added:** ~2,263
|
||||
|
||||
---
|
||||
|
||||
**Great work! The authentication system is complete, documented, and pushed to the remote repository.** 🎉
|
||||
437
AUTH_SETUP.md
437
AUTH_SETUP.md
@@ -1,437 +0,0 @@
|
||||
# Authentication Setup Guide
|
||||
|
||||
This guide walks you through setting up API key authentication for AegisGitea MCP to ensure only your ChatGPT workspace can access your self-hosted Gitea instance.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
AegisGitea MCP uses **Bearer Token authentication** to secure access:
|
||||
|
||||
- **API Keys**: Cryptographically secure 64-character tokens
|
||||
- **Header-based**: Keys sent via `Authorization: Bearer <key>` header
|
||||
- **Multi-key support**: Multiple keys for rotation grace periods
|
||||
- **Rate limiting**: Failed auth attempts trigger IP-based rate limits
|
||||
- **Audit logging**: All auth attempts logged for security monitoring
|
||||
|
||||
---
|
||||
|
||||
## Quick Start (5 minutes)
|
||||
|
||||
### 1. Generate API Key
|
||||
|
||||
```bash
|
||||
# Using Make
|
||||
make generate-key
|
||||
|
||||
# Or directly
|
||||
python3 scripts/generate_api_key.py
|
||||
```
|
||||
|
||||
This will:
|
||||
- Generate a secure 64-character API key
|
||||
- Show you the key (save it immediately!)
|
||||
- Provide `.env` configuration snippet
|
||||
- Optionally save metadata (not the key itself) for tracking
|
||||
|
||||
**Example output:**
|
||||
```
|
||||
✓ API Key Generated Successfully!
|
||||
|
||||
API KEY:
|
||||
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
|
||||
|
||||
📋 Next Steps:
|
||||
1. Add this key to your .env file
|
||||
2. Restart the MCP server
|
||||
3. Configure ChatGPT Business
|
||||
```
|
||||
|
||||
### 2. Add Key to .env
|
||||
|
||||
```bash
|
||||
# Edit .env
|
||||
nano .env
|
||||
|
||||
# Add/update this line:
|
||||
MCP_API_KEYS=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
|
||||
```
|
||||
|
||||
### 3. Restart MCP Server
|
||||
|
||||
```bash
|
||||
docker-compose restart aegis-mcp
|
||||
|
||||
# Verify authentication is enabled
|
||||
docker-compose logs aegis-mcp | grep "authentication"
|
||||
```
|
||||
|
||||
Expected log output:
|
||||
```
|
||||
API key authentication ENABLED (1 key(s) configured)
|
||||
```
|
||||
|
||||
### 4. Test Authentication
|
||||
|
||||
```bash
|
||||
# Without key - should fail with 401
|
||||
curl https://mcp.yourdomain.com/mcp/tools
|
||||
|
||||
# With valid key (header) - should succeed
|
||||
curl -H "Authorization: Bearer YOUR_KEY_HERE" \
|
||||
https://mcp.yourdomain.com/mcp/tools
|
||||
|
||||
# With valid key (query param) - should succeed
|
||||
curl "https://mcp.yourdomain.com/mcp/tools?api_key=YOUR_KEY_HERE"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
# Enable/disable authentication
|
||||
AUTH_ENABLED=true # Set to false ONLY for testing
|
||||
|
||||
# API keys (comma-separated for multiple)
|
||||
MCP_API_KEYS=key1 # Single key
|
||||
MCP_API_KEYS=key1,key2,key3 # Multiple keys (rotation grace period)
|
||||
|
||||
# Rate limiting
|
||||
MAX_AUTH_FAILURES=5 # Max failed attempts before blocking
|
||||
AUTH_FAILURE_WINDOW=300 # Time window in seconds (5 min)
|
||||
```
|
||||
|
||||
### Multiple Keys (Rotation Grace Period)
|
||||
|
||||
During key rotation, you can temporarily allow multiple keys:
|
||||
|
||||
```bash
|
||||
# Old key
|
||||
MCP_API_KEYS=old-key-here
|
||||
|
||||
# Add new key (both work)
|
||||
MCP_API_KEYS=old-key-here,new-key-here
|
||||
|
||||
# After updating ChatGPT, remove old key
|
||||
MCP_API_KEYS=new-key-here
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Features
|
||||
|
||||
### 1. Constant-Time Comparison
|
||||
|
||||
Keys are compared using `hmac.compare_digest()` to prevent timing attacks.
|
||||
|
||||
### 2. Rate Limiting
|
||||
|
||||
- **Threshold**: 5 failed attempts per IP
|
||||
- **Window**: 5 minutes (configurable)
|
||||
- **Action**: Reject all requests from that IP until window expires
|
||||
- **Logging**: High-severity security event logged
|
||||
|
||||
### 3. Audit Logging
|
||||
|
||||
Every authentication attempt logs:
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-01-29T12:34:56.789Z",
|
||||
"event": "api_authentication",
|
||||
"status": "success",
|
||||
"client_ip": "203.0.113.42",
|
||||
"user_agent": "ChatGPT-User/1.0",
|
||||
"key_hint": "a1b2c3d4...e1f2"
|
||||
}
|
||||
```
|
||||
|
||||
Failed attempts:
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-01-29T12:34:56.789Z",
|
||||
"event": "access_denied",
|
||||
"reason": "invalid_api_key",
|
||||
"client_ip": "203.0.113.42"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Key Format Validation
|
||||
|
||||
- Minimum length: 32 characters
|
||||
- Recommended length: 64 characters
|
||||
- Format: Hexadecimal string (0-9, a-f)
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: "No API keys configured" error
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
docker-compose logs: "No API keys configured in environment"
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
1. Check `.env` file exists and contains `MCP_API_KEYS`
|
||||
2. Ensure no typos (`MCP_API_KEYS` not `MCP_API_KEY`)
|
||||
3. Verify key is at least 32 characters
|
||||
4. Restart container after updating `.env`
|
||||
|
||||
### Issue: "Invalid API key" on valid key
|
||||
|
||||
**Possible causes:**
|
||||
|
||||
1. **Whitespace in .env**: Remove spaces around `=`
|
||||
```bash
|
||||
# Wrong
|
||||
MCP_API_KEYS = key-here
|
||||
|
||||
# Correct
|
||||
MCP_API_KEYS=key-here
|
||||
```
|
||||
|
||||
2. **Key truncated**: Ensure entire 64-char key is copied
|
||||
```bash
|
||||
# Check key length
|
||||
echo -n "your-key-here" | wc -c
|
||||
# Should output: 64
|
||||
```
|
||||
|
||||
3. **Container not restarted**: Always restart after changing `.env`
|
||||
```bash
|
||||
docker-compose restart aegis-mcp
|
||||
```
|
||||
|
||||
### Issue: Rate limit blocking legitimate requests
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
"Too many failed authentication attempts"
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
1. Check audit logs for failed attempts:
|
||||
```bash
|
||||
docker-compose exec aegis-mcp cat /var/log/aegis-mcp/audit.log | grep "invalid_api_key"
|
||||
```
|
||||
|
||||
2. Wait 5 minutes for rate limit to reset
|
||||
|
||||
3. If accidentally blocked yourself:
|
||||
```bash
|
||||
# Restart container (clears in-memory rate limits)
|
||||
docker-compose restart aegis-mcp
|
||||
```
|
||||
|
||||
4. Adjust rate limit settings in `.env` if needed:
|
||||
```bash
|
||||
MAX_AUTH_FAILURES=10
|
||||
AUTH_FAILURE_WINDOW=600
|
||||
```
|
||||
|
||||
### Issue: ChatGPT can't connect after adding auth
|
||||
|
||||
See [CHATGPT_SETUP.md](CHATGPT_SETUP.md) for detailed ChatGPT configuration.
|
||||
|
||||
**Quick check:**
|
||||
1. Verify key in ChatGPT settings matches `.env`
|
||||
2. Check Authorization header format: `Bearer <key>` (with space)
|
||||
3. Test manually with curl first
|
||||
|
||||
---
|
||||
|
||||
## Monitoring Authentication
|
||||
|
||||
### View All Auth Attempts
|
||||
|
||||
```bash
|
||||
# All auth events
|
||||
docker-compose exec aegis-mcp grep "api_authentication" /var/log/aegis-mcp/audit.log
|
||||
|
||||
# Failed attempts only
|
||||
docker-compose exec aegis-mcp grep "access_denied" /var/log/aegis-mcp/audit.log
|
||||
|
||||
# Rate limit triggers
|
||||
docker-compose exec aegis-mcp grep "auth_rate_limit_exceeded" /var/log/aegis-mcp/audit.log
|
||||
```
|
||||
|
||||
### Real-Time Monitoring
|
||||
|
||||
```bash
|
||||
# Follow auth events
|
||||
docker-compose exec aegis-mcp tail -f /var/log/aegis-mcp/audit.log | grep "auth"
|
||||
```
|
||||
|
||||
### Weekly Summary Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Save as scripts/auth_summary.sh
|
||||
|
||||
CONTAINER="aegis-gitea-mcp"
|
||||
LOG_PATH="/var/log/aegis-mcp/audit.log"
|
||||
|
||||
echo "=== Weekly Auth Summary ==="
|
||||
echo ""
|
||||
echo "Total auth attempts:"
|
||||
docker exec $CONTAINER grep "api_authentication" $LOG_PATH | wc -l
|
||||
|
||||
echo ""
|
||||
echo "Successful:"
|
||||
docker exec $CONTAINER grep "api_authentication.*success" $LOG_PATH | wc -l
|
||||
|
||||
echo ""
|
||||
echo "Failed:"
|
||||
docker exec $CONTAINER grep "access_denied.*invalid_api_key" $LOG_PATH | wc -l
|
||||
|
||||
echo ""
|
||||
echo "Rate limited IPs:"
|
||||
docker exec $CONTAINER grep "auth_rate_limit_exceeded" $LOG_PATH | wc -l
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Key Storage
|
||||
|
||||
- ✅ Store in `.env` file (never commit to git)
|
||||
- ✅ Use password manager for backup
|
||||
- ✅ Save key metadata (not key itself) for tracking
|
||||
- ❌ Never hardcode in application code
|
||||
- ❌ Never share via unencrypted channels
|
||||
|
||||
### 2. Key Rotation
|
||||
|
||||
- **Schedule**: Every 90 days (automated check available)
|
||||
- **Method**: Use `make rotate-key` for guided rotation
|
||||
- **Grace period**: Temporarily allow both old and new keys
|
||||
- **Verification**: Test new key before removing old key
|
||||
|
||||
See [KEY_ROTATION.md](KEY_ROTATION.md) for detailed rotation procedures.
|
||||
|
||||
### 3. Access Control
|
||||
|
||||
- **Single workspace**: One key for your ChatGPT Business account
|
||||
- **Multiple users**: One key per user (track with metadata)
|
||||
- **Revocation**: Remove key from `.env` and restart
|
||||
|
||||
### 4. Monitoring
|
||||
|
||||
- **Weekly**: Review auth logs for anomalies
|
||||
- **Monthly**: Check key age with `make check-key-age`
|
||||
- **Quarterly**: Rotate keys
|
||||
- **Alerts**: Set up notifications for rate limit events
|
||||
|
||||
---
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Disable Authentication (Testing Only)
|
||||
|
||||
**⚠️ WARNING: Only for isolated test environments**
|
||||
|
||||
```bash
|
||||
# .env
|
||||
AUTH_ENABLED=false
|
||||
```
|
||||
|
||||
Server will log critical warning:
|
||||
```
|
||||
API key authentication DISABLED - server is open to all requests!
|
||||
```
|
||||
|
||||
### Custom Rate Limiting
|
||||
|
||||
```bash
|
||||
# More lenient (for high-traffic)
|
||||
MAX_AUTH_FAILURES=20
|
||||
AUTH_FAILURE_WINDOW=600 # 10 minutes
|
||||
|
||||
# More strict (for high-security)
|
||||
MAX_AUTH_FAILURES=3
|
||||
AUTH_FAILURE_WINDOW=1800 # 30 minutes
|
||||
```
|
||||
|
||||
### Key Hashing (Future Enhancement)
|
||||
|
||||
Currently keys are stored in plaintext in `.env`. Future versions will support hashed keys in database.
|
||||
|
||||
**Current security model:**
|
||||
- ✅ Keys never logged in full (only first 8 + last 4 chars)
|
||||
- ✅ `.env` file protected by filesystem permissions
|
||||
- ✅ Container runs as non-root user
|
||||
- ⚠️ Ensure `.env` has restrictive permissions: `chmod 600 .env`
|
||||
|
||||
---
|
||||
|
||||
## Security Checklist
|
||||
|
||||
Before going to production:
|
||||
|
||||
- [ ] API key generated with `make generate-key` (not manually typed)
|
||||
- [ ] Key is exactly 64 characters
|
||||
- [ ] `.env` file has restrictive permissions (`chmod 600 .env`)
|
||||
- [ ] `.env` is in `.gitignore` (verify: `git status` shows no .env)
|
||||
- [ ] Container restarted after adding key
|
||||
- [ ] Authentication enabled (check logs for "ENABLED" message)
|
||||
- [ ] Test curl command succeeds with key, fails without
|
||||
- [ ] ChatGPT configured with Authorization header
|
||||
- [ ] Rate limiting tested (try 6 failed attempts)
|
||||
- [ ] Audit logs capturing auth events
|
||||
- [ ] Key rotation reminder set (90 days)
|
||||
- [ ] Backup key stored securely (password manager)
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Can I use the same key for multiple ChatGPT workspaces?**
|
||||
|
||||
A: Yes, but not recommended. Generate separate keys for auditability:
|
||||
```bash
|
||||
MCP_API_KEYS=workspace1-key,workspace2-key
|
||||
```
|
||||
|
||||
**Q: How do I revoke access immediately?**
|
||||
|
||||
A: Remove key from `.env` and restart:
|
||||
```bash
|
||||
# Edit .env (remove old key)
|
||||
docker-compose restart aegis-mcp
|
||||
```
|
||||
|
||||
Access is revoked instantly after restart.
|
||||
|
||||
**Q: Does rate limiting affect valid keys?**
|
||||
|
||||
A: No. Rate limiting only applies to IPs that fail authentication. Valid keys are never rate limited.
|
||||
|
||||
**Q: Can I see which key was used for each request?**
|
||||
|
||||
A: Yes. Audit logs include `key_hint` (first 8 + last 4 characters):
|
||||
```json
|
||||
{"key_hint": "a1b2c3d4...e1f2"}
|
||||
```
|
||||
|
||||
**Q: What happens if I lose my API key?**
|
||||
|
||||
A: Generate a new one with `make generate-key`, update `.env`, restart server, update ChatGPT config. The old key stops working immediately after restart.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [ChatGPT Setup Guide](CHATGPT_SETUP.md) - Configure ChatGPT Business
|
||||
- [Key Rotation Guide](KEY_ROTATION.md) - Automated rotation procedures
|
||||
- [Security Policy](SECURITY.md) - Overall security best practices
|
||||
|
||||
---
|
||||
|
||||
**Need help?** Open an issue in the Gitea repository.
|
||||
419
CHATGPT_SETUP.md
419
CHATGPT_SETUP.md
@@ -1,419 +0,0 @@
|
||||
# ChatGPT Business Setup Guide
|
||||
|
||||
Complete guide for connecting ChatGPT Business to your secured AegisGitea MCP server.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- ✅ AegisGitea MCP server deployed and running
|
||||
- ✅ API key generated (see [AUTH_SETUP.md](AUTH_SETUP.md))
|
||||
- ✅ Traefik configured with HTTPS (or reverse proxy with TLS)
|
||||
- ✅ ChatGPT Business or Developer subscription
|
||||
|
||||
---
|
||||
|
||||
## Setup Steps
|
||||
|
||||
### Step 1: Verify MCP Server is Running
|
||||
|
||||
```bash
|
||||
# Check container status
|
||||
docker-compose ps
|
||||
|
||||
# Should show "Up" status
|
||||
aegis-gitea-mcp Up 0.0.0.0:8080->8080/tcp
|
||||
```
|
||||
|
||||
### Step 2: Test Authentication
|
||||
|
||||
```bash
|
||||
# Replace YOUR_API_KEY with your actual key
|
||||
curl "https://mcp.yourdomain.com/mcp/tools?api_key=YOUR_API_KEY"
|
||||
|
||||
# Expected: JSON response with available tools
|
||||
# If error: Check AUTH_SETUP.md troubleshooting
|
||||
```
|
||||
|
||||
### Step 3: Configure ChatGPT Business
|
||||
|
||||
1. **Open ChatGPT Settings**
|
||||
- Click your profile icon (bottom left)
|
||||
- Select "Settings"
|
||||
- Navigate to "Beta Features" or "Integrations"
|
||||
|
||||
2. **Add MCP Server**
|
||||
- Look for "Model Context Protocol" or "MCP Servers"
|
||||
- Click "Add Server" or "+"
|
||||
|
||||
3. **Enter Server Details**
|
||||
```
|
||||
Name: AegisGitea MCP
|
||||
URL: https://mcp.yourdomain.com?api_key=YOUR_API_KEY_HERE
|
||||
Type: HTTP/SSE (Server-Sent Events)
|
||||
Auth: None (or Mixed, leave OAuth fields empty)
|
||||
```
|
||||
|
||||
4. **Save Configuration**
|
||||
|
||||
### Step 4: Test Connection
|
||||
|
||||
Start a new ChatGPT conversation and try:
|
||||
|
||||
```
|
||||
List my Gitea repositories
|
||||
```
|
||||
|
||||
**Expected Response:**
|
||||
```
|
||||
I found X repositories in your Gitea instance:
|
||||
|
||||
1. org/repo-name - Description here
|
||||
2. org/another-repo - Another description
|
||||
...
|
||||
```
|
||||
|
||||
**If it fails**, see Troubleshooting section below.
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
After setup, verify:
|
||||
|
||||
- [ ] ChatGPT shows "Connected" status for AegisGitea MCP
|
||||
- [ ] Test command "List my Gitea repositories" works
|
||||
- [ ] Audit logs show successful authentication:
|
||||
```bash
|
||||
docker-compose logs aegis-mcp | grep "api_authentication.*success"
|
||||
```
|
||||
- [ ] Can read file contents:
|
||||
```
|
||||
Show me the README.md file from org/repo-name
|
||||
```
|
||||
- [ ] Can browse repository structure:
|
||||
```
|
||||
What files are in the src/ directory of org/repo-name?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Commands
|
||||
|
||||
Once connected, try these commands in ChatGPT:
|
||||
|
||||
### Repository Discovery
|
||||
```
|
||||
What repositories do I have access to?
|
||||
List all my Gitea repositories
|
||||
Show me my private repositories
|
||||
```
|
||||
|
||||
### Repository Information
|
||||
```
|
||||
Tell me about the org/my-repo repository
|
||||
What's the default branch of org/my-repo?
|
||||
When was org/my-repo last updated?
|
||||
```
|
||||
|
||||
### File Operations
|
||||
```
|
||||
Show me the file tree of org/my-repo
|
||||
What files are in the src/ directory of org/my-repo?
|
||||
Read the README.md file from org/my-repo
|
||||
Show me the contents of src/main.py in org/my-repo
|
||||
```
|
||||
|
||||
### Code Understanding
|
||||
```
|
||||
Explain what the main function does in org/my-repo/src/main.py
|
||||
Summarize the architecture of org/my-repo
|
||||
What dependencies does org/my-repo use?
|
||||
Find all TODO comments in org/my-repo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: "Cannot connect to MCP server"
|
||||
|
||||
**Check 1: Server is running**
|
||||
```bash
|
||||
docker-compose ps
|
||||
docker-compose logs aegis-mcp
|
||||
```
|
||||
|
||||
**Check 2: Domain/URL is correct**
|
||||
```bash
|
||||
curl https://mcp.yourdomain.com/health
|
||||
# Should return: {"status": "healthy"}
|
||||
```
|
||||
|
||||
**Check 3: Firewall/Network**
|
||||
- Ensure port 443 is open
|
||||
- Check Traefik is routing correctly
|
||||
- Verify DNS resolves correctly
|
||||
|
||||
### Issue: "Authentication failed"
|
||||
|
||||
**Check 1: MCP Server URL format**
|
||||
|
||||
Correct:
|
||||
```
|
||||
https://mcp.yourdomain.com?api_key=YOUR_KEY
|
||||
```
|
||||
|
||||
Wrong:
|
||||
```
|
||||
https://mcp.yourdomain.com (missing api_key)
|
||||
https://mcp.yourdomain.com?api_key= (empty key)
|
||||
https://mcp.yourdomain.com?api_key=short (too short)
|
||||
```
|
||||
|
||||
**Check 2: Key matches .env**
|
||||
```bash
|
||||
# Show configured keys (first 12 chars only for security)
|
||||
docker-compose exec aegis-mcp printenv MCP_API_KEYS | cut -c1-12
|
||||
```
|
||||
|
||||
Compare with your ChatGPT key (first 12 chars).
|
||||
|
||||
**Check 3: Server logs**
|
||||
```bash
|
||||
docker-compose logs aegis-mcp | tail -50
|
||||
```
|
||||
|
||||
Look for:
|
||||
- `invalid_api_key` → Key doesn't match
|
||||
- `missing_api_key` → api_key not provided
|
||||
- `invalid_key_format` → Key too short
|
||||
|
||||
### Issue: "No repositories visible"
|
||||
|
||||
**Verify bot user has access:**
|
||||
|
||||
1. Log into Gitea
|
||||
2. Go to repository Settings > Collaborators
|
||||
3. Confirm bot user is listed with Read permission
|
||||
4. If not, add bot user as collaborator
|
||||
|
||||
**Test manually:**
|
||||
```bash
|
||||
curl "https://mcp.yourdomain.com/mcp/tool/call?api_key=YOUR_KEY" \
|
||||
-X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"tool": "list_repositories", "arguments": {}}'
|
||||
```
|
||||
|
||||
### Issue: "Rate limited"
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Too many failed authentication attempts
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
1. Wait 5 minutes
|
||||
2. Verify API key is correct
|
||||
3. Check audit logs:
|
||||
```bash
|
||||
docker-compose exec aegis-mcp grep "auth_rate_limit" /var/log/aegis-mcp/audit.log
|
||||
```
|
||||
4. If accidentally locked out:
|
||||
```bash
|
||||
docker-compose restart aegis-mcp
|
||||
```
|
||||
|
||||
### Issue: ChatGPT says "Tool not available"
|
||||
|
||||
**Verify tools are registered:**
|
||||
```bash
|
||||
curl "https://mcp.yourdomain.com/mcp/tools?api_key=YOUR_KEY"
|
||||
```
|
||||
|
||||
Should return JSON with tools array.
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### 1. API Key Management
|
||||
|
||||
- ✅ Store key in password manager
|
||||
- ✅ Never share key in chat or messages
|
||||
- ✅ Rotate key every 90 days
|
||||
- ❌ Don't paste key in public ChatGPT conversations
|
||||
- ❌ Don't screenshot ChatGPT settings with key visible
|
||||
|
||||
### 2. Workspace Separation
|
||||
|
||||
If multiple team members:
|
||||
|
||||
```
|
||||
User A: Use key_a1b2c3...
|
||||
User B: Use key_b4c5d6...
|
||||
User C: Use key_c7d8e9...
|
||||
```
|
||||
|
||||
Configure in `.env`:
|
||||
```bash
|
||||
MCP_API_KEYS=key_a1b2c3...,key_b4c5d6...,key_c7d8e9...
|
||||
```
|
||||
|
||||
Each user configures their own ChatGPT with their specific key.
|
||||
|
||||
### 3. Monitoring Usage
|
||||
|
||||
Weekly check:
|
||||
```bash
|
||||
# Who's using the MCP server?
|
||||
docker-compose exec aegis-mcp grep "api_authentication" /var/log/aegis-mcp/audit.log | \
|
||||
grep "success" | \
|
||||
tail -20
|
||||
```
|
||||
|
||||
Look for `key_hint` to identify which key was used.
|
||||
|
||||
### 4. Incident Response
|
||||
|
||||
If key is compromised:
|
||||
|
||||
1. **Immediate:** Remove from `.env`
|
||||
```bash
|
||||
# Edit .env (remove compromised key)
|
||||
docker-compose restart aegis-mcp
|
||||
```
|
||||
|
||||
2. **Generate new key**
|
||||
```bash
|
||||
make generate-key
|
||||
```
|
||||
|
||||
3. **Update ChatGPT** with new key
|
||||
|
||||
4. **Check audit logs** for unauthorized access:
|
||||
```bash
|
||||
docker-compose exec aegis-mcp grep "key_hint: <compromised_key_hint>" /var/log/aegis-mcp/audit.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Custom MCP Tool Calls (For Developers)
|
||||
|
||||
You can call tools directly via API:
|
||||
|
||||
```bash
|
||||
# List repositories
|
||||
curl -X POST "https://mcp.yourdomain.com/mcp/tool/call?api_key=YOUR_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tool": "list_repositories",
|
||||
"arguments": {}
|
||||
}'
|
||||
|
||||
# Get file contents
|
||||
curl -X POST "https://mcp.yourdomain.com/mcp/tool/call?api_key=YOUR_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tool": "get_file_contents",
|
||||
"arguments": {
|
||||
"owner": "org",
|
||||
"repo": "my-repo",
|
||||
"filepath": "README.md"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### Webhook Integration (Future)
|
||||
|
||||
For automated workflows, you can integrate MCP tools into CI/CD:
|
||||
|
||||
```yaml
|
||||
# Example GitHub Actions (future enhancement)
|
||||
- name: Sync to Gitea via MCP
|
||||
run: |
|
||||
curl -X POST "https://mcp.yourdomain.com/mcp/tool/call?api_key=${{ secrets.MCP_API_KEY }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{...}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Tips
|
||||
|
||||
### 1. Rate Limiting Awareness
|
||||
|
||||
Traefik limits: **60 requests/minute**
|
||||
|
||||
For heavy usage:
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
labels:
|
||||
- "traefik.http.middlewares.aegis-ratelimit.ratelimit.average=120"
|
||||
```
|
||||
|
||||
### 2. Large Files
|
||||
|
||||
Files >1MB are rejected by default. To increase:
|
||||
|
||||
```bash
|
||||
# .env
|
||||
MAX_FILE_SIZE_BYTES=5242880 # 5MB
|
||||
```
|
||||
|
||||
### 3. Batch Operations
|
||||
|
||||
Instead of:
|
||||
```
|
||||
Show me file1.py
|
||||
Show me file2.py
|
||||
Show me file3.py
|
||||
```
|
||||
|
||||
Use:
|
||||
```
|
||||
Show me all Python files in the src/ directory
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Can I use this with Claude or other AI assistants?**
|
||||
|
||||
A: Currently optimized for ChatGPT Business, but the MCP protocol is standard. Other AI assistants with MCP support should work with the same configuration.
|
||||
|
||||
**Q: Do I need a separate key for each ChatGPT conversation?**
|
||||
|
||||
A: No. One key per user/workspace. All conversations in that workspace use the same key.
|
||||
|
||||
**Q: Can ChatGPT modify my repositories?**
|
||||
|
||||
A: **No**. AegisGitea MCP is read-only by design. ChatGPT can only read code, never write/commit/push.
|
||||
|
||||
**Q: What happens if I hit the rate limit?**
|
||||
|
||||
A: You'll receive a 429 error. Wait 1 minute and try again. The limit is per IP, not per key.
|
||||
|
||||
**Q: Can I use this from mobile ChatGPT app?**
|
||||
|
||||
A: Yes, if your ChatGPT Business account syncs to mobile. The MCP configuration follows your account.
|
||||
|
||||
**Q: How do I disconnect ChatGPT?**
|
||||
|
||||
A: Go to ChatGPT Settings > MCP Servers > Remove "AegisGitea MCP"
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Key Rotation Guide](KEY_ROTATION.md) - Rotate keys every 90 days
|
||||
- [Authentication Setup](AUTH_SETUP.md) - Detailed auth configuration
|
||||
- [Security Policy](SECURITY.md) - Best practices and threat model
|
||||
|
||||
---
|
||||
|
||||
**Happy coding with AI-assisted Gitea access!** 🚀
|
||||
343
DEPLOYMENT.md
343
DEPLOYMENT.md
@@ -1,343 +0,0 @@
|
||||
# AegisGitea MCP Deployment Guide
|
||||
|
||||
This guide walks you through deploying AegisGitea MCP in a production environment.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Self-hosted Gitea instance** (running and accessible)
|
||||
2. **Docker and Docker Compose** installed on your server
|
||||
3. **Reverse proxy** (Traefik, Caddy, or Nginx) for TLS termination
|
||||
4. **Bot user account** created in Gitea with read-only access
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Gitea Bot User
|
||||
|
||||
1. Log into your Gitea instance as an admin
|
||||
2. Create a new user account (e.g., `aegis-bot`)
|
||||
3. **Important**: Do NOT grant admin privileges to this user
|
||||
4. Generate an access token:
|
||||
- Go to Settings > Applications
|
||||
- Generate new token with `read:repository` scope only
|
||||
- Save the token securely (you'll need it in Step 3)
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Grant Repository Access
|
||||
|
||||
The bot user can only see repositories where it has been explicitly granted access:
|
||||
|
||||
### Method 1: Add as Collaborator (for individual repos)
|
||||
|
||||
1. Go to repository Settings > Collaborators
|
||||
2. Add `aegis-bot` user
|
||||
3. Set permission to **Read** only
|
||||
|
||||
### Method 2: Add to Organization Team (for multiple repos)
|
||||
|
||||
1. Create an organization team (e.g., "AI Reviewers")
|
||||
2. Add `aegis-bot` to the team
|
||||
3. Grant team **Read** access to desired repositories
|
||||
|
||||
**Result**: Only repositories where the bot is a collaborator are AI-visible.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Configure AegisGitea MCP
|
||||
|
||||
Clone the repository and set up environment:
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://your-gitea.com/your-org/AegisGitea-MCP.git
|
||||
cd AegisGitea-MCP
|
||||
|
||||
# Copy environment template
|
||||
cp .env.example .env
|
||||
|
||||
# Edit configuration
|
||||
nano .env
|
||||
```
|
||||
|
||||
### Required Configuration
|
||||
|
||||
```bash
|
||||
# Gitea instance URL (must be accessible from Docker container)
|
||||
GITEA_URL=https://gitea.example.com
|
||||
|
||||
# Bot user token from Step 1
|
||||
GITEA_TOKEN=your-bot-token-here
|
||||
|
||||
# MCP server configuration
|
||||
MCP_HOST=0.0.0.0
|
||||
MCP_PORT=8080
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=INFO
|
||||
AUDIT_LOG_PATH=/var/log/aegis-mcp/audit.log
|
||||
```
|
||||
|
||||
### Optional Security Configuration
|
||||
|
||||
```bash
|
||||
# File size limit (bytes)
|
||||
MAX_FILE_SIZE_BYTES=1048576 # 1MB
|
||||
|
||||
# API request timeout (seconds)
|
||||
REQUEST_TIMEOUT_SECONDS=30
|
||||
|
||||
# Rate limiting (requests per minute)
|
||||
RATE_LIMIT_PER_MINUTE=60
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Deploy with Docker Compose
|
||||
|
||||
```bash
|
||||
# Build and start container
|
||||
docker-compose up -d
|
||||
|
||||
# Check logs
|
||||
docker-compose logs -f aegis-mcp
|
||||
|
||||
# Verify health
|
||||
curl http://localhost:8080/health
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```json
|
||||
{"status": "healthy"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Configure Reverse Proxy
|
||||
|
||||
**Never expose the MCP server directly to the internet without TLS.**
|
||||
|
||||
### Example: Traefik
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml (add to aegis-mcp service)
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.aegis-mcp.rule=Host(`mcp.example.com`)"
|
||||
- "traefik.http.routers.aegis-mcp.entrypoints=websecure"
|
||||
- "traefik.http.routers.aegis-mcp.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.aegis-mcp.loadbalancer.server.port=8080"
|
||||
```
|
||||
|
||||
### Example: Caddy
|
||||
|
||||
```caddyfile
|
||||
# Caddyfile
|
||||
mcp.example.com {
|
||||
reverse_proxy aegis-mcp:8080
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Nginx
|
||||
|
||||
```nginx
|
||||
# /etc/nginx/sites-available/aegis-mcp
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name mcp.example.com;
|
||||
|
||||
ssl_certificate /path/to/cert.pem;
|
||||
ssl_certificate_key /path/to/key.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# SSE support
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
proxy_set_header Connection '';
|
||||
chunked_transfer_encoding off;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Register with ChatGPT
|
||||
|
||||
1. Go to ChatGPT Settings > MCP Servers
|
||||
2. Add new MCP server:
|
||||
- **Name**: AegisGitea MCP
|
||||
- **URL**: `https://mcp.example.com`
|
||||
- **Type**: SSE (Server-Sent Events)
|
||||
|
||||
3. Test connection by asking ChatGPT:
|
||||
```
|
||||
List my Gitea repositories
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
- [ ] Bot user created in Gitea
|
||||
- [ ] Bot user has read-only token
|
||||
- [ ] Bot user added as collaborator to desired repositories
|
||||
- [ ] `.env` file configured with correct values
|
||||
- [ ] Docker container running and healthy
|
||||
- [ ] Reverse proxy configured with TLS
|
||||
- [ ] MCP server accessible via HTTPS
|
||||
- [ ] ChatGPT successfully connects to MCP server
|
||||
- [ ] Audit logs are being written
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### 1. Token Management
|
||||
|
||||
- **Rotate tokens quarterly** or when team members leave
|
||||
- Store tokens in a secrets manager (Vault, 1Password, etc.)
|
||||
- Never commit tokens to version control
|
||||
|
||||
### 2. Network Security
|
||||
|
||||
- Use a firewall to restrict MCP server access
|
||||
- Only allow HTTPS connections (port 443)
|
||||
- Consider VPN or IP allowlisting for extra security
|
||||
|
||||
### 3. Monitoring
|
||||
|
||||
Monitor audit logs for unexpected activity:
|
||||
|
||||
```bash
|
||||
# View recent audit events
|
||||
docker-compose exec aegis-mcp tail -f /var/log/aegis-mcp/audit.log
|
||||
|
||||
# Search for specific repository access
|
||||
docker-compose exec aegis-mcp grep "repository-name" /var/log/aegis-mcp/audit.log
|
||||
```
|
||||
|
||||
### 4. Access Control
|
||||
|
||||
- Review bot user permissions monthly
|
||||
- Remove access from archived repositories
|
||||
- Audit which repositories are AI-visible
|
||||
|
||||
### 5. Updates
|
||||
|
||||
```bash
|
||||
# Pull latest changes
|
||||
git pull
|
||||
|
||||
# Rebuild container
|
||||
docker-compose down
|
||||
docker-compose build --no-cache
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container won't start
|
||||
|
||||
```bash
|
||||
# Check logs for errors
|
||||
docker-compose logs aegis-mcp
|
||||
|
||||
# Verify environment variables
|
||||
docker-compose config
|
||||
```
|
||||
|
||||
### Authentication errors
|
||||
|
||||
```bash
|
||||
# Test Gitea connection manually
|
||||
curl -H "Authorization: token YOUR_TOKEN" https://gitea.example.com/api/v1/user
|
||||
|
||||
# If 401: Token is invalid or expired
|
||||
# If 403: Token lacks necessary permissions
|
||||
```
|
||||
|
||||
### ChatGPT can't connect
|
||||
|
||||
1. Verify reverse proxy is working:
|
||||
```bash
|
||||
curl https://mcp.example.com/health
|
||||
```
|
||||
|
||||
2. Check firewall rules:
|
||||
```bash
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
3. Review reverse proxy logs
|
||||
|
||||
### No repositories visible
|
||||
|
||||
- Verify bot user has been added as collaborator
|
||||
- Check repository is not archived
|
||||
- Confirm bot user permissions in Gitea UI
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If something goes wrong:
|
||||
|
||||
```bash
|
||||
# Stop container
|
||||
docker-compose down
|
||||
|
||||
# Remove container and volumes
|
||||
docker-compose down -v
|
||||
|
||||
# Restore previous configuration
|
||||
git checkout HEAD~1 .env
|
||||
|
||||
# Restart
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
To completely disable AI access:
|
||||
|
||||
1. Remove bot user token in Gitea
|
||||
2. Stop MCP container: `docker-compose down`
|
||||
|
||||
**The system is designed to be reversible.**
|
||||
|
||||
---
|
||||
|
||||
## Production Checklist
|
||||
|
||||
Before going live:
|
||||
|
||||
- [ ] All sensitive data in `.env` (not hardcoded)
|
||||
- [ ] TLS configured and tested
|
||||
- [ ] Audit logging enabled and accessible
|
||||
- [ ] Resource limits set in docker-compose.yml
|
||||
- [ ] Monitoring and alerting configured
|
||||
- [ ] Backup strategy for audit logs
|
||||
- [ ] Incident response plan documented
|
||||
- [ ] Team trained on emergency procedures
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For deployment issues:
|
||||
|
||||
1. Check logs: `docker-compose logs -f`
|
||||
2. Review audit logs for access patterns
|
||||
3. Open an issue in Gitea repository
|
||||
4. Include sanitized logs (remove tokens!)
|
||||
|
||||
---
|
||||
|
||||
**Remember**: This system prioritizes security over convenience. When in doubt, restrict access first and expand gradually.
|
||||
548
KEY_ROTATION.md
548
KEY_ROTATION.md
@@ -1,548 +0,0 @@
|
||||
# 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! 🔑
|
||||
@@ -1,362 +0,0 @@
|
||||
# AegisGitea MCP - Project Summary
|
||||
|
||||
**Status**: Phase 1 Complete - Foundation Implemented
|
||||
|
||||
---
|
||||
|
||||
## What Was Built
|
||||
|
||||
A complete, production-ready implementation of AegisGitea MCP - a security-first Model Context Protocol server that enables controlled AI access to self-hosted Gitea repositories.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
AegisGitea-MCP/
|
||||
├── src/aegis_gitea_mcp/ # Main application code
|
||||
│ ├── __init__.py # Package initialization
|
||||
│ ├── server.py # FastAPI server with MCP endpoints
|
||||
│ ├── mcp_protocol.py # MCP protocol definitions
|
||||
│ ├── config.py # Configuration management
|
||||
│ ├── audit.py # Audit logging system
|
||||
│ ├── gitea_client.py # Gitea API client
|
||||
│ └── tools/ # MCP tool implementations
|
||||
│ ├── __init__.py
|
||||
│ └── repository.py # Repository access tools
|
||||
│
|
||||
├── tests/ # Test suite
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py # Pytest configuration
|
||||
│ └── test_config.py # Configuration tests
|
||||
│
|
||||
├── docker/ # Docker configuration
|
||||
│ ├── Dockerfile # Multi-stage build
|
||||
│ └── docker-compose.yml # Container orchestration
|
||||
│
|
||||
├── Documentation
|
||||
│ ├── README.md # Main project documentation
|
||||
│ ├── QUICKSTART.md # 5-minute setup guide
|
||||
│ ├── DEPLOYMENT.md # Production deployment guide
|
||||
│ ├── SECURITY.md # Security policy and best practices
|
||||
│ └── PROJECT_SUMMARY.md # This file
|
||||
│
|
||||
├── Configuration
|
||||
│ ├── .env.example # Environment variable template
|
||||
│ ├── .gitignore # Git ignore patterns
|
||||
│ ├── pyproject.toml # Python project configuration
|
||||
│ ├── requirements.txt # Production dependencies
|
||||
│ ├── requirements-dev.txt # Development dependencies
|
||||
│ ├── Makefile # Development commands
|
||||
│ └── docker-compose.yml # Root-level compose file
|
||||
│
|
||||
└── LICENSE # MIT License
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implemented Features
|
||||
|
||||
### Phase 1: Foundation (COMPLETE)
|
||||
|
||||
#### Core Infrastructure
|
||||
- [x] FastAPI-based MCP server
|
||||
- [x] Server-Sent Events (SSE) endpoint for real-time communication
|
||||
- [x] Health check and status endpoints
|
||||
- [x] Structured logging with configurable levels
|
||||
- [x] Environment-based configuration management
|
||||
|
||||
#### Security Features
|
||||
- [x] Bot user authentication via access tokens
|
||||
- [x] Dynamic authorization via Gitea permissions
|
||||
- [x] Comprehensive audit logging (timestamp, tool, repo, target, correlation ID)
|
||||
- [x] File size limits (configurable, default 1MB)
|
||||
- [x] Request timeout protection
|
||||
- [x] Input validation and error handling
|
||||
- [x] Non-root Docker container execution
|
||||
|
||||
#### MCP Tools
|
||||
- [x] `list_repositories` - List all bot-visible repositories
|
||||
- [x] `get_repository_info` - Get repository metadata
|
||||
- [x] `get_file_tree` - Browse repository file structure
|
||||
- [x] `get_file_contents` - Read file contents with size limits
|
||||
|
||||
#### Gitea Integration
|
||||
- [x] Async HTTP client with proper error handling
|
||||
- [x] Bot user authentication and verification
|
||||
- [x] Repository access control enforcement
|
||||
- [x] File content retrieval with encoding handling
|
||||
- [x] Tree/directory listing support
|
||||
|
||||
#### Developer Experience
|
||||
- [x] Docker containerization with multi-stage builds
|
||||
- [x] Docker Compose for easy deployment
|
||||
- [x] Makefile with common development tasks
|
||||
- [x] Pytest test suite with fixtures
|
||||
- [x] Type hints and validation with Pydantic
|
||||
- [x] Code quality tools (black, ruff, mypy)
|
||||
- [x] Comprehensive documentation
|
||||
|
||||
---
|
||||
|
||||
## Technical Stack
|
||||
|
||||
| Component | Technology | Purpose |
|
||||
|-----------|-----------|---------|
|
||||
| **Server** | FastAPI + Uvicorn | Async HTTP server with SSE support |
|
||||
| **HTTP Client** | httpx | Async Gitea API communication |
|
||||
| **Validation** | Pydantic | Type-safe configuration and data models |
|
||||
| **Logging** | structlog | Structured, machine-readable audit logs |
|
||||
| **Containerization** | Docker | Isolated, reproducible deployment |
|
||||
| **Testing** | pytest + pytest-asyncio | Comprehensive test coverage |
|
||||
| **Code Quality** | black, ruff, mypy | Consistent code style and type safety |
|
||||
|
||||
---
|
||||
|
||||
## Architecture Highlights
|
||||
|
||||
### Separation of Concerns
|
||||
|
||||
```
|
||||
ChatGPT ──HTTP/SSE──> MCP Server ──API──> Gitea
|
||||
│
|
||||
├──> Audit Logger (all actions logged)
|
||||
├──> Config Manager (env-based settings)
|
||||
└──> Tool Handlers (bounded operations)
|
||||
```
|
||||
|
||||
### Security Model
|
||||
|
||||
1. **Authorization**: Fully delegated to Gitea (bot user permissions)
|
||||
2. **Authentication**: Token-based, rotatable
|
||||
3. **Auditability**: Every action logged with correlation IDs
|
||||
4. **Safety**: Read-only, bounded operations, fail-safe defaults
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
- **No write operations**: Read-only by design, impossible to modify repositories
|
||||
- **No global search**: All tools require explicit repository targeting
|
||||
- **Dynamic permissions**: Changes in Gitea take effect immediately
|
||||
- **Stateless server**: No session management, fully stateless
|
||||
- **Explicit over implicit**: No hidden or automatic operations
|
||||
|
||||
---
|
||||
|
||||
## What's NOT Implemented (Future Phases)
|
||||
|
||||
### Phase 2: Extended Context (Planned)
|
||||
- Commit history and diff viewing
|
||||
- Issue and pull request access
|
||||
- Branch listing and comparison
|
||||
- Tag and release information
|
||||
|
||||
### Phase 3: Advanced Features (Future)
|
||||
- Rate limiting per client (currently per-server)
|
||||
- Webhook support for real-time updates
|
||||
- Caching layer for performance
|
||||
- Multi-tenant support
|
||||
- OAuth2 flow instead of static tokens
|
||||
|
||||
---
|
||||
|
||||
## Testing Status
|
||||
|
||||
### Implemented Tests
|
||||
- Configuration loading and validation
|
||||
- Environment variable handling
|
||||
- Default value verification
|
||||
- Singleton pattern testing
|
||||
|
||||
### Test Coverage Needed
|
||||
- Gitea client operations (requires mocking)
|
||||
- MCP tool implementations
|
||||
- Audit logging functionality
|
||||
- Server endpoints and SSE
|
||||
|
||||
---
|
||||
|
||||
## Deployment Status
|
||||
|
||||
### Ready for Production
|
||||
- ✅ Docker containerization
|
||||
- ✅ Environment-based configuration
|
||||
- ✅ Health checks and monitoring hooks
|
||||
- ✅ Audit logging enabled
|
||||
- ✅ Security hardening (non-root, resource limits)
|
||||
- ✅ Documentation complete
|
||||
|
||||
### Needs Configuration
|
||||
- ⚠️ Reverse proxy setup (Traefik/Caddy/Nginx)
|
||||
- ⚠️ TLS certificates
|
||||
- ⚠️ Bot user creation in Gitea
|
||||
- ⚠️ Repository access grants
|
||||
- ⚠️ Production environment variables
|
||||
|
||||
---
|
||||
|
||||
## Security Posture
|
||||
|
||||
### Implemented Safeguards
|
||||
- Read-only operations only
|
||||
- Bot user with minimal permissions
|
||||
- Comprehensive audit logging
|
||||
- File size limits
|
||||
- Request timeouts
|
||||
- Input validation
|
||||
- Container security (non-root, no-new-privileges)
|
||||
|
||||
### Recommended Next Steps
|
||||
- Set up log rotation for audit logs
|
||||
- Implement monitoring/alerting on audit logs
|
||||
- Regular token rotation policy
|
||||
- Periodic access reviews
|
||||
- Security training for operators
|
||||
|
||||
---
|
||||
|
||||
## Performance Characteristics
|
||||
|
||||
### Resource Usage (Typical)
|
||||
- **Memory**: ~128-256 MB
|
||||
- **CPU**: Minimal (async I/O bound)
|
||||
- **Disk**: Audit logs grow over time (implement rotation)
|
||||
- **Network**: Depends on file sizes and request frequency
|
||||
|
||||
### Scalability
|
||||
- Stateless design allows horizontal scaling
|
||||
- Async operations handle concurrent requests efficiently
|
||||
- Rate limiting prevents abuse
|
||||
|
||||
---
|
||||
|
||||
## Next Steps for Deployment
|
||||
|
||||
1. **Setup Bot User** (5 min)
|
||||
- Create `aegis-bot` user in Gitea
|
||||
- Generate read-only access token
|
||||
|
||||
2. **Configure Environment** (2 min)
|
||||
- Copy `.env.example` to `.env`
|
||||
- Set `GITEA_URL` and `GITEA_TOKEN`
|
||||
|
||||
3. **Deploy Container** (1 min)
|
||||
- Run `docker-compose up -d`
|
||||
- Verify with `curl http://localhost:8080/health`
|
||||
|
||||
4. **Setup Reverse Proxy** (10-30 min)
|
||||
- Configure Traefik/Caddy/Nginx
|
||||
- Obtain TLS certificates
|
||||
- Test HTTPS access
|
||||
|
||||
5. **Grant Repository Access** (2 min per repo)
|
||||
- Add `aegis-bot` as collaborator
|
||||
- Set Read permission
|
||||
|
||||
6. **Connect ChatGPT** (5 min)
|
||||
- Add MCP server in ChatGPT settings
|
||||
- Test with "List my Gitea repositories"
|
||||
|
||||
**Total time**: ~30-60 minutes for complete setup
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
This implementation successfully meets all Phase 1 objectives:
|
||||
|
||||
- ✅ Secure communication between ChatGPT and Gitea
|
||||
- ✅ Bot user authentication working
|
||||
- ✅ Dynamic authorization via Gitea
|
||||
- ✅ Comprehensive audit logging
|
||||
- ✅ Read-only operations enforced
|
||||
- ✅ Production-ready deployment
|
||||
- ✅ Complete documentation
|
||||
|
||||
---
|
||||
|
||||
## Maintainability
|
||||
|
||||
### Code Quality
|
||||
- Type hints throughout
|
||||
- Docstrings on all public functions
|
||||
- Pydantic models for validation
|
||||
- Structured error handling
|
||||
- Separation of concerns
|
||||
|
||||
### Documentation
|
||||
- Inline code comments where needed
|
||||
- Comprehensive README
|
||||
- Step-by-step deployment guide
|
||||
- Security policy and best practices
|
||||
- Quick start guide
|
||||
|
||||
### Testability
|
||||
- Pytest framework set up
|
||||
- Fixtures for common test scenarios
|
||||
- Configuration reset between tests
|
||||
- Mock-friendly architecture
|
||||
|
||||
---
|
||||
|
||||
## Known Limitations
|
||||
|
||||
1. **Audit Log Size**: Logs grow unbounded (implement rotation)
|
||||
2. **Rate Limiting**: Per-server, not per-client
|
||||
3. **Caching**: No caching layer (every request hits Gitea)
|
||||
4. **Error Messages**: Could be more user-friendly
|
||||
5. **Test Coverage**: Core logic tested, tools need more coverage
|
||||
|
||||
None of these are blockers for production use.
|
||||
|
||||
---
|
||||
|
||||
## Support and Maintenance
|
||||
|
||||
### Regular Maintenance Tasks
|
||||
- **Weekly**: Review audit logs for anomalies
|
||||
- **Monthly**: Review bot user permissions
|
||||
- **Quarterly**: Rotate bot user token
|
||||
- **As needed**: Update Docker images
|
||||
|
||||
### Monitoring Recommendations
|
||||
- Track API response times
|
||||
- Monitor error rates
|
||||
- Alert on authentication failures
|
||||
- Watch audit log size
|
||||
|
||||
---
|
||||
|
||||
## Final Notes
|
||||
|
||||
This project was built with **security, auditability, and simplicity** as the primary goals. The architecture intentionally avoids clever optimizations in favor of straightforward, auditable behavior.
|
||||
|
||||
**It's designed to be boring, predictable, and safe** - exactly what you want in a security-critical system.
|
||||
|
||||
---
|
||||
|
||||
## Questions for Stakeholders
|
||||
|
||||
Before going live, confirm:
|
||||
|
||||
1. **Bot user naming**: Is `aegis-bot` acceptable?
|
||||
2. **Token rotation**: What's the policy (recommend: quarterly)?
|
||||
3. **Audit log retention**: How long to keep logs (recommend: 90 days)?
|
||||
4. **Access approval**: Who approves new repository access?
|
||||
5. **Incident response**: Who responds to security alerts?
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
**AegisGitea MCP is ready for production deployment.**
|
||||
|
||||
All Phase 1 objectives have been met, the system is fully documented, and security best practices have been implemented throughout. The next steps are configuration and deployment-specific rather than development work.
|
||||
|
||||
The foundation is solid, boring, and secure - ready to enable safe AI access to your private Gitea repositories.
|
||||
|
||||
---
|
||||
|
||||
**Project Status**: ✅ Phase 1 Complete - Ready for Deployment
|
||||
|
||||
**Last Updated**: January 29, 2026
|
||||
**Version**: 0.1.0
|
||||
131
QUICKSTART.md
131
QUICKSTART.md
@@ -1,131 +0,0 @@
|
||||
# Quick Start Guide
|
||||
|
||||
Get AegisGitea MCP running in 5 minutes.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker and Docker Compose installed
|
||||
- Self-hosted Gitea instance
|
||||
- 5 minutes of your time
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Bot User (2 minutes)
|
||||
|
||||
1. Log into your Gitea instance
|
||||
2. Create a new user `aegis-bot` (or any name you prefer)
|
||||
3. Go to Settings > Applications
|
||||
4. Generate an access token with **read-only** permissions
|
||||
5. Copy the token
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Clone and Configure (1 minute)
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone <your-repo-url>
|
||||
cd AegisGitea-MCP
|
||||
|
||||
# Configure environment
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
Edit `.env`:
|
||||
```bash
|
||||
GITEA_URL=https://your-gitea-instance.com
|
||||
GITEA_TOKEN=your-bot-token-here
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Start Server (1 minute)
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Verify it's running:
|
||||
```bash
|
||||
# Check logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Test health endpoint
|
||||
curl http://localhost:8080/health
|
||||
```
|
||||
|
||||
Expected response:
|
||||
```json
|
||||
{"status": "healthy"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Grant Repository Access (1 minute)
|
||||
|
||||
1. Go to a repository in Gitea
|
||||
2. Settings > Collaborators
|
||||
3. Add `aegis-bot` user
|
||||
4. Set permission to **Read**
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Connect ChatGPT (Optional)
|
||||
|
||||
If using ChatGPT Business/Developer:
|
||||
|
||||
1. Go to ChatGPT Settings
|
||||
2. Add MCP Server:
|
||||
- **URL**: `http://localhost:8080` (or your domain)
|
||||
- **Type**: HTTP/SSE
|
||||
3. Test by asking: "List my Gitea repositories"
|
||||
|
||||
---
|
||||
|
||||
## What's Next?
|
||||
|
||||
- Read [DEPLOYMENT.md](DEPLOYMENT.md) for production setup
|
||||
- Review [SECURITY.md](SECURITY.md) for security best practices
|
||||
- Check audit logs: `docker-compose exec aegis-mcp cat /var/log/aegis-mcp/audit.log`
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container won't start
|
||||
|
||||
```bash
|
||||
docker-compose logs aegis-mcp
|
||||
```
|
||||
|
||||
Common issues:
|
||||
- Invalid `GITEA_URL` or `GITEA_TOKEN` in `.env`
|
||||
- Port 8080 already in use
|
||||
- Gitea instance not accessible
|
||||
|
||||
### Bot can't see repositories
|
||||
|
||||
1. Verify bot user is added as collaborator
|
||||
2. Check bot user has Read permission
|
||||
3. Confirm repository is not archived
|
||||
|
||||
### ChatGPT can't connect
|
||||
|
||||
- Ensure MCP server is accessible from ChatGPT
|
||||
- Check firewall rules
|
||||
- Verify HTTPS is configured (required for production)
|
||||
|
||||
---
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Check the [README.md](README.md) for detailed documentation
|
||||
- Review logs for error messages
|
||||
- Open an issue in the repository
|
||||
|
||||
---
|
||||
|
||||
**You're all set!** The AI can now securely access your Gitea repositories.
|
||||
322
README.md
322
README.md
@@ -1,322 +0,0 @@
|
||||
# AegisGitea MCP
|
||||
|
||||
**A private, security-first MCP server for controlled AI access to self-hosted Gitea**
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
AegisGitea MCP is a Model Context Protocol (MCP) server that enables controlled, auditable, read-only AI access to a self-hosted Gitea environment.
|
||||
|
||||
The system allows ChatGPT (Business / Developer environment) to inspect repositories, code, commits, issues, and pull requests **only through explicit MCP tool calls**, while all access control is dynamically managed through a dedicated bot user inside Gitea itself.
|
||||
|
||||
### Core Principles
|
||||
|
||||
- **Strong separation of concerns**: Clear boundaries between AI, MCP server, and Gitea
|
||||
- **Least-privilege access**: Bot user has minimal necessary permissions
|
||||
- **Full auditability**: Every AI action is logged with context
|
||||
- **Dynamic authorization**: Access control via Gitea permissions (no redeployment needed)
|
||||
- **Privacy-first**: Designed for homelab and private infrastructure
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ChatGPT (Business/Developer) │
|
||||
│ - Initiates explicit MCP tool calls │
|
||||
│ - Human-in-the-loop decision making │
|
||||
└────────────────────┬────────────────────────────────────────┘
|
||||
│ HTTPS (MCP over SSE)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ AegisGitea MCP Server (Python, Docker) │
|
||||
│ - Implements MCP protocol │
|
||||
│ - Translates tool calls → Gitea API requests │
|
||||
│ - Enforces access, logging, and safety constraints │
|
||||
│ - Provides bounded, single-purpose tools │
|
||||
└────────────────────┬────────────────────────────────────────┘
|
||||
│ Gitea API (Bot User Token)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Gitea Instance (Docker) │
|
||||
│ - Source of truth for authorization │
|
||||
│ - Hosts dedicated read-only bot user │
|
||||
│ - Determines AI-visible repositories dynamically │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Trust Model
|
||||
|
||||
| Component | Responsibility |
|
||||
|-----------|----------------|
|
||||
| **Gitea** | Authorization (what the AI can see) |
|
||||
| **MCP Server** | Policy enforcement (how the AI accesses data) |
|
||||
| **ChatGPT** | Decision initiation (when the AI acts) |
|
||||
| **Human** | Final decision authority (why the AI acts) |
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Phase 1 — Foundation (Current)
|
||||
|
||||
- MCP protocol handling with SSE lifecycle
|
||||
- Secure Gitea API communication via bot user token
|
||||
- Health and readiness endpoints
|
||||
- ChatGPT MCP registration flow
|
||||
|
||||
### Phase 2 — Authorization & Data Access (Planned)
|
||||
|
||||
- Repository discovery based on bot user permissions
|
||||
- File tree and content retrieval with size limits
|
||||
- Dynamic access control (changes in Gitea apply instantly)
|
||||
|
||||
### Phase 3 — Audit & Hardening (Planned)
|
||||
|
||||
- Comprehensive audit logging (timestamp, tool, repo, path, correlation ID)
|
||||
- Request correlation and tracing
|
||||
- Input validation and rate limiting
|
||||
- Defensive bounds on all operations
|
||||
|
||||
### Phase 4 — Extended Context (Future)
|
||||
|
||||
- Commit history and diff inspection
|
||||
- Issue and pull request visibility
|
||||
- Full contextual understanding while maintaining read-only guarantees
|
||||
|
||||
---
|
||||
|
||||
## Authorization Model
|
||||
|
||||
### Bot User Strategy
|
||||
|
||||
A dedicated Gitea bot user represents "the AI":
|
||||
|
||||
- The MCP server authenticates as this user using a read-only token
|
||||
- The bot user's repository permissions define AI visibility
|
||||
- **No admin privileges**
|
||||
- **No write permissions**
|
||||
- **No implicit access**
|
||||
|
||||
This allows dynamic enable/disable of AI access **without restarting or reconfiguring** the MCP server.
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Grant AI access to a repository
|
||||
git clone https://gitea.example.com/org/repo.git
|
||||
cd repo
|
||||
# Add bot user as collaborator with Read permission in Gitea UI
|
||||
|
||||
# Revoke AI access
|
||||
# Remove bot user from repository in Gitea UI
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Tool Design
|
||||
|
||||
All tools are:
|
||||
|
||||
- **Explicit**: Single-purpose, no hidden behavior
|
||||
- **Deterministic**: Same input always produces same output
|
||||
- **Bounded**: Size limits, path constraints, no wildcards
|
||||
- **Auditable**: Full logging of every invocation
|
||||
|
||||
### Tool Categories
|
||||
|
||||
1. **Repository Discovery**
|
||||
- List repositories visible to bot user
|
||||
- Get repository metadata
|
||||
|
||||
2. **File Operations**
|
||||
- Get file tree for a repository
|
||||
- Read file contents (with size limits)
|
||||
|
||||
3. **Commit History** (Phase 4)
|
||||
- List commits for a repository
|
||||
- Get commit details and diffs
|
||||
|
||||
4. **Issues & PRs** (Phase 4)
|
||||
- List issues and pull requests
|
||||
- Read issue/PR details and comments
|
||||
|
||||
### Explicit Constraints
|
||||
|
||||
- No wildcard search tools
|
||||
- No full-text indexing
|
||||
- No recursive "read everything" operations
|
||||
- No hidden or implicit data access
|
||||
|
||||
---
|
||||
|
||||
## Audit & Observability
|
||||
|
||||
Every MCP tool invocation logs:
|
||||
|
||||
- **Timestamp** (UTC)
|
||||
- **Tool name**
|
||||
- **Repository identifier**
|
||||
- **Target** (path / commit / issue)
|
||||
- **Correlation ID**
|
||||
|
||||
Logs are:
|
||||
|
||||
- Append-only
|
||||
- Human-readable JSON
|
||||
- Machine-parseable
|
||||
- Stored locally by default
|
||||
|
||||
**Audit Philosophy**: The system must answer "What exactly did the AI see, and when?" without ambiguity.
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker and Docker Compose
|
||||
- Self-hosted Gitea instance
|
||||
- Gitea bot user with read-only access token
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://gitea.example.com/your-org/AegisGitea-MCP.git
|
||||
cd AegisGitea-MCP
|
||||
|
||||
# Configure environment
|
||||
cp .env.example .env
|
||||
# Edit .env with your Gitea URL and bot token
|
||||
|
||||
# Start MCP server
|
||||
docker-compose up -d
|
||||
|
||||
# Check logs
|
||||
docker-compose logs -f aegis-mcp
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description | Required |
|
||||
|----------|-------------|----------|
|
||||
| `GITEA_URL` | Base URL of Gitea instance | Yes |
|
||||
| `GITEA_TOKEN` | Bot user access token | Yes |
|
||||
| `MCP_HOST` | MCP server listen host | No (default: 0.0.0.0) |
|
||||
| `MCP_PORT` | MCP server listen port | No (default: 8080) |
|
||||
| `LOG_LEVEL` | Logging verbosity | No (default: INFO) |
|
||||
| `AUDIT_LOG_PATH` | Audit log file path | No (default: /var/log/aegis-mcp/audit.log) |
|
||||
|
||||
### Security Considerations
|
||||
|
||||
1. **Never expose the MCP server publicly** — use a reverse proxy with TLS
|
||||
2. **Rotate bot tokens regularly**
|
||||
3. **Monitor audit logs** for unexpected access patterns
|
||||
4. **Keep Docker images updated**
|
||||
5. **Use a dedicated bot user** — never use a personal account token
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
# Create virtual environment
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
# Run tests
|
||||
pytest tests/
|
||||
|
||||
# Run server locally
|
||||
python -m aegis_gitea_mcp.server
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
AegisGitea-MCP/
|
||||
├── src/
|
||||
│ └── aegis_gitea_mcp/
|
||||
│ ├── __init__.py
|
||||
│ ├── server.py # MCP server entry point
|
||||
│ ├── mcp_protocol.py # MCP protocol implementation
|
||||
│ ├── gitea_client.py # Gitea API client
|
||||
│ ├── audit.py # Audit logging
|
||||
│ ├── config.py # Configuration management
|
||||
│ └── tools/ # MCP tool implementations
|
||||
│ ├── __init__.py
|
||||
│ ├── repository.py # Repository discovery tools
|
||||
│ └── files.py # File access tools
|
||||
├── tests/
|
||||
│ ├── test_mcp_protocol.py
|
||||
│ ├── test_gitea_client.py
|
||||
│ └── test_tools.py
|
||||
├── docker/
|
||||
│ ├── Dockerfile
|
||||
│ └── docker-compose.yml
|
||||
├── .env.example
|
||||
├── pyproject.toml
|
||||
├── requirements.txt
|
||||
├── requirements-dev.txt
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Non-Goals
|
||||
|
||||
Explicitly **out of scope**:
|
||||
|
||||
- No write access to Gitea (no commits, comments, merges, edits)
|
||||
- No autonomous or background scanning
|
||||
- No global search or unrestricted crawling
|
||||
- No public exposure of repositories or credentials
|
||||
- No coupling to GitHub or external VCS platforms
|
||||
|
||||
---
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [x] Project initialization and architecture design
|
||||
- [ ] **Phase 1**: MCP server foundation and Gitea integration
|
||||
- [ ] **Phase 2**: Repository discovery and file access tools
|
||||
- [ ] **Phase 3**: Audit logging and security hardening
|
||||
- [ ] **Phase 4**: Commit history, issues, and PR support
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
This project prioritizes security and privacy. Contributions should:
|
||||
|
||||
1. Maintain read-only guarantees
|
||||
2. Add comprehensive audit logging for new tools
|
||||
3. Include tests for authorization and boundary cases
|
||||
4. Document security implications
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See LICENSE file for details
|
||||
|
||||
---
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Built on the [Model Context Protocol](https://modelcontextprotocol.io/) by Anthropic.
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For issues, questions, or security concerns, please open an issue in the Gitea repository.
|
||||
|
||||
**Remember**: This is designed to be **boring, predictable, and safe** — not clever, not magical, and not autonomous.
|
||||
285
SECURITY.md
285
SECURITY.md
@@ -1,285 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 0.1.x | :white_check_mark: |
|
||||
|
||||
---
|
||||
|
||||
## Security Principles
|
||||
|
||||
AegisGitea MCP is designed with security as the primary concern:
|
||||
|
||||
1. **Read-Only by Design**: No write operations to Gitea
|
||||
2. **Least Privilege**: Bot user has minimal necessary permissions
|
||||
3. **Explicit Access**: No implicit or hidden data access
|
||||
4. **Full Auditability**: Every action is logged
|
||||
5. **Fail-Safe**: Errors deny access rather than grant it
|
||||
|
||||
---
|
||||
|
||||
## Threat Model
|
||||
|
||||
### In Scope
|
||||
|
||||
- Unauthorized access to repositories
|
||||
- Token compromise and misuse
|
||||
- Data exfiltration via MCP tools
|
||||
- Denial of service attacks
|
||||
- API abuse and rate limiting bypass
|
||||
|
||||
### Out of Scope
|
||||
|
||||
- Physical access to server
|
||||
- Social engineering attacks
|
||||
- Compromise of Gitea instance itself
|
||||
- ChatGPT platform security
|
||||
|
||||
---
|
||||
|
||||
## Security Features
|
||||
|
||||
### 1. Authorization
|
||||
|
||||
- **Dynamic**: Permissions managed in Gitea, not MCP server
|
||||
- **Explicit**: Bot user must be added to each repository
|
||||
- **Reversible**: Removing bot user immediately revokes access
|
||||
|
||||
### 2. Authentication
|
||||
|
||||
- Token-based authentication with Gitea
|
||||
- No password storage
|
||||
- Tokens should be rotated regularly
|
||||
|
||||
### 3. Audit Logging
|
||||
|
||||
Every tool invocation logs:
|
||||
- Timestamp (UTC)
|
||||
- Tool name
|
||||
- Repository accessed
|
||||
- Target file/path
|
||||
- Request correlation ID
|
||||
- Success/failure status
|
||||
|
||||
Logs are append-only and tamper-evident.
|
||||
|
||||
### 4. Input Validation
|
||||
|
||||
- File size limits enforced
|
||||
- Path traversal protection
|
||||
- Request timeout limits
|
||||
- Rate limiting per minute
|
||||
|
||||
### 5. Container Security
|
||||
|
||||
- Runs as non-root user
|
||||
- No unnecessary privileges
|
||||
- Resource limits enforced
|
||||
- Read-only filesystem where possible
|
||||
|
||||
---
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
Instead:
|
||||
|
||||
1. **Email**: security@youromain.com (replace with your actual contact)
|
||||
2. **Subject**: `[SECURITY] AegisGitea MCP - Brief Description`
|
||||
3. **Include**:
|
||||
- Description of vulnerability
|
||||
- Steps to reproduce
|
||||
- Potential impact
|
||||
- Suggested fix (if any)
|
||||
|
||||
### Response Timeline
|
||||
|
||||
- **24 hours**: Acknowledgment of report
|
||||
- **7 days**: Initial assessment and severity rating
|
||||
- **30 days**: Fix developed and tested
|
||||
- **45 days**: Public disclosure (if applicable)
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### For Operators
|
||||
|
||||
1. **Token Management**
|
||||
- Use dedicated bot user (never personal accounts)
|
||||
- Rotate tokens quarterly
|
||||
- Store tokens in secrets manager
|
||||
- Never commit tokens to version control
|
||||
|
||||
2. **Network Security**
|
||||
- Always use HTTPS with valid TLS certificates
|
||||
- Never expose MCP server directly to internet
|
||||
- Use reverse proxy for TLS termination
|
||||
- Consider VPN or IP allowlisting
|
||||
|
||||
3. **Access Control**
|
||||
- Review bot user permissions monthly
|
||||
- Remove access from archived repositories
|
||||
- Document which repositories are AI-visible
|
||||
- Implement approval workflow for new access
|
||||
|
||||
4. **Monitoring**
|
||||
- Review audit logs weekly
|
||||
- Set up alerts for unusual access patterns
|
||||
- Monitor for failed authentication attempts
|
||||
- Track file access frequency
|
||||
|
||||
5. **Updates**
|
||||
- Keep Docker images updated
|
||||
- Monitor security advisories
|
||||
- Test updates in staging first
|
||||
- Maintain rollback capability
|
||||
|
||||
### For Developers
|
||||
|
||||
1. **Code Review**
|
||||
- All changes require peer review
|
||||
- Security-critical changes require 2+ reviewers
|
||||
- Automated tests must pass
|
||||
|
||||
2. **Dependencies**
|
||||
- Pin dependency versions
|
||||
- Review dependency licenses
|
||||
- Monitor for security advisories
|
||||
- Use tools like `pip-audit` or `safety`
|
||||
|
||||
3. **Testing**
|
||||
- Write tests for authorization logic
|
||||
- Test boundary conditions
|
||||
- Include negative test cases
|
||||
- Fuzz test inputs
|
||||
|
||||
---
|
||||
|
||||
## Known Limitations
|
||||
|
||||
1. **Trust in Gitea**: Authorization depends on Gitea's access control
|
||||
2. **Token Security**: Compromised token = compromised access until rotated
|
||||
3. **Rate Limiting**: Current implementation is per-server, not per-client
|
||||
4. **Audit Log Size**: Logs grow unbounded (implement rotation)
|
||||
|
||||
---
|
||||
|
||||
## Security Checklist
|
||||
|
||||
Before deploying to production:
|
||||
|
||||
- [ ] Bot user created with minimal permissions
|
||||
- [ ] Access token generated with read-only scope
|
||||
- [ ] TLS configured with valid certificate
|
||||
- [ ] Reverse proxy properly configured
|
||||
- [ ] Audit logging enabled and tested
|
||||
- [ ] Resource limits set in Docker
|
||||
- [ ] Firewall rules configured
|
||||
- [ ] Monitoring and alerting set up
|
||||
- [ ] Incident response plan documented
|
||||
- [ ] Team trained on security procedures
|
||||
|
||||
---
|
||||
|
||||
## Incident Response
|
||||
|
||||
If you suspect a security breach:
|
||||
|
||||
### Immediate Actions (within 5 minutes)
|
||||
|
||||
1. **Isolate**: Stop the MCP container
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
2. **Revoke**: Delete bot user token in Gitea
|
||||
- Go to Gitea > Settings > Applications
|
||||
- Delete the token immediately
|
||||
|
||||
3. **Preserve**: Save audit logs for analysis
|
||||
```bash
|
||||
docker cp aegis-gitea-mcp:/var/log/aegis-mcp/audit.log ./incident-$(date +%Y%m%d-%H%M%S).log
|
||||
```
|
||||
|
||||
### Investigation (within 1 hour)
|
||||
|
||||
1. Review audit logs for unauthorized access
|
||||
2. Check which repositories were accessed
|
||||
3. Identify timeframe of suspicious activity
|
||||
4. Document findings
|
||||
|
||||
### Remediation (within 24 hours)
|
||||
|
||||
1. Generate new bot user token
|
||||
2. Review and update bot user permissions
|
||||
3. Deploy updated configuration
|
||||
4. Monitor for continued suspicious activity
|
||||
5. Notify affected repository owners if necessary
|
||||
|
||||
### Post-Incident (within 1 week)
|
||||
|
||||
1. Conduct post-mortem analysis
|
||||
2. Update security procedures
|
||||
3. Implement additional safeguards
|
||||
4. Document lessons learned
|
||||
5. Train team on new procedures
|
||||
|
||||
---
|
||||
|
||||
## Compliance Considerations
|
||||
|
||||
### GDPR
|
||||
|
||||
- Audit logs may contain personal data (usernames, timestamps)
|
||||
- Implement log retention policy (recommend 90 days)
|
||||
- Provide mechanism for data deletion requests
|
||||
|
||||
### SOC 2
|
||||
|
||||
- Audit logging satisfies monitoring requirements
|
||||
- Access control model supports least privilege
|
||||
- Incident response procedures documented
|
||||
|
||||
### Internal Policies
|
||||
|
||||
- Adapt security practices to your organization's policies
|
||||
- Document any deviations from standard procedures
|
||||
- Obtain necessary approvals before deployment
|
||||
|
||||
---
|
||||
|
||||
## Security Roadmap
|
||||
|
||||
Future security enhancements (not yet implemented):
|
||||
|
||||
- [ ] Multi-factor authentication for bot token generation
|
||||
- [ ] Per-client rate limiting (not just per-server)
|
||||
- [ ] Automated audit log analysis and anomaly detection
|
||||
- [ ] Integration with SIEM systems
|
||||
- [ ] Encrypted audit logs
|
||||
- [ ] Support for multiple bot users with different permissions
|
||||
- [ ] OAuth2 flow instead of static tokens
|
||||
- [ ] Content scanning for sensitive data patterns
|
||||
|
||||
---
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Security vulnerabilities responsibly disclosed by:
|
||||
|
||||
- (None yet - be the first!)
|
||||
|
||||
---
|
||||
|
||||
## Contact
|
||||
|
||||
Security Team: security@yourdomain.com
|
||||
General Support: issues in Gitea repository
|
||||
|
||||
---
|
||||
|
||||
**Remember**: Security is a process, not a product. Stay vigilant.
|
||||
441
TESTING.md
441
TESTING.md
@@ -1,441 +0,0 @@
|
||||
# Testing Guide
|
||||
|
||||
Comprehensive guide for testing AegisGitea MCP.
|
||||
|
||||
---
|
||||
|
||||
## Test Suite Overview
|
||||
|
||||
The project includes a complete test suite covering:
|
||||
|
||||
1. **Unit Tests** - Individual components (auth, config)
|
||||
2. **Integration Tests** - Component interactions
|
||||
3. **Server Tests** - API endpoints and middleware
|
||||
4. **Manual Tests** - Real-world scenarios
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Run All Tests
|
||||
|
||||
```bash
|
||||
# Using test script (recommended)
|
||||
./run_tests.sh
|
||||
|
||||
# Or using Make
|
||||
make test
|
||||
|
||||
# Or directly with pytest
|
||||
pytest tests/ -v
|
||||
```
|
||||
|
||||
### Run Specific Test Files
|
||||
|
||||
```bash
|
||||
# Authentication tests only
|
||||
pytest tests/test_auth.py -v
|
||||
|
||||
# Server endpoint tests only
|
||||
pytest tests/test_server.py -v
|
||||
|
||||
# Integration tests only
|
||||
pytest tests/test_integration.py -v
|
||||
```
|
||||
|
||||
### Run with Coverage
|
||||
|
||||
```bash
|
||||
pytest tests/ --cov=aegis_gitea_mcp --cov-report=html
|
||||
|
||||
# View coverage report
|
||||
open htmlcov/index.html # macOS
|
||||
xdg-open htmlcov/index.html # Linux
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test Modules
|
||||
|
||||
### 1. `test_auth.py` - Authentication Module Tests
|
||||
|
||||
**Coverage:**
|
||||
- API key generation (length, format)
|
||||
- Key hashing (SHA256)
|
||||
- Bearer token extraction
|
||||
- Constant-time comparison
|
||||
- Rate limiting (per IP)
|
||||
- Multiple keys support
|
||||
- Auth enable/disable
|
||||
|
||||
**Key Tests:**
|
||||
```python
|
||||
test_generate_api_key() # 64-char hex key generation
|
||||
test_validate_api_key_valid() # Valid key acceptance
|
||||
test_validate_api_key_invalid() # Invalid key rejection
|
||||
test_rate_limiting() # 5 attempts/5min limit
|
||||
test_multiple_keys() # Comma-separated keys
|
||||
```
|
||||
|
||||
**Run:**
|
||||
```bash
|
||||
pytest tests/test_auth.py -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. `test_server.py` - Server Endpoint Tests
|
||||
|
||||
**Coverage:**
|
||||
- Middleware authentication
|
||||
- Protected vs public endpoints
|
||||
- Authorization header formats
|
||||
- Rate limiting at HTTP level
|
||||
- Error messages
|
||||
|
||||
**Key Tests:**
|
||||
```python
|
||||
test_health_endpoint_no_auth_required() # /health is public
|
||||
test_list_tools_without_auth() # /mcp/tools requires auth
|
||||
test_list_tools_with_valid_key() # Valid key works
|
||||
test_auth_header_formats() # Bearer format validation
|
||||
test_rate_limiting() # HTTP-level rate limit
|
||||
```
|
||||
|
||||
**Run:**
|
||||
```bash
|
||||
pytest tests/test_server.py -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. `test_integration.py` - Integration Tests
|
||||
|
||||
**Coverage:**
|
||||
- Complete authentication flow
|
||||
- Key rotation simulation
|
||||
- Multi-tool discovery
|
||||
- Concurrent requests
|
||||
- Error handling
|
||||
|
||||
**Key Tests:**
|
||||
```python
|
||||
test_complete_authentication_flow() # End-to-end flow
|
||||
test_key_rotation_simulation() # Grace period handling
|
||||
test_all_mcp_tools_discoverable() # Tool registration
|
||||
test_concurrent_requests_different_ips() # Per-IP rate limiting
|
||||
```
|
||||
|
||||
**Run:**
|
||||
```bash
|
||||
pytest tests/test_integration.py -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. `test_config.py` - Configuration Tests
|
||||
|
||||
**Coverage:**
|
||||
- Environment variable loading
|
||||
- Default values
|
||||
- Validation rules
|
||||
- Required fields
|
||||
|
||||
**Key Tests:**
|
||||
```python
|
||||
test_settings_from_env() # Env var parsing
|
||||
test_settings_defaults() # Default values
|
||||
test_settings_invalid_log_level() # Validation
|
||||
```
|
||||
|
||||
**Run:**
|
||||
```bash
|
||||
pytest tests/test_config.py -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manual Testing
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```bash
|
||||
# 1. Generate API key
|
||||
make generate-key
|
||||
|
||||
# 2. Add to .env
|
||||
echo "MCP_API_KEYS=your-key-here" >> .env
|
||||
|
||||
# 3. Build and start
|
||||
docker-compose build
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Test 1: Health Check (No Auth)
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/health
|
||||
|
||||
# Expected: {"status": "healthy"}
|
||||
```
|
||||
|
||||
### Test 2: Protected Endpoint Without Auth
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/mcp/tools
|
||||
|
||||
# Expected: 401 Unauthorized
|
||||
```
|
||||
|
||||
### Test 3: Protected Endpoint With Invalid Key
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer invalid-key-1234567890123456789012345678901234" \
|
||||
http://localhost:8080/mcp/tools
|
||||
|
||||
# Expected: 401 Unauthorized with "Invalid API key" message
|
||||
```
|
||||
|
||||
### Test 4: Protected Endpoint With Valid Key
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
|
||||
http://localhost:8080/mcp/tools
|
||||
|
||||
# Expected: 200 OK with JSON list of tools
|
||||
```
|
||||
|
||||
### Test 5: Rate Limiting
|
||||
|
||||
```bash
|
||||
# Run this script to trigger rate limit:
|
||||
for i in {1..6}; do
|
||||
curl -H "Authorization: Bearer wrong-key-12345678901234567890123456789012345" \
|
||||
http://localhost:8080/mcp/tools
|
||||
echo ""
|
||||
done
|
||||
|
||||
# 6th request should return "Too many failed authentication attempts"
|
||||
```
|
||||
|
||||
### Test 6: Key Rotation
|
||||
|
||||
```bash
|
||||
# 1. Add second key to .env
|
||||
MCP_API_KEYS=old-key,new-key
|
||||
|
||||
# 2. Restart
|
||||
docker-compose restart aegis-mcp
|
||||
|
||||
# 3. Test both keys work
|
||||
curl -H "Authorization: Bearer old-key" http://localhost:8080/mcp/tools
|
||||
curl -H "Authorization: Bearer new-key" http://localhost:8080/mcp/tools
|
||||
|
||||
# 4. Remove old key
|
||||
MCP_API_KEYS=new-key
|
||||
|
||||
# 5. Restart and verify only new key works
|
||||
docker-compose restart aegis-mcp
|
||||
curl -H "Authorization: Bearer old-key" http://localhost:8080/mcp/tools # Should fail
|
||||
curl -H "Authorization: Bearer new-key" http://localhost:8080/mcp/tools # Should work
|
||||
```
|
||||
|
||||
### Test 7: ChatGPT Integration
|
||||
|
||||
1. Configure ChatGPT Business with your API key
|
||||
2. Ask: "List my Gitea repositories"
|
||||
3. Verify response contains repository list
|
||||
4. Check audit logs:
|
||||
```bash
|
||||
docker-compose exec aegis-mcp cat /var/log/aegis-mcp/audit.log | grep "api_authentication"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test Data
|
||||
|
||||
### Valid API Keys (for testing)
|
||||
|
||||
```python
|
||||
# 64-character keys
|
||||
KEY_A = "a" * 64
|
||||
KEY_B = "b" * 64
|
||||
KEY_C = "c" * 64
|
||||
|
||||
# Invalid keys (too short)
|
||||
INVALID_SHORT = "short"
|
||||
INVALID_32 = "x" * 31 # Less than 32 chars
|
||||
```
|
||||
|
||||
### Test Environment Variables
|
||||
|
||||
```bash
|
||||
# Minimal test environment
|
||||
GITEA_URL=https://gitea.example.com
|
||||
GITEA_TOKEN=test-token-12345
|
||||
AUTH_ENABLED=true
|
||||
MCP_API_KEYS=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
|
||||
# Auth disabled for testing
|
||||
AUTH_ENABLED=false
|
||||
MCP_API_KEYS= # Can be empty when disabled
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions Example
|
||||
|
||||
```yaml
|
||||
name: Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
pytest tests/ -v --cov=aegis_gitea_mcp
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Testing
|
||||
|
||||
### Load Test Example
|
||||
|
||||
```bash
|
||||
# Install Apache Bench
|
||||
sudo apt-get install apache2-utils
|
||||
|
||||
# Test authenticated endpoint (100 requests, 10 concurrent)
|
||||
ab -n 100 -c 10 \
|
||||
-H "Authorization: Bearer YOUR_KEY" \
|
||||
http://localhost:8080/mcp/tools
|
||||
|
||||
# Check results:
|
||||
# - Requests per second
|
||||
# - Mean time per request
|
||||
# - Longest request
|
||||
```
|
||||
|
||||
### Expected Performance
|
||||
|
||||
- **Health check**: < 10ms per request
|
||||
- **List tools** (authenticated): < 50ms per request
|
||||
- **Rate limit enforcement**: < 5ms overhead
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting Tests
|
||||
|
||||
### Issue: Import errors
|
||||
|
||||
```bash
|
||||
# Solution: Install dev dependencies
|
||||
pip install -r requirements-dev.txt
|
||||
```
|
||||
|
||||
### Issue: "No module named 'aegis_gitea_mcp'"
|
||||
|
||||
```bash
|
||||
# Solution: Set PYTHONPATH
|
||||
export PYTHONPATH="${PYTHONPATH}:$(pwd)/src"
|
||||
|
||||
# Or run from project root
|
||||
cd /path/to/AegisGitea-MCP
|
||||
pytest tests/
|
||||
```
|
||||
|
||||
### Issue: Tests pass locally but fail in Docker
|
||||
|
||||
```bash
|
||||
# Solution: Run tests inside Docker
|
||||
docker-compose exec aegis-mcp pytest /app/tests/
|
||||
```
|
||||
|
||||
### Issue: Rate limiting tests interfere with each other
|
||||
|
||||
```bash
|
||||
# Solution: Run tests with fresh validator each time
|
||||
pytest tests/test_auth.py::test_rate_limiting -v --tb=short
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Code Coverage Goals
|
||||
|
||||
| Module | Target Coverage | Current |
|
||||
|--------|----------------|---------|
|
||||
| `auth.py` | > 90% | ✅ |
|
||||
| `config.py` | > 85% | ✅ |
|
||||
| `server.py` | > 80% | ✅ |
|
||||
| `mcp_protocol.py` | > 90% | ✅ |
|
||||
| `gitea_client.py` | > 70% | ⏳ Requires mocking |
|
||||
| `audit.py` | > 80% | ⏳ Requires file I/O |
|
||||
|
||||
---
|
||||
|
||||
## Test Maintenance
|
||||
|
||||
### Adding New Tests
|
||||
|
||||
1. Create test file: `tests/test_<module>.py`
|
||||
2. Follow existing patterns:
|
||||
```python
|
||||
def test_descriptive_name():
|
||||
"""Clear description of what's being tested."""
|
||||
# Arrange
|
||||
# Act
|
||||
# Assert
|
||||
```
|
||||
3. Run tests to ensure they pass
|
||||
4. Update this doc with new test coverage
|
||||
|
||||
### Updating Tests
|
||||
|
||||
- Run full suite after changes: `./run_tests.sh`
|
||||
- Check coverage: `pytest --cov`
|
||||
- Update test data if needed
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Isolation**: Each test should be independent
|
||||
2. **Fixtures**: Use pytest fixtures for setup/teardown
|
||||
3. **Naming**: `test_<what>_<scenario>_<expected>`
|
||||
4. **Assertions**: One logical assertion per test
|
||||
5. **Documentation**: Clear docstrings explaining purpose
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [ ] Add tests for Gitea client (with mocking)
|
||||
- [ ] Add tests for audit logging (with temp files)
|
||||
- [ ] Add performance benchmarks
|
||||
- [ ] Add end-to-end tests with real Gitea instance
|
||||
- [ ] Set up continuous testing in CI/CD
|
||||
|
||||
---
|
||||
|
||||
**Test coverage is currently ~85% for core authentication and server modules. All critical paths are covered.**
|
||||
@@ -1,10 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check API key age and alert if rotation needed."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@@ -46,7 +45,7 @@ def main() -> None:
|
||||
critical = []
|
||||
|
||||
for metadata_file in sorted(metadata_files):
|
||||
with open(metadata_file, "r") as f:
|
||||
with open(metadata_file, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Extract metadata
|
||||
|
||||
@@ -87,7 +87,7 @@ def main() -> None:
|
||||
key_id = api_key[:12]
|
||||
metadata_file = keys_dir / f"key-{key_id}-{created_at.strftime('%Y%m%d')}.txt"
|
||||
|
||||
with open(metadata_file, "w") as f:
|
||||
with open(metadata_file, "w", encoding="utf-8") as f:
|
||||
f.write(f"API Key Metadata\n")
|
||||
f.write(f"================\n\n")
|
||||
f.write(f"Key ID: {key_id}...\n")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rotate API key for AegisGitea MCP."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
@@ -31,7 +30,7 @@ def main() -> None:
|
||||
sys.exit(1)
|
||||
|
||||
# Read current .env
|
||||
with open(env_file, "r") as f:
|
||||
with open(env_file, "r", encoding="utf-8") as f:
|
||||
env_content = f.read()
|
||||
|
||||
# Check if MCP_API_KEYS exists
|
||||
@@ -115,13 +114,13 @@ def main() -> None:
|
||||
|
||||
# Backup old .env
|
||||
backup_file = env_file.with_suffix(f".env.backup-{datetime.now().strftime('%Y%m%d-%H%M%S')}")
|
||||
with open(backup_file, "w") as f:
|
||||
with open(backup_file, "w", encoding="utf-8") as f:
|
||||
f.write(env_content)
|
||||
|
||||
print(f"✓ Backed up old .env to: {backup_file.name}")
|
||||
|
||||
# Write new .env
|
||||
with open(env_file, "w") as f:
|
||||
with open(env_file, "w", encoding="utf-8") as f:
|
||||
f.write(new_env_content)
|
||||
|
||||
print(f"✓ Updated .env file with new key(s)")
|
||||
|
||||
@@ -25,6 +25,7 @@ class AuditLogger:
|
||||
|
||||
# Ensure log directory exists
|
||||
self.log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self._log_file = self._get_log_file()
|
||||
|
||||
# Configure structlog for audit logging
|
||||
structlog.configure(
|
||||
@@ -35,7 +36,7 @@ class AuditLogger:
|
||||
],
|
||||
wrapper_class=structlog.BoundLogger,
|
||||
context_class=dict,
|
||||
logger_factory=structlog.PrintLoggerFactory(file=self._get_log_file()),
|
||||
logger_factory=structlog.PrintLoggerFactory(file=self._log_file),
|
||||
cache_logger_on_first_use=True,
|
||||
)
|
||||
|
||||
@@ -45,6 +46,13 @@ class AuditLogger:
|
||||
"""Get file handle for audit log."""
|
||||
return open(self.log_path, "a", encoding="utf-8")
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close open audit log resources."""
|
||||
try:
|
||||
self._log_file.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def log_tool_invocation(
|
||||
self,
|
||||
tool_name: str,
|
||||
@@ -166,4 +174,6 @@ def get_audit_logger() -> AuditLogger:
|
||||
def reset_audit_logger() -> None:
|
||||
"""Reset global audit logger instance (primarily for testing)."""
|
||||
global _audit_logger
|
||||
if _audit_logger is not None:
|
||||
_audit_logger.close()
|
||||
_audit_logger = None
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Configuration management for AegisGitea MCP server."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field, HttpUrl, field_validator, model_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
@@ -112,20 +112,20 @@ class Settings(BaseSettings):
|
||||
def validate_and_parse_api_keys(self) -> "Settings":
|
||||
"""Parse and validate API keys if authentication is enabled."""
|
||||
# Parse comma-separated keys into list
|
||||
keys = []
|
||||
keys: list[str] = []
|
||||
if self.mcp_api_keys_raw and self.mcp_api_keys_raw.strip():
|
||||
keys = [key.strip() for key in self.mcp_api_keys_raw.split(",") if key.strip()]
|
||||
|
||||
|
||||
# Store in a property we'll access
|
||||
object.__setattr__(self, "_mcp_api_keys", keys)
|
||||
|
||||
|
||||
# Validate if auth is enabled
|
||||
if self.auth_enabled and not keys:
|
||||
raise ValueError(
|
||||
"At least one API key must be configured when auth_enabled=True. "
|
||||
"Set MCP_API_KEYS environment variable or disable auth with AUTH_ENABLED=false"
|
||||
)
|
||||
|
||||
|
||||
# Validate key format (at least 32 characters for security)
|
||||
for key in keys:
|
||||
if len(key) < 32:
|
||||
@@ -133,11 +133,11 @@ class Settings(BaseSettings):
|
||||
f"API keys must be at least 32 characters long. "
|
||||
f"Use scripts/generate_api_key.py to generate secure keys."
|
||||
)
|
||||
|
||||
|
||||
return self
|
||||
|
||||
|
||||
@property
|
||||
def mcp_api_keys(self) -> List[str]:
|
||||
def mcp_api_keys(self) -> list[str]:
|
||||
"""Get parsed list of API keys."""
|
||||
return getattr(self, "_mcp_api_keys", [])
|
||||
|
||||
@@ -155,7 +155,7 @@ def get_settings() -> Settings:
|
||||
"""Get or create global settings instance."""
|
||||
global _settings
|
||||
if _settings is None:
|
||||
_settings = Settings() # type: ignore
|
||||
_settings = Settings()
|
||||
return _settings
|
||||
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ class GiteaClient:
|
||||
if self.client:
|
||||
await self.client.aclose()
|
||||
|
||||
def _handle_response(self, response: Response, correlation_id: str) -> Dict[str, Any]:
|
||||
def _handle_response(self, response: Response, correlation_id: str) -> Any:
|
||||
"""Handle Gitea API response and raise appropriate exceptions.
|
||||
|
||||
Args:
|
||||
@@ -148,12 +148,12 @@ class GiteaClient:
|
||||
|
||||
return user_data
|
||||
|
||||
except Exception as e:
|
||||
except Exception as exc:
|
||||
self.audit.log_tool_invocation(
|
||||
tool_name="get_current_user",
|
||||
correlation_id=correlation_id,
|
||||
result_status="error",
|
||||
error=str(e),
|
||||
error=str(exc),
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -177,7 +177,7 @@ class GiteaClient:
|
||||
try:
|
||||
response = await self.client.get("/api/v1/user/repos")
|
||||
repos_data = self._handle_response(response, correlation_id)
|
||||
|
||||
|
||||
# Ensure we have a list
|
||||
repos = repos_data if isinstance(repos_data, list) else []
|
||||
|
||||
@@ -190,12 +190,12 @@ class GiteaClient:
|
||||
|
||||
return repos
|
||||
|
||||
except Exception as e:
|
||||
except Exception as exc:
|
||||
self.audit.log_tool_invocation(
|
||||
tool_name="list_repositories",
|
||||
correlation_id=correlation_id,
|
||||
result_status="error",
|
||||
error=str(e),
|
||||
error=str(exc),
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -236,13 +236,13 @@ class GiteaClient:
|
||||
|
||||
return repo_data
|
||||
|
||||
except Exception as e:
|
||||
except Exception as exc:
|
||||
self.audit.log_tool_invocation(
|
||||
tool_name="get_repository",
|
||||
repository=repo_id,
|
||||
correlation_id=correlation_id,
|
||||
result_status="error",
|
||||
error=str(e),
|
||||
error=str(exc),
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -314,14 +314,14 @@ class GiteaClient:
|
||||
|
||||
return file_data
|
||||
|
||||
except Exception as e:
|
||||
except Exception as exc:
|
||||
self.audit.log_tool_invocation(
|
||||
tool_name="get_file_contents",
|
||||
repository=repo_id,
|
||||
target=filepath,
|
||||
correlation_id=correlation_id,
|
||||
result_status="error",
|
||||
error=str(e),
|
||||
error=str(exc),
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -370,12 +370,12 @@ class GiteaClient:
|
||||
|
||||
return tree_data
|
||||
|
||||
except Exception as e:
|
||||
except Exception as exc:
|
||||
self.audit.log_tool_invocation(
|
||||
tool_name="get_tree",
|
||||
repository=repo_id,
|
||||
correlation_id=correlation_id,
|
||||
result_status="error",
|
||||
error=str(e),
|
||||
error=str(exc),
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class MCPTool(BaseModel):
|
||||
@@ -14,9 +14,10 @@ class MCPTool(BaseModel):
|
||||
..., alias="inputSchema", description="JSON Schema for tool input"
|
||||
)
|
||||
|
||||
class Config:
|
||||
populate_by_name = True
|
||||
by_alias = True
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
serialize_by_alias=True,
|
||||
)
|
||||
|
||||
|
||||
class MCPToolCallRequest(BaseModel):
|
||||
|
||||
@@ -40,9 +40,7 @@ app = FastAPI(
|
||||
)
|
||||
|
||||
# Global settings and audit logger
|
||||
# Note: auth_validator is fetched dynamically in middleware to support test resets
|
||||
settings = get_settings()
|
||||
audit = get_audit_logger()
|
||||
# Note: access settings/audit logger dynamically to support test resets.
|
||||
|
||||
|
||||
# Tool dispatcher mapping
|
||||
@@ -115,6 +113,7 @@ async def authenticate_request(request: Request, call_next):
|
||||
@app.on_event("startup")
|
||||
async def startup_event() -> None:
|
||||
"""Initialize server on startup."""
|
||||
settings = get_settings()
|
||||
logger.info(f"Starting AegisGitea MCP Server on {settings.mcp_host}:{settings.mcp_port}")
|
||||
logger.info(f"Connected to Gitea instance: {settings.gitea_base_url}")
|
||||
logger.info(f"Audit logging enabled: {settings.audit_log_path}")
|
||||
@@ -180,6 +179,7 @@ async def call_tool(request: MCPToolCallRequest) -> JSONResponse:
|
||||
Returns:
|
||||
JSON response with tool execution result
|
||||
"""
|
||||
audit = get_audit_logger()
|
||||
correlation_id = request.correlation_id or audit.log_tool_invocation(
|
||||
tool_name=request.tool,
|
||||
params=request.arguments,
|
||||
@@ -312,6 +312,7 @@ async def sse_message_handler(request: Request) -> JSONResponse:
|
||||
JSON response acknowledging the message
|
||||
"""
|
||||
try:
|
||||
audit = get_audit_logger()
|
||||
body = await request.json()
|
||||
logger.info(f"Received MCP message via SSE POST: {body}")
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@ async def list_repositories_tool(gitea: GiteaClient, arguments: Dict[str, Any])
|
||||
"count": len(simplified_repos),
|
||||
}
|
||||
|
||||
except GiteaError as e:
|
||||
raise Exception(f"Failed to list repositories: {str(e)}")
|
||||
except GiteaError as exc:
|
||||
raise RuntimeError(f"Failed to list repositories: {exc}") from exc
|
||||
|
||||
|
||||
async def get_repository_info_tool(
|
||||
@@ -84,8 +84,8 @@ async def get_repository_info_tool(
|
||||
"clone_url": repo_data.get("clone_url", ""),
|
||||
}
|
||||
|
||||
except GiteaError as e:
|
||||
raise Exception(f"Failed to get repository info: {str(e)}")
|
||||
except GiteaError as exc:
|
||||
raise RuntimeError(f"Failed to get repository info: {exc}") from exc
|
||||
|
||||
|
||||
async def get_file_tree_tool(gitea: GiteaClient, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@@ -129,8 +129,8 @@ async def get_file_tree_tool(gitea: GiteaClient, arguments: Dict[str, Any]) -> D
|
||||
"count": len(simplified_tree),
|
||||
}
|
||||
|
||||
except GiteaError as e:
|
||||
raise Exception(f"Failed to get file tree: {str(e)}")
|
||||
except GiteaError as exc:
|
||||
raise RuntimeError(f"Failed to get file tree: {exc}") from exc
|
||||
|
||||
|
||||
async def get_file_contents_tool(gitea: GiteaClient, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@@ -185,5 +185,5 @@ async def get_file_contents_tool(gitea: GiteaClient, arguments: Dict[str, Any])
|
||||
"url": file_data.get("html_url", ""),
|
||||
}
|
||||
|
||||
except GiteaError as e:
|
||||
raise Exception(f"Failed to get file contents: {str(e)}")
|
||||
except GiteaError as exc:
|
||||
raise RuntimeError(f"Failed to get file contents: {exc}") from exc
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Pytest configuration and fixtures."""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Generator
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import pytest
|
||||
from aegis_gitea_mcp.auth import (
|
||||
APIKeyValidator,
|
||||
generate_api_key,
|
||||
hash_api_key,
|
||||
get_validator,
|
||||
hash_api_key,
|
||||
reset_validator,
|
||||
)
|
||||
from aegis_gitea_mcp.config import reset_settings
|
||||
@@ -40,7 +40,7 @@ def validator(mock_env_with_key):
|
||||
def test_generate_api_key():
|
||||
"""Test API key generation."""
|
||||
key = generate_api_key(length=64)
|
||||
|
||||
|
||||
assert len(key) == 64
|
||||
assert all(c in "0123456789abcdef" for c in key)
|
||||
|
||||
@@ -48,7 +48,7 @@ def test_generate_api_key():
|
||||
def test_generate_api_key_custom_length():
|
||||
"""Test API key generation with custom length."""
|
||||
key = generate_api_key(length=128)
|
||||
|
||||
|
||||
assert len(key) == 128
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ def test_hash_api_key():
|
||||
"""Test API key hashing."""
|
||||
key = "test-key-12345"
|
||||
hashed = hash_api_key(key)
|
||||
|
||||
|
||||
assert len(hashed) == 64 # SHA256 produces 64-char hex string
|
||||
assert hashed == hash_api_key(key) # Deterministic
|
||||
|
||||
@@ -65,7 +65,7 @@ def test_validator_singleton():
|
||||
"""Test that get_validator returns same instance."""
|
||||
validator1 = get_validator()
|
||||
validator2 = get_validator()
|
||||
|
||||
|
||||
assert validator1 is validator2
|
||||
|
||||
|
||||
@@ -73,10 +73,10 @@ def test_constant_time_compare(validator):
|
||||
"""Test constant-time string comparison."""
|
||||
# Same strings
|
||||
assert validator._constant_time_compare("test", "test")
|
||||
|
||||
|
||||
# Different strings
|
||||
assert not validator._constant_time_compare("test", "fail")
|
||||
|
||||
|
||||
# Different lengths
|
||||
assert not validator._constant_time_compare("test", "testing")
|
||||
|
||||
@@ -86,19 +86,19 @@ def test_extract_bearer_token(validator):
|
||||
# Valid bearer token
|
||||
token = validator.extract_bearer_token("Bearer abc123")
|
||||
assert token == "abc123"
|
||||
|
||||
|
||||
# No header
|
||||
token = validator.extract_bearer_token(None)
|
||||
assert token is None
|
||||
|
||||
|
||||
# Wrong format
|
||||
token = validator.extract_bearer_token("abc123")
|
||||
assert token is None
|
||||
|
||||
|
||||
# Wrong scheme
|
||||
token = validator.extract_bearer_token("Basic abc123")
|
||||
assert token is None
|
||||
|
||||
|
||||
# Too many parts
|
||||
token = validator.extract_bearer_token("Bearer abc 123")
|
||||
assert token is None
|
||||
@@ -107,7 +107,7 @@ def test_extract_bearer_token(validator):
|
||||
def test_validate_api_key_missing(validator):
|
||||
"""Test validation with missing API key."""
|
||||
is_valid, error = validator.validate_api_key(None, "127.0.0.1", "test-agent")
|
||||
|
||||
|
||||
assert not is_valid
|
||||
assert "Authorization header missing" in error
|
||||
|
||||
@@ -115,7 +115,7 @@ def test_validate_api_key_missing(validator):
|
||||
def test_validate_api_key_too_short(validator):
|
||||
"""Test validation with too-short API key."""
|
||||
is_valid, error = validator.validate_api_key("short", "127.0.0.1", "test-agent")
|
||||
|
||||
|
||||
assert not is_valid
|
||||
assert "Invalid API key format" in error
|
||||
|
||||
@@ -123,7 +123,7 @@ def test_validate_api_key_too_short(validator):
|
||||
def test_validate_api_key_invalid(validator, mock_env_with_key):
|
||||
"""Test validation with invalid API key."""
|
||||
is_valid, error = validator.validate_api_key("b" * 64, "127.0.0.1", "test-agent")
|
||||
|
||||
|
||||
assert not is_valid
|
||||
assert "Invalid API key" in error
|
||||
|
||||
@@ -131,7 +131,7 @@ def test_validate_api_key_invalid(validator, mock_env_with_key):
|
||||
def test_validate_api_key_valid(validator, mock_env_with_key):
|
||||
"""Test validation with valid API key."""
|
||||
is_valid, error = validator.validate_api_key("a" * 64, "127.0.0.1", "test-agent")
|
||||
|
||||
|
||||
assert is_valid
|
||||
assert error is None
|
||||
|
||||
@@ -139,12 +139,12 @@ def test_validate_api_key_valid(validator, mock_env_with_key):
|
||||
def test_rate_limiting(validator):
|
||||
"""Test rate limiting after multiple failures."""
|
||||
client_ip = "192.168.1.1"
|
||||
|
||||
|
||||
# First 5 attempts should be allowed
|
||||
for i in range(5):
|
||||
for _ in range(5):
|
||||
is_valid, error = validator.validate_api_key("b" * 64, client_ip, "test-agent")
|
||||
assert not is_valid
|
||||
|
||||
|
||||
# 6th attempt should be rate limited
|
||||
is_valid, error = validator.validate_api_key("b" * 64, client_ip, "test-agent")
|
||||
assert not is_valid
|
||||
@@ -155,15 +155,15 @@ def test_rate_limiting_per_ip(validator):
|
||||
"""Test that rate limiting is per IP address."""
|
||||
ip1 = "192.168.1.1"
|
||||
ip2 = "192.168.1.2"
|
||||
|
||||
|
||||
# Fail 5 times from IP1
|
||||
for i in range(5):
|
||||
for _ in range(5):
|
||||
validator.validate_api_key("b" * 64, ip1, "test-agent")
|
||||
|
||||
|
||||
# IP1 should be rate limited
|
||||
is_valid, error = validator.validate_api_key("b" * 64, ip1, "test-agent")
|
||||
assert "Too many failed" in error
|
||||
|
||||
|
||||
# IP2 should still work
|
||||
is_valid, error = validator.validate_api_key("b" * 64, ip2, "test-agent")
|
||||
assert "Invalid API key" in error # Wrong key, but not rate limited
|
||||
@@ -175,9 +175,9 @@ def test_auth_disabled(monkeypatch):
|
||||
monkeypatch.setenv("GITEA_TOKEN", "test-token-12345")
|
||||
monkeypatch.setenv("AUTH_ENABLED", "false")
|
||||
monkeypatch.setenv("MCP_API_KEYS", "") # No keys needed when disabled
|
||||
|
||||
|
||||
validator = APIKeyValidator()
|
||||
|
||||
|
||||
# Should allow access without key
|
||||
is_valid, error = validator.validate_api_key(None, "127.0.0.1", "test-agent")
|
||||
assert is_valid
|
||||
@@ -190,19 +190,19 @@ def test_multiple_keys(monkeypatch):
|
||||
monkeypatch.setenv("GITEA_TOKEN", "test-token-12345")
|
||||
monkeypatch.setenv("AUTH_ENABLED", "true")
|
||||
monkeypatch.setenv("MCP_API_KEYS", f"{'a' * 64},{'b' * 64},{'c' * 64}")
|
||||
|
||||
|
||||
validator = APIKeyValidator()
|
||||
|
||||
|
||||
# All three keys should work
|
||||
is_valid, _ = validator.validate_api_key("a" * 64, "127.0.0.1", "test-agent")
|
||||
assert is_valid
|
||||
|
||||
|
||||
is_valid, _ = validator.validate_api_key("b" * 64, "127.0.0.1", "test-agent")
|
||||
assert is_valid
|
||||
|
||||
|
||||
is_valid, _ = validator.validate_api_key("c" * 64, "127.0.0.1", "test-agent")
|
||||
assert is_valid
|
||||
|
||||
|
||||
# Invalid key should fail
|
||||
is_valid, _ = validator.validate_api_key("d" * 64, "127.0.0.1", "test-agent")
|
||||
assert not is_valid
|
||||
|
||||
@@ -120,7 +120,7 @@ def test_concurrent_requests_different_ips(client):
|
||||
tool_call_data = {"tool": "list_repositories", "arguments": {}}
|
||||
|
||||
# Make 5 failed attempts on protected endpoint
|
||||
for i in range(5):
|
||||
for _ in range(5):
|
||||
response = client.post("/mcp/tool/call", headers=headers_invalid, json=tool_call_data)
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ def test_rate_limiting(client):
|
||||
tool_data = {"tool": "list_repositories", "arguments": {}}
|
||||
|
||||
# Make 6 failed attempts on protected endpoint
|
||||
for i in range(6):
|
||||
for _ in range(6):
|
||||
response = client.post(
|
||||
"/mcp/tool/call", headers={"Authorization": "Bearer " + "x" * 64}, json=tool_data
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user