Add wiki page: Authentication-and-Security
@@ -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
|
||||||
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user