Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ tracing-subscriber = { version = "~0.3", features = ["env-filter"] }
uuid = { version = "~1.3", features = ["v4", "fast-rng"] }

[dev-dependencies]
base64 = "~0.21"
mockito = "~1.0"
serial_test = "3.3.1"
tempfile = "~3.5"
Expand Down
8 changes: 4 additions & 4 deletions doc/api/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The JWT token contains a simple claim structure:

**Signing Process:**

1. The shared secret is loaded from configuration (`status_dashboard.secret`)
1. The shared secret is loaded from configuration (`status_dashboard.jwt_secret`)
2. An HMAC-SHA256 key is created from the secret bytes
3. Claims are signed with the key to produce the JWT token
4. The token is included in the `Authorization` header as a Bearer token
Expand All @@ -51,7 +51,7 @@ status_dashboard:
The secret can also be set via environment variable:

```bash
export MP_STATUS_DASHBOARD__SECRET="your-shared-secret-key"
export MP_STATUS_DASHBOARD__JWT_SECRET="your-shared-secret-key"
```

Environment variables are merged with the configuration file, with environment variables taking precedence.
Expand All @@ -68,7 +68,7 @@ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdGFja21vbiI6ImR1b

### Request Flow

1. **Configuration Load:** The reporter reads the `status_dashboard.secret` from configuration
1. **Configuration Load:** The reporter reads the `status_dashboard.jwt_secret` from configuration
2. **Token Generation:** If a secret is configured, a JWT token is generated at startup
3. **Request Authentication:** All POST requests to `/v1/component_status` include the Bearer token
4. **Server Validation:** The status-dashboard validates the token signature using the same shared secret
Expand All @@ -86,7 +86,7 @@ On the server side (status-dashboard), tokens should be validated by:
### Secret Management

- **Never commit secrets** to version control
- Use environment variables (`MP_STATUS_DASHBOARD__SECRET`) in production
- Use environment variables (`MP_STATUS_DASHBOARD__JWT_SECRET`) in production
- Rotate secrets periodically
- Use strong, randomly-generated secrets (minimum 32 characters recommended)

Expand Down
4 changes: 2 additions & 2 deletions doc/architecture/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,8 @@ src/

```bash
# Environment variable example
MP_STATUS_DASHBOARD__SECRET=my-jwt-secret
# Translates to: status_dashboard.secret = "my-jwt-secret"
MP_STATUS_DASHBOARD__JWT_SECRET=my-jwt-secret
# Translates to: status_dashboard.jwt_secret = "my-jwt-secret"
```

## Security Considerations
Expand Down
12 changes: 6 additions & 6 deletions doc/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ environments:

status_dashboard:
url: "https://status.cloudmon.com"
secret: "dev"
jwt_secret: "dev"

flag_metrics:
### Comp1
Expand Down Expand Up @@ -94,13 +94,13 @@ Configures URL and JWT secret for communication with the status dashboard.
```yaml
status_dashboard:
url: "https://status-dashboard.example.com"
secret: "your-jwt-secret"
jwt_secret: "your-jwt-secret"
```

| Property | Type | Required | Default | Description |
|----------|--------|----------|---------|---------------------------------------|
| `url` | string | Yes | - | Status Dashboard API URL |
| `secret` | string | No | - | JWT signing secret for authentication |
| Property | Type | Required | Default | Description |
|--------------|--------|----------|---------|---------------------------------------|
| `url` | string | Yes | - | Status Dashboard API URL |
| `jwt_secret` | string | No | - | JWT signing secret for authentication |

## health_query

Expand Down
2 changes: 1 addition & 1 deletion doc/configuration/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ server:

status_dashboard:
url: "https://status.example.com"
# Secret should be set via MP_STATUS_DASHBOARD__SECRET environment variable
# Secret should be set via MP_STATUS_DASHBOARD__JWT_SECRET environment variable

metric_templates:
api_latency:
Expand Down
4 changes: 2 additions & 2 deletions doc/configuration/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ export MP_DATASOURCE__URL="http://graphite.example.com:8080"
# Override server.port
export MP_SERVER__PORT=3005

# Set status_dashboard.secret (sensitive values)
export MP_STATUS_DASHBOARD__SECRET="your-jwt-secret"
# Set status_dashboard.jwt_secret (sensitive values)
export MP_STATUS_DASHBOARD__JWT_SECRET="your-jwt-secret"
```

**Best Practice**: Use environment variables for sensitive values like secrets and for deployment-specific overrides in containerized environments.
Expand Down
4 changes: 2 additions & 2 deletions doc/configuration/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,12 @@ Optional status dashboard integration.
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `url` | string | Yes | Status dashboard URL |
| `secret` | string | No | JWT signing secret |
| `jwt_secret` | string | No | JWT signing secret |

```yaml
status_dashboard:
url: "https://status.example.com"
secret: "your-jwt-secret" # Use MP_STATUS_DASHBOARD__SECRET env var instead
jwt_secret: "your-jwt-secret" # Use MP_STATUS_DASHBOARD__JWT_SECRET env var instead
```

## Comparison Operators
Expand Down
8 changes: 4 additions & 4 deletions doc/guides/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ docker run -d \
--network host \
-v $(pwd)/config:/cloudmon/config:ro \
-e RUST_LOG=info \
-e MP_STATUS_DASHBOARD__SECRET=your-jwt-secret \
-e MP_STATUS_DASHBOARD__JWT_SECRET=your-jwt-secret \
metrics-processor:latest \
/cloudmon/cloudmon-metrics-reporter
```
Expand Down Expand Up @@ -130,7 +130,7 @@ services:
- ./config:/cloudmon/config:ro
environment:
- RUST_LOG=info
- MP_STATUS_DASHBOARD__SECRET=${STATUS_DASHBOARD_SECRET}
- MP_STATUS_DASHBOARD__JWT_SECRET=${STATUS_DASHBOARD_SECRET}
depends_on:
convertor:
condition: service_healthy
Expand Down Expand Up @@ -343,7 +343,7 @@ spec:
env:
- name: RUST_LOG
value: "info"
- name: MP_STATUS_DASHBOARD__SECRET
- name: MP_STATUS_DASHBOARD__JWT_SECRET
valueFrom:
secretKeyRef:
name: metrics-processor-secrets
Expand Down Expand Up @@ -462,7 +462,7 @@ Override configuration values using environment variables prefixed with `MP_`:

```bash
# Override status dashboard secret
export MP_STATUS_DASHBOARD__SECRET=production-secret
export MP_STATUS_DASHBOARD__JWT_SECRET=production-secret

# Override datasource URL
export MP_DATASOURCE__URL=https://graphite-prod.example.com
Expand Down
2 changes: 1 addition & 1 deletion doc/modules/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ The `AppState` contains:

## Authentication

Currently, the API does not implement authentication. The `status_dashboard.secret` configuration option suggests JWT-based authentication may be planned for integration with status dashboard services.
Currently, the API does not implement authentication. The `status_dashboard.jwt_secret` configuration option suggests JWT-based authentication may be planned for integration with status dashboard services.

## Integration with Other Modules

Expand Down
4 changes: 2 additions & 2 deletions doc/modules/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Environment variables use `MP_` prefix with `__` as sublevel separator:
|---------------------|-------------|
| `MP_DATASOURCE__URL` | `datasource.url` |
| `MP_SERVER__PORT` | `server.port` |
| `MP_STATUS_DASHBOARD__SECRET` | `status_dashboard.secret` |
| `MP_STATUS_DASHBOARD__JWT_SECRET` | `status_dashboard.jwt_secret` |

```rust
Environment::with_prefix("MP")
Expand Down Expand Up @@ -163,7 +163,7 @@ health_metrics:

status_dashboard:
url: 'https://status.example.com'
secret: ${MP_STATUS_DASHBOARD__SECRET}
jwt_secret: ${MP_STATUS_DASHBOARD__JWT_SECRET}
```

### Modular Configuration (conf.d)
Expand Down
29 changes: 24 additions & 5 deletions doc/modules/sd.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,33 @@ Key: `(component_name, sorted_attributes)` → Value: `component_id`
#### `build_auth_headers`

```rust
pub fn build_auth_headers(secret: Option<&str>) -> HeaderMap
pub fn build_auth_headers(
secret: Option<&str>,
preferred_username: Option<&str>,
group: Option<&str>,
) -> HeaderMap
```

Generates HMAC-JWT authorization headers for Status Dashboard API.

- Creates Bearer token using HMAC-SHA256 signing
- Returns empty HeaderMap if no secret provided (optional auth)
- Optionally includes `preferred_username` claim for audit logging
- Optionally includes `groups` array claim with single group for authorization

**Example**:
```rust
let headers = build_auth_headers(Some("my-secret"));
// Headers contain: Authorization: Bearer eyJ...
// With claims
let headers = build_auth_headers(
Some("status-dashboard-secret"),
Some("operator-sd"),
Some("sd-operators"),
);
// JWT payload: {"preferred_username": "operator-sd", "groups": ["sd-operators"]}

// Without claims (backward compatible)
let headers = build_auth_headers(Some("my-secret"), None, None);
// JWT payload: {}
```

### Component Management
Expand Down Expand Up @@ -184,8 +199,12 @@ use cloudmon_metrics::sd::{
Component, ComponentAttribute,
};

// Build auth headers
let headers = build_auth_headers(config.secret.as_deref());
// Build auth headers with optional claims
let headers = build_auth_headers(
config.jwt_secret.as_deref(),
config.claim_preferred_username.as_deref(),
config.claim_group.as_deref(),
);

// Fetch and cache components
let components = fetch_components(&client, &url, &headers).await?;
Expand Down
52 changes: 39 additions & 13 deletions doc/reporter.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,19 +138,35 @@ Incidents are created with static, secure payloads:

### 5. Authentication

The reporter uses HMAC-JWT for authentication (unchanged from V1):
The reporter uses HMAC-JWT for authentication with optional claims:

```rust
// Generate HMAC-JWT token
let headers = build_auth_headers(secret.as_deref());
// Generate HMAC-JWT token with optional claims
let headers = build_auth_headers(
secret.as_deref(),
preferred_username.as_deref(),
group.as_deref(),
);
// Headers contain: Authorization: Bearer <jwt-token>
```

**Token Format**:
- Algorithm: HMAC-SHA256
- Claims: `{"stackmon": "dummy"}`
- Claims (when configured):
- `preferred_username`: User identifier for audit logging
- `groups`: Array containing single group for authorization
- Optional: No secret = no auth header (for environments without auth)

**Example JWT Payload** (with all claims configured):
```json
{
"preferred_username": "sd-username",
"groups": ["operators-sd-group", "group_1", "group_2"]
}
```

**Backward Compatibility**: If `claim_preferred_username` and `claim_group` are not configured, the JWT payload will be empty (same behavior as before).

## Module Structure

The Status Dashboard integration is consolidated in `src/sd.rs`:
Expand All @@ -166,7 +182,11 @@ pub struct IncidentData { title, description, impact, components, start_date, sy
pub type ComponentCache = HashMap<(String, Vec<ComponentAttribute>), u32>;

// Authentication
pub fn build_auth_headers(secret: Option<&str>) -> HeaderMap
pub fn build_auth_headers(
secret: Option<&str>,
preferred_username: Option<&str>,
group: Option<&str>,
) -> HeaderMap

// V2 API Functions
pub async fn fetch_components(...) -> Result<Vec<StatusDashboardComponent>>
Expand All @@ -193,13 +213,17 @@ convertor:
```yaml
status_dashboard:
url: "https://dashboard.example.com"
secret: "your-jwt-secret"
jwt_secret: "your-jwt-secret"
claim_preferred_username: "sd-username" # Optional: user identifier for JWT
claim_group: "operators-sd-group" # Optional: group for authorization
```

| Property | Type | Required | Default | Description |
|----------|--------|----------|---------|---------------------------------------|
| `url` | string | Yes | - | Status Dashboard API URL |
| `secret` | string | No | - | JWT signing secret for authentication |
| Property | Type | Required | Default | Description |
|------------------------|--------|----------|---------|--------------------------------------------------|
| `url` | string | Yes | - | Status Dashboard API URL |
| `jwt_secret` | string | No | - | JWT signing secret for authentication |
| `claim_preferred_username` | string | No | - | Username claim for JWT (audit logging) |
| `claim_group` | string | No | - | Group claim for JWT (placed into `groups` array) |

### Health Query Configuration

Expand Down Expand Up @@ -282,7 +306,9 @@ spec:
Override configuration:

```bash
MP_STATUS_DASHBOARD__SECRET=new-secret \
MP_STATUS_DASHBOARD__JWT_SECRET=status-dashboard-secret \
MP_STATUS_DASHBOARD__CLAIM_PREFERRED_USERNAME=sd-username \
MP_STATUS_DASHBOARD__CLAIM_GROUP=operators-sd-group \
MP_CONVERTOR__URL=http://convertor-svc:3005 \
cloudmon-metrics-reporter --config config.yaml
```
Expand Down Expand Up @@ -391,7 +417,7 @@ When the reporter decides to create an incident, it logs all the information nee
### Authentication Failures

**Cause**: Invalid JWT secret
**Solution**: Update `status_dashboard.secret` in configuration
**Solution**: Update `status_dashboard.jwt_secret` in configuration

## Use Cases

Expand Down Expand Up @@ -430,7 +456,7 @@ curl http://localhost:3005/v1/health?service=api&environment=prod&from=2024-01-0
### "Dashboard authentication failed"

**Cause**: Invalid JWT secret
**Solution**: Ensure `status_dashboard.secret` matches dashboard configuration
**Solution**: Ensure `status_dashboard.jwt_secret` matches dashboard configuration

### "No services being polled"

Expand Down
2 changes: 1 addition & 1 deletion specs/003-sd-api-v2-migration/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ No configuration changes required. Existing `config.yaml` is compatible:
```yaml
status_dashboard:
url: "https://status-dashboard.example.com"
secret: "your-hmac-secret" # Optional, for auth
jwt_secret: "your-hmac-secret" # Optional, for auth

environments:
- name: production
Expand Down
6 changes: 5 additions & 1 deletion src/bin/reporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,11 @@ async fn metric_watcher(config: &Config) {

// Build authorization headers using status_dashboard module (T021, T022, T023 - US3)
// VERIFIED: Existing HMAC-JWT mechanism works unchanged with V2 endpoints
let headers = build_auth_headers(sdb_config.secret.as_deref());
let headers = build_auth_headers(
sdb_config.jwt_secret.as_deref(),
sdb_config.claim_preferred_username.as_deref(),
sdb_config.claim_group.as_deref(),
);

// Initialize component ID cache at startup with retry logic (T024, T025, T026, T027)
// Per FR-006: 3 retry attempts with 60-second delays
Expand Down
Loading
Loading