Add wiki page: Authentication-and-Security

2026-05-22 12:33:03 +00:00
parent 66dfca18d5
commit 45c59932a6

@@ -0,0 +1,270 @@
# Authentication and Security
This page covers the full authentication model, token management, API key usage,
SSO configuration, and security controls in Aegis.
---
## Authentication Methods
Aegis supports two authentication methods, checked in order:
### 1. HttpOnly Cookie (Primary — Browser Use)
After a successful login, the server sets:
```
Set-Cookie: aegis_token=<JWT>; HttpOnly; SameSite=Strict; Path=/; [Secure in prod]
```
The browser automatically sends this cookie on every request.
**This is the recommended method for the frontend.**
### 2. Authorization: Bearer Header (API / Machine Use)
```http
Authorization: Bearer <JWT>
```
Use this for:
- API integrations
- Scripts and CI/CD pipelines
- When cookies are not available (e.g. cross-origin API calls)
---
## JWT Token
### Structure
The JWT payload contains:
```json
{
"sub": "user-uuid",
"role": "red_lead",
"jti": "unique-token-id",
"exp": 1710000000,
"iat": 1709996400
}
```
### Expiry
Configurable via environment variable:
```
ACCESS_TOKEN_EXPIRE_MINUTES=480 # 8 hours default
```
### Token Revocation (Blacklist)
On logout, the token's `jti` is stored in Redis with TTL = remaining token lifetime:
```
Redis key: blacklist:{jti}
Value: "1"
TTL: <seconds until token expiry>
```
Every authenticated request checks Redis for the `jti`. If found → 401 Unauthorized.
---
## Cookie Security
| Setting | Development | Production |
|---------|-------------|------------|
| `HttpOnly` | True | True |
| `SameSite` | Strict | Strict |
| `Secure` | False | True |
| `Path` | / | / |
**`SECURE_COOKIES` environment variable:**
- `auto` (default): `Secure=True` when `AEGIS_ENV=production`
- `true`: Always set `Secure` flag
- `false`: Never set `Secure` flag (development only)
---
## Password Policy
- Minimum length: **12 characters**
- Must contain: uppercase, lowercase, digit, and special character
- Enforced at account creation and password change
- Passwords are hashed with **bcrypt** (work factor 12)
### must_change_password
When an admin creates a user, `must_change_password` is set to `True`.
Until the user changes their password, **every endpoint** returns:
```json
{"detail": "PASSWORD_CHANGE_REQUIRED"}
```
with status `403`, **except**:
- `GET /api/v1/auth/me`
- `POST /api/v1/auth/change-password`
Change password:
```http
POST /api/v1/auth/change-password
Content-Type: application/json
{
"current_password": "admin-set-password",
"new_password": "MyNewSecurePassword123!"
}
```
---
## API Keys
API keys provide long-lived credentials for machine-to-machine access without
session management.
### Creating a Key
```http
POST /api/v1/api-keys
Content-Type: application/json
{
"name": "CI Pipeline Key",
"scope": "read",
"expires_at": "2025-12-31T00:00:00Z"
}
```
Response includes the key value (only shown once):
```json
{
"id": "uuid",
"name": "CI Pipeline Key",
"key": "aegis_a1b2c3d4e5f6...",
"scope": "read"
}
```
### Using a Key
```http
Authorization: Bearer aegis_a1b2c3d4e5f6...
```
### Scopes
| Scope | Allowed HTTP methods |
|-------|---------------------|
| `read` | GET, HEAD, OPTIONS only |
| `write` | GET, POST, PATCH, PUT, DELETE |
| `admin` | All + admin-only endpoints |
A `read` scope key that attempts POST/PATCH/DELETE receives:
```json
{"detail": "API key scope 'read' does not permit this operation"}
```
with status `403`.
### Revoking a Key
```http
DELETE /api/v1/api-keys/{id}
```
---
## SAML 2.0 SSO
Aegis supports SAML 2.0 for enterprise single sign-on (e.g. Azure AD, Okta, Ping).
### Configuration (admin only)
```http
PUT /api/v1/sso/config
Content-Type: application/json
{
"enabled": true,
"idp_entity_id": "https://idp.example.com",
"idp_sso_url": "https://idp.example.com/sso/saml",
"idp_certificate": "-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----",
"sp_entity_id": "https://aegis.example.com",
"attribute_mapping": {
"email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
"role": "http://schemas.microsoft.com/ws/2008/06/identity/claims/groups"
}
}
```
### SSO Login Flow
1. Browser visits `GET /api/v1/sso/login` → redirect to IdP
2. User authenticates with IdP
3. IdP POSTs SAML assertion to `POST /api/v1/sso/callback`
4. Aegis validates assertion, finds/creates user, issues JWT cookie
5. Browser redirected to frontend
### Service Provider Metadata
```http
GET /api/v1/sso/metadata
```
Returns XML metadata to register Aegis as a SAML service provider in your IdP.
---
## Rate Limiting
- **Login endpoint**: 5 attempts per minute per IP
- Exceeded: `429 Too Many Requests`
- Implemented via Redis counters with TTL
---
## SSRF Protection (Webhooks)
Webhook URLs are validated before saving. The following IP ranges are blocked:
| Range | Description |
|-------|-------------|
| 10.0.0.0/8 | Private (Class A) |
| 172.16.0.0/12 | Private (Class B) |
| 192.168.0.0/16 | Private (Class C) |
| 169.254.0.0/16 | Link-local / APIPA |
| 127.0.0.0/8 | Loopback |
| ::1/128 | IPv6 loopback |
| fc00::/7 | IPv6 unique local |
| 0.0.0.0/8 | Reserved |
| 100.64.0.0/10 | Shared address space |
Attempting to create a webhook pointing to a private IP returns:
```json
{"detail": "Webhook URL points to a private or reserved IP address (SSRF prevention)"}
```
---
## Audit Logging
Every significant action is recorded in the `audit_logs` table (admin read-only):
| Event type | Logged when |
|------------|-------------|
| `auth.login` | Successful login |
| `auth.logout` | Logout |
| `auth.login_failed` | Failed login attempt |
| `auth.password_changed` | Password change |
| `user.created` | New user created |
| `user.updated` | User updated |
| `user.deleted` | User deleted |
| `test.created` | New test created |
| `test.state_changed` | Test state transition |
| `test.validated` | Both leads approved |
| `campaign.completed` | Campaign completed |
| `permission.denied` | 403 on a protected endpoint |
Query audit logs:
```http
GET /api/v1/audit-logs?user_id=<id>&event_type=auth.login&from=2024-01-01&to=2024-12-31
```