tests fixes and some product advisories tunes ups
This commit is contained in:
@@ -739,54 +739,213 @@ sequenceDiagram
|
||||
- Health endpoints: `/health/liveness`, `/health/readiness`, `/status`; verification probe `/api/attestations/verify` once demo bundle is available (see runbook).
|
||||
- Alert hints: signing latency > 1s p99, verification failure spikes, tlog submission lag >10s, key rotation age over policy threshold, backlog above configured threshold.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 17) Rekor Entry Events
|
||||
|
||||
> Sprint: SPRINT_20260112_007_ATTESTOR_rekor_entry_events
|
||||
|
||||
Attestor emits deterministic events when DSSE bundles are logged to Rekor and inclusion proofs become available. These events drive policy reanalysis.
|
||||
|
||||
### Event Types
|
||||
|
||||
| Event Type | Constant | Description |
|
||||
|------------|----------|-------------|
|
||||
| `rekor.entry.logged` | `RekorEventTypes.EntryLogged` | Bundle successfully logged with inclusion proof |
|
||||
| `rekor.entry.queued` | `RekorEventTypes.EntryQueued` | Bundle queued for logging (async mode) |
|
||||
| `rekor.entry.inclusion_verified` | `RekorEventTypes.InclusionVerified` | Inclusion proof independently verified |
|
||||
| `rekor.entry.failed` | `RekorEventTypes.EntryFailed` | Logging or verification failed |
|
||||
|
||||
### RekorEntryEvent Schema
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"eventId": "rekor-evt-sha256:...",
|
||||
"eventType": "rekor.entry.logged",
|
||||
"tenant": "default",
|
||||
"bundleDigest": "sha256:abc123...",
|
||||
"artifactDigest": "sha256:def456...",
|
||||
"predicateType": "StellaOps.ScanResults@1",
|
||||
"rekorEntry": {
|
||||
"uuid": "24296fb24b8ad77a...",
|
||||
"logIndex": 123456789,
|
||||
"logUrl": "https://rekor.sigstore.dev",
|
||||
"integratedTime": "2026-01-15T10:30:02Z"
|
||||
},
|
||||
"reanalysisHints": {
|
||||
"cveIds": ["CVE-2026-1234"],
|
||||
"productKeys": ["pkg:npm/lodash@4.17.21"],
|
||||
"mayAffectDecision": true,
|
||||
"reanalysisScope": "immediate"
|
||||
},
|
||||
"occurredAtUtc": "2026-01-15T10:30:05Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Offline Mode Behavior
|
||||
|
||||
When operating in offline/air-gapped mode:
|
||||
1. Events are not emitted when Rekor is unreachable
|
||||
2. Bundles are queued locally for later submission
|
||||
3. Verification uses bundled checkpoints
|
||||
|
||||
---
|
||||
|
||||
## 17) Rekor Entry Events
|
||||
|
||||
> Sprint: SPRINT_20260112_007_ATTESTOR_rekor_entry_events
|
||||
|
||||
Attestor emits deterministic events when DSSE bundles are logged to Rekor and inclusion proofs become available. These events drive policy reanalysis.
|
||||
|
||||
### Event Types
|
||||
|
||||
| Event Type | Constant | Description |
|
||||
|------------|----------|-------------|
|
||||
| `rekor.entry.logged` | `RekorEventTypes.EntryLogged` | Bundle successfully logged with inclusion proof |
|
||||
| `rekor.entry.queued` | `RekorEventTypes.EntryQueued` | Bundle queued for logging (async mode) |
|
||||
| `rekor.entry.inclusion_verified` | `RekorEventTypes.InclusionVerified` | Inclusion proof independently verified |
|
||||
| `rekor.entry.failed` | `RekorEventTypes.EntryFailed` | Logging or verification failed |
|
||||
|
||||
### RekorEntryEvent Schema
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"eventId": "rekor-evt-sha256:...",
|
||||
"eventType": "rekor.entry.logged",
|
||||
"tenant": "default",
|
||||
"bundleDigest": "sha256:abc123...",
|
||||
"artifactDigest": "sha256:def456...",
|
||||
"predicateType": "StellaOps.ScanResults@1",
|
||||
"rekorEntry": {
|
||||
"uuid": "24296fb24b8ad77a...",
|
||||
"logIndex": 123456789,
|
||||
"logUrl": "https://rekor.sigstore.dev",
|
||||
"integratedTime": "2026-01-15T10:30:02Z"
|
||||
},
|
||||
"reanalysisHints": {
|
||||
"cveIds": ["CVE-2026-1234"],
|
||||
"productKeys": ["pkg:npm/lodash@4.17.21"],
|
||||
"mayAffectDecision": true,
|
||||
"reanalysisScope": "immediate"
|
||||
},
|
||||
"occurredAtUtc": "2026-01-15T10:30:05Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Offline Mode Behavior
|
||||
|
||||
When operating in offline/air-gapped mode:
|
||||
1. Events are not emitted when Rekor is unreachable
|
||||
2. Bundles are queued locally for later submission
|
||||
3. Verification uses bundled checkpoints
|
||||
4. Events are generated when connectivity is restored
|
||||
|
||||
---
|
||||
|
||||
## 18) Identity Watchlist & Monitoring
|
||||
|
||||
> Sprint: SPRINT_0129_001_ATTESTOR_identity_watchlist_alerting
|
||||
|
||||
The Attestor provides proactive monitoring for signing identities appearing in transparency logs. Organizations can define watchlists to receive alerts when specific identities sign artifacts.
|
||||
|
||||
### Purpose
|
||||
|
||||
- **Credential compromise detection**: Alert when your signing identity appears unexpectedly
|
||||
- **Third-party monitoring**: Watch for specific vendors or dependencies signing artifacts
|
||||
- **Compliance auditing**: Track all signing activity for specific issuers
|
||||
|
||||
### Watchlist Entry Model
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"id": "uuid",
|
||||
"tenantId": "tenant-123",
|
||||
"scope": "tenant", // tenant | global | system
|
||||
"displayName": "GitHub Actions Signer",
|
||||
"description": "Watch for GitHub Actions OIDC tokens",
|
||||
|
||||
// Identity fields (at least one required)
|
||||
"issuer": "https://token.actions.githubusercontent.com",
|
||||
"subjectAlternativeName": "repo:org/repo:*", // glob pattern
|
||||
"keyId": null,
|
||||
|
||||
"matchMode": "glob", // exact | prefix | glob | regex
|
||||
|
||||
// Alert configuration
|
||||
"severity": "warning", // info | warning | critical
|
||||
"enabled": true,
|
||||
"channelOverrides": ["slack-security"],
|
||||
"suppressDuplicatesMinutes": 60,
|
||||
|
||||
"tags": ["github", "ci-cd"],
|
||||
"createdAt": "2026-01-29T10:00:00Z",
|
||||
"createdBy": "admin@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
### Matching Modes
|
||||
|
||||
| Mode | Behavior | Example Pattern | Matches |
|
||||
|------|----------|-----------------|---------|
|
||||
| `exact` | Case-insensitive equality | `alice@example.com` | `Alice@example.com` |
|
||||
| `prefix` | Starts-with match | `https://accounts.google.com/` | Any Google OIDC issuer |
|
||||
| `glob` | Glob pattern (`*`, `?`) | `*@example.com` | `alice@example.com`, `bob@example.com` |
|
||||
| `regex` | Full regex (with timeout) | `repo:org/(frontend\|backend):.*` | `repo:org/frontend:ref:main` |
|
||||
|
||||
### Scope Hierarchy
|
||||
|
||||
| Scope | Visibility | Who Can Create |
|
||||
|-------|------------|----------------|
|
||||
| `tenant` | Owning tenant only | Tenant admins |
|
||||
| `global` | All tenants | Platform admins |
|
||||
| `system` | All tenants (read-only) | System bootstrap |
|
||||
|
||||
### Event Flow
|
||||
|
||||
```
|
||||
New AttestorEntry persisted
|
||||
→ SignerIdentityDescriptor extracted
|
||||
→ IIdentityMatcher.MatchAsync()
|
||||
→ For each match:
|
||||
→ Check dedup window (default 60 min)
|
||||
→ Emit attestor.identity.matched event
|
||||
→ Route via Notifier rules → Slack/Email/Webhook
|
||||
```
|
||||
|
||||
### Event Schema (IdentityAlertEvent)
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"eventId": "uuid",
|
||||
"eventKind": "attestor.identity.matched",
|
||||
"tenantId": "tenant-123",
|
||||
"watchlistEntryId": "uuid",
|
||||
"watchlistEntryName": "GitHub Actions Signer",
|
||||
"matchedIdentity": {
|
||||
"issuer": "https://token.actions.githubusercontent.com",
|
||||
"subjectAlternativeName": "repo:org/repo:ref:refs/heads/main",
|
||||
"keyId": null
|
||||
},
|
||||
"rekorEntry": {
|
||||
"uuid": "24296fb24b8ad77a...",
|
||||
"logIndex": 123456789,
|
||||
"artifactSha256": "sha256:abc123...",
|
||||
"integratedTimeUtc": "2026-01-29T10:30:00Z"
|
||||
},
|
||||
"severity": "warning",
|
||||
"occurredAtUtc": "2026-01-29T10:30:05Z",
|
||||
"suppressedCount": 0
|
||||
}
|
||||
```
|
||||
|
||||
### API Endpoints
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `POST` | `/api/v1/watchlist` | Create watchlist entry |
|
||||
| `GET` | `/api/v1/watchlist` | List entries (tenant + optional global) |
|
||||
| `GET` | `/api/v1/watchlist/{id}` | Get single entry |
|
||||
| `PUT` | `/api/v1/watchlist/{id}` | Update entry |
|
||||
| `DELETE` | `/api/v1/watchlist/{id}` | Delete entry |
|
||||
| `POST` | `/api/v1/watchlist/{id}/test` | Test pattern against sample identity |
|
||||
| `GET` | `/api/v1/watchlist/alerts` | List recent alerts (paginated) |
|
||||
|
||||
### CLI Commands
|
||||
|
||||
```bash
|
||||
# Add a watchlist entry
|
||||
stella watchlist add --issuer "https://token.actions.githubusercontent.com" \
|
||||
--san "repo:org/*" --match-mode glob --severity warning
|
||||
|
||||
# List entries
|
||||
stella watchlist list --include-global
|
||||
|
||||
# Test a pattern
|
||||
stella watchlist test <id> --issuer "https://..." --san "repo:org/repo:ref:main"
|
||||
|
||||
# View recent alerts
|
||||
stella watchlist alerts --since 24h --severity warning
|
||||
```
|
||||
|
||||
### Metrics
|
||||
|
||||
| Metric | Description |
|
||||
|--------|-------------|
|
||||
| `attestor.watchlist.entries_scanned_total` | Entries processed by monitor |
|
||||
| `attestor.watchlist.matches_total{severity}` | Pattern matches by severity |
|
||||
| `attestor.watchlist.alerts_emitted_total` | Alerts sent to notification system |
|
||||
| `attestor.watchlist.alerts_suppressed_total` | Alerts deduplicated |
|
||||
| `attestor.watchlist.scan_latency_seconds` | Per-entry scan duration |
|
||||
|
||||
### Configuration
|
||||
|
||||
```yaml
|
||||
attestor:
|
||||
watchlist:
|
||||
enabled: true
|
||||
monitorMode: "changefeed" # changefeed | polling
|
||||
pollingIntervalSeconds: 5 # only for polling mode
|
||||
maxEventsPerSecond: 100 # rate limit
|
||||
defaultDedupWindowMinutes: 60
|
||||
regexTimeoutMs: 100 # safety limit
|
||||
maxWatchlistEntriesPerTenant: 1000
|
||||
```
|
||||
|
||||
### Offline Mode
|
||||
|
||||
In air-gapped environments:
|
||||
- Polling mode used instead of Postgres NOTIFY
|
||||
- Alerts queued locally if notification channels unavailable
|
||||
- Alerts delivered when connectivity restored
|
||||
|
||||
|
||||
237
docs/modules/attestor/guides/identity-watchlist.md
Normal file
237
docs/modules/attestor/guides/identity-watchlist.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# Identity Watchlist User Guide
|
||||
|
||||
This guide covers how to use the identity watchlist feature in Stella Ops to monitor signing activity in transparency logs.
|
||||
|
||||
## Overview
|
||||
|
||||
The identity watchlist enables proactive alerting when specific signing identities appear in Rekor transparency log entries. Use cases include:
|
||||
|
||||
- **Threat Detection**: Monitor for known malicious or compromised signing identities
|
||||
- **Compliance Monitoring**: Track when specific OIDC issuers sign artifacts
|
||||
- **Operational Awareness**: Get notified when CI/CD workflows from specific repositories sign releases
|
||||
- **Key Tracking**: Monitor usage of specific signing keys across your organization
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Watchlist Entries
|
||||
|
||||
A watchlist entry defines an identity pattern to monitor and alert configuration:
|
||||
|
||||
| Field | Description | Required |
|
||||
|-------|-------------|----------|
|
||||
| `displayName` | Human-readable label | Yes |
|
||||
| `description` | Why this identity is watched | No |
|
||||
| `issuer` | OIDC issuer URL pattern | At least one |
|
||||
| `subjectAlternativeName` | Certificate SAN pattern | of these |
|
||||
| `keyId` | Key identifier for keyful signing | three |
|
||||
| `matchMode` | Pattern matching mode | No (default: `exact`) |
|
||||
| `severity` | Alert severity level | No (default: `warning`) |
|
||||
| `enabled` | Whether entry is active | No (default: `true`) |
|
||||
| `scope` | Visibility scope | No (default: `tenant`) |
|
||||
|
||||
### Match Modes
|
||||
|
||||
| Mode | Description | Example Pattern | Matches |
|
||||
|------|-------------|-----------------|---------|
|
||||
| `exact` | Case-insensitive equality | `https://accounts.google.com` | Only that exact URL |
|
||||
| `prefix` | Starts with pattern | `https://token.actions.` | Any GitHub Actions issuer |
|
||||
| `glob` | Wildcard matching | `*@example.com` | Any email at example.com |
|
||||
| `regex` | Full regex (advanced) | `user\d+@.*` | user123@domain.com |
|
||||
|
||||
> **Note**: Regex mode has a 100ms timeout and pattern validation. Use sparingly for performance-critical environments.
|
||||
|
||||
### Severity Levels
|
||||
|
||||
| Level | Use Case | Notification Priority |
|
||||
|-------|----------|----------------------|
|
||||
| `info` | Informational tracking | Low |
|
||||
| `warning` | Unexpected but not critical (default) | Medium |
|
||||
| `critical` | Immediate attention required | High |
|
||||
|
||||
### Scope Levels
|
||||
|
||||
| Scope | Description | Who Can Create |
|
||||
|-------|-------------|----------------|
|
||||
| `tenant` | Visible only to owning tenant | Any user with `watchlist:write` |
|
||||
| `global` | Shared across all tenants | Administrators only |
|
||||
| `system` | System-managed entries | System only |
|
||||
|
||||
## CLI Usage
|
||||
|
||||
### Adding a Watchlist Entry
|
||||
|
||||
Monitor a specific OIDC issuer:
|
||||
```bash
|
||||
stella watchlist add \
|
||||
--issuer "https://token.actions.githubusercontent.com" \
|
||||
--name "GitHub Actions" \
|
||||
--severity warning \
|
||||
--description "Track all GitHub Actions signatures"
|
||||
```
|
||||
|
||||
Monitor email patterns with glob matching:
|
||||
```bash
|
||||
stella watchlist add \
|
||||
--san "*@malicious-domain.com" \
|
||||
--match-mode glob \
|
||||
--severity critical \
|
||||
--name "Malicious Domain"
|
||||
```
|
||||
|
||||
### Listing Entries
|
||||
|
||||
List all entries including global ones:
|
||||
```bash
|
||||
stella watchlist list --include-global
|
||||
```
|
||||
|
||||
Output as JSON for scripting:
|
||||
```bash
|
||||
stella watchlist list --format json
|
||||
```
|
||||
|
||||
### Testing Patterns
|
||||
|
||||
Test if an identity would trigger an alert:
|
||||
```bash
|
||||
stella watchlist test <entry-id> \
|
||||
--issuer "https://token.actions.githubusercontent.com" \
|
||||
--san "repo:org/repo:ref:refs/heads/main"
|
||||
```
|
||||
|
||||
### Managing Entries
|
||||
|
||||
Update an entry:
|
||||
```bash
|
||||
stella watchlist update <entry-id> --enabled false
|
||||
stella watchlist update <entry-id> --severity critical
|
||||
```
|
||||
|
||||
Delete an entry:
|
||||
```bash
|
||||
stella watchlist remove <entry-id>
|
||||
stella watchlist remove <entry-id> --force # Skip confirmation
|
||||
```
|
||||
|
||||
### Viewing Alerts
|
||||
|
||||
List recent alerts:
|
||||
```bash
|
||||
stella watchlist alerts --since 24h
|
||||
stella watchlist alerts --severity critical --limit 50
|
||||
```
|
||||
|
||||
## API Usage
|
||||
|
||||
### Create Entry
|
||||
|
||||
```http
|
||||
POST /api/v1/watchlist
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"displayName": "GitHub Actions Monitor",
|
||||
"issuer": "https://token.actions.githubusercontent.com",
|
||||
"matchMode": "prefix",
|
||||
"severity": "warning",
|
||||
"description": "Monitor all GitHub Actions signatures",
|
||||
"enabled": true,
|
||||
"suppressDuplicatesMinutes": 60
|
||||
}
|
||||
```
|
||||
|
||||
### List Entries
|
||||
|
||||
```http
|
||||
GET /api/v1/watchlist?includeGlobal=true
|
||||
```
|
||||
|
||||
### Test Pattern
|
||||
|
||||
```http
|
||||
POST /api/v1/watchlist/{id}/test
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"issuer": "https://token.actions.githubusercontent.com",
|
||||
"subjectAlternativeName": "repo:org/repo:ref:refs/heads/main"
|
||||
}
|
||||
```
|
||||
|
||||
## Alert Deduplication
|
||||
|
||||
To prevent alert storms, the watchlist system deduplicates alerts based on:
|
||||
|
||||
1. **Watchlist Entry ID** - Which entry triggered the alert
|
||||
2. **Identity Hash** - SHA-256 of the matched identity fields
|
||||
|
||||
Configure the deduplication window per entry with `suppressDuplicatesMinutes` (default: 60 minutes).
|
||||
|
||||
When an alert is suppressed, subsequent alerts will include a `suppressedCount` indicating how many similar alerts were skipped.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Pattern Design
|
||||
|
||||
1. **Start specific, broaden as needed**: Begin with exact matches before using wildcards
|
||||
2. **Test before deploying**: Use the test endpoint to validate patterns
|
||||
3. **Document why**: Always include a description explaining the monitoring purpose
|
||||
4. **Use appropriate match modes**:
|
||||
- `exact` for known bad actors
|
||||
- `prefix` for issuer families (e.g., all Google issuers)
|
||||
- `glob` for email/SAN patterns
|
||||
- `regex` only when simpler modes can't express the pattern
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- Limit regex patterns to < 256 characters
|
||||
- Avoid catastrophic backtracking patterns (e.g., `(a+)+`)
|
||||
- Monitor the `attestor.watchlist.scan_latency_seconds` metric
|
||||
- Keep watchlist size reasonable (< 1000 entries per tenant)
|
||||
|
||||
### Alert Management
|
||||
|
||||
1. **Set appropriate severity**: Reserve `critical` for genuine emergencies
|
||||
2. **Configure dedup windows**: Use shorter windows (5-15 min) for critical entries
|
||||
3. **Review suppressed counts**: High counts may indicate a pattern is too broad
|
||||
4. **Route appropriately**: Use channel overrides for sensitive entries
|
||||
|
||||
## Monitoring & Metrics
|
||||
|
||||
The following OpenTelemetry metrics are exposed:
|
||||
|
||||
| Metric | Description |
|
||||
|--------|-------------|
|
||||
| `attestor.watchlist.entries_scanned_total` | Total Rekor entries processed |
|
||||
| `attestor.watchlist.matches_total{severity}` | Pattern matches by severity |
|
||||
| `attestor.watchlist.alerts_emitted_total` | Alerts sent to notification system |
|
||||
| `attestor.watchlist.alerts_suppressed_total` | Alerts suppressed by deduplication |
|
||||
| `attestor.watchlist.scan_latency_seconds` | Per-entry scan duration |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Entry Not Matching Expected Identity
|
||||
|
||||
1. Verify the match mode is appropriate for your pattern
|
||||
2. Use the test endpoint with verbose output
|
||||
3. Check if the entry is enabled
|
||||
4. Verify case sensitivity (all modes are case-insensitive)
|
||||
|
||||
### Too Many Alerts
|
||||
|
||||
1. Increase `suppressDuplicatesMinutes`
|
||||
2. Narrow the pattern (more specific issuer, SAN prefix)
|
||||
3. Consider if the pattern is too broad
|
||||
|
||||
### Missing Alerts
|
||||
|
||||
1. Verify the entry is enabled
|
||||
2. Check if alerts are being suppressed (view suppressed count)
|
||||
3. Verify notification routing is configured
|
||||
4. Check service logs for matching errors
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Attestor Architecture](../architecture.md)
|
||||
- [Notification Templates](../../notify/templates.md)
|
||||
- [Watchlist Monitoring Runbook](../../operations/watchlist-monitoring-runbook.md)
|
||||
Reference in New Issue
Block a user