feat: Add native binary analyzer test utilities and implement SM2 signing tests
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
- Introduced `NativeTestBase` class for ELF, PE, and Mach-O binary parsing helpers and assertions. - Created `TestCryptoFactory` for SM2 cryptographic provider setup and key generation. - Implemented `Sm2SigningTests` to validate signing functionality with environment gate checks. - Developed console export service and store with comprehensive unit tests for export status management.
This commit is contained in:
72
docs/contracts/authority-routing-decision.md
Normal file
72
docs/contracts/authority-routing-decision.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Authority Routing Decision
|
||||
|
||||
**Decision ID:** DECISION-AUTH-001
|
||||
**Status:** DEFAULT-APPROVED
|
||||
**Effective Date:** 2025-12-06
|
||||
**48h Window Started:** 2025-12-06T00:00:00Z
|
||||
|
||||
## Decision
|
||||
|
||||
Authority claim routing uses **RBAC-standard routing** patterns aligned with existing `docs/security/scopes-and-roles.md`.
|
||||
|
||||
## Rationale
|
||||
|
||||
1. RBAC patterns are well-established and auditable
|
||||
2. Consistent with Authority module implementation
|
||||
3. Supports multi-tenancy requirements
|
||||
4. Compatible with external IdP integration (OIDC, SAML)
|
||||
|
||||
## Routing Matrix
|
||||
|
||||
| Claim | Source | Routing | Scope |
|
||||
|-------|--------|---------|-------|
|
||||
| `tenant_id` | Token/Session | Per-request | All endpoints |
|
||||
| `project_id` | Token/Header | Per-request | Project-scoped |
|
||||
| `user_id` | Token | Per-request | User-scoped |
|
||||
| `role` | Token claims | Authorization | Role-based access |
|
||||
| `scope` | Token claims | Authorization | Fine-grained access |
|
||||
|
||||
## Claim Priority
|
||||
|
||||
When claims conflict:
|
||||
1. Explicit header overrides token claim (if authorized)
|
||||
2. Token claim is authoritative for identity
|
||||
3. Session context provides defaults
|
||||
|
||||
## Implementation Pattern
|
||||
|
||||
```csharp
|
||||
// Authority claim resolution
|
||||
public class ClaimResolver : IClaimResolver
|
||||
{
|
||||
public AuthorityContext Resolve(HttpContext context)
|
||||
{
|
||||
var tenantId = context.Request.Headers["X-Tenant-Id"]
|
||||
?? context.User.FindFirst("tenant_id")?.Value;
|
||||
|
||||
var projectId = context.Request.Headers["X-Project-Id"]
|
||||
?? context.User.FindFirst("project_id")?.Value;
|
||||
|
||||
return new AuthorityContext(tenantId, projectId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Impact
|
||||
|
||||
- Tasks unblocked: ~5
|
||||
- Sprint files affected: SPRINT_0303
|
||||
|
||||
## Reversibility
|
||||
|
||||
To change routing patterns:
|
||||
1. Update `docs/security/scopes-and-roles.md`
|
||||
2. Get Authority Guild + Security Guild sign-off
|
||||
3. Update `AuthorityClaimsProvider` implementations
|
||||
4. Migration path for existing integrations
|
||||
|
||||
## References
|
||||
|
||||
- [Scopes and Roles](../security/scopes-and-roles.md)
|
||||
- [Auth Scopes](../security/auth-scopes.md)
|
||||
- [Tenancy Overview](../security/tenancy-overview.md)
|
||||
56
docs/contracts/dossier-sequencing-decision.md
Normal file
56
docs/contracts/dossier-sequencing-decision.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Dossier Sequencing Decision
|
||||
|
||||
**Decision ID:** DECISION-DOCS-001
|
||||
**Status:** DEFAULT-APPROVED
|
||||
**Effective Date:** 2025-12-06
|
||||
**48h Window Started:** 2025-12-06T00:00:00Z
|
||||
|
||||
## Decision
|
||||
|
||||
Module dossiers (Md.II through Md.X) are **sequenced after Md.I completion**, following the dependency chain in `docs/implplan/SPRINT_0300_*.md` files.
|
||||
|
||||
## Rationale
|
||||
|
||||
1. Md.I establishes baseline architecture documentation structure
|
||||
2. Subsequent modules depend on patterns defined in Md.I
|
||||
3. Sequential ordering prevents documentation conflicts
|
||||
4. Allows parallel work within each dossier batch
|
||||
|
||||
## Sequencing Order
|
||||
|
||||
| Phase | Dossiers | Dependencies | Sprint |
|
||||
|-------|----------|--------------|--------|
|
||||
| Md.I | Concelier, Scanner, Authority | None | 0300 |
|
||||
| Md.II | Attestor, Signer, Evidence | Md.I complete | 0301 |
|
||||
| Md.III | VEX Lens, Excititor | Md.II complete | 0302 |
|
||||
| Md.IV | Policy, Risk | Md.II complete | 0303 |
|
||||
| Md.V | Scheduler, TaskRunner | Md.IV complete | 0304 |
|
||||
| Md.VI | Notify, Telemetry | Md.V complete | 0305 |
|
||||
| Md.VII | CLI, Web | Md.VI complete | 0306 |
|
||||
| Md.VIII | AirGap, Mirror | Md.VII complete | 0307 |
|
||||
| Md.IX | Zastava, Signals | Md.VIII complete | 0308 |
|
||||
| Md.X | Integration, E2E | All above | 0309 |
|
||||
|
||||
## Parallelism Rules
|
||||
|
||||
Within each phase, dossiers MAY be worked in parallel if:
|
||||
1. No cross-dependencies within the phase
|
||||
2. Shared components are stable
|
||||
3. Different owners/guilds assigned
|
||||
|
||||
## Impact
|
||||
|
||||
- Tasks unblocked: ~10
|
||||
- Sprint files affected: SPRINT_0300, SPRINT_0301, SPRINT_0302
|
||||
|
||||
## Reversibility
|
||||
|
||||
To change sequencing:
|
||||
1. Propose new order in `docs/process/dossier-sequencing.md`
|
||||
2. Get Docs Guild sign-off
|
||||
3. Update all affected SPRINT_03xx files
|
||||
|
||||
## References
|
||||
|
||||
- [SPRINT_0300 Documentation](../implplan/SPRINT_0300_0001_0001_documentation_i.md)
|
||||
- [Module Dossier Template](../modules/template/)
|
||||
263
docs/contracts/rate-limit-design.md
Normal file
263
docs/contracts/rate-limit-design.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# Rate Limit Design Contract
|
||||
|
||||
**Contract ID:** CONTRACT-RATE-LIMIT-001
|
||||
**Status:** APPROVED
|
||||
**Effective Date:** 2025-12-07
|
||||
**Owners:** Platform Reliability Guild, Gateway Guild
|
||||
|
||||
## Overview
|
||||
|
||||
This contract defines the rate limiting design for StellaOps API endpoints, ensuring fair resource allocation, protection against abuse, and consistent client experience across all services.
|
||||
|
||||
## Rate Limiting Strategy
|
||||
|
||||
### Tiered Rate Limits
|
||||
|
||||
| Tier | Requests/Minute | Requests/Hour | Burst Limit | Typical Use Case |
|
||||
|------|-----------------|---------------|-------------|------------------|
|
||||
| **Free** | 60 | 1,000 | 10 | Evaluation, small projects |
|
||||
| **Standard** | 300 | 10,000 | 50 | Production workloads |
|
||||
| **Enterprise** | 1,000 | 50,000 | 200 | Large-scale deployments |
|
||||
| **Unlimited** | No limit | No limit | No limit | Internal services, VIP |
|
||||
|
||||
### Per-Endpoint Rate Limits
|
||||
|
||||
Some endpoints have additional rate limits based on resource intensity:
|
||||
|
||||
| Endpoint Category | Rate Limit | Rationale |
|
||||
|-------------------|------------|-----------|
|
||||
| `/api/risk/simulation/*` | 30/min | CPU-intensive simulation |
|
||||
| `/api/risk/simulation/studio/*` | 10/min | Full breakdown analysis |
|
||||
| `/system/airgap/seal` | 5/hour | Critical state change |
|
||||
| `/policy/decisions` | 100/min | Lightweight evaluation |
|
||||
| `/api/policy/packs/*/bundle` | 10/min | Bundle compilation |
|
||||
| Export endpoints | 20/min | I/O-intensive operations |
|
||||
|
||||
## Implementation
|
||||
|
||||
### Algorithm
|
||||
|
||||
Use **Token Bucket** algorithm with the following configuration:
|
||||
|
||||
```yaml
|
||||
rate_limit:
|
||||
algorithm: token_bucket
|
||||
bucket_size: ${BURST_LIMIT}
|
||||
refill_rate: ${REQUESTS_PER_MINUTE} / 60
|
||||
refill_interval: 1s
|
||||
```
|
||||
|
||||
### Rate Limit Headers
|
||||
|
||||
All responses include standard rate limit headers:
|
||||
|
||||
```http
|
||||
X-RateLimit-Limit: 300
|
||||
X-RateLimit-Remaining: 295
|
||||
X-RateLimit-Reset: 1701936000
|
||||
X-RateLimit-Policy: standard
|
||||
Retry-After: 30
|
||||
```
|
||||
|
||||
### Rate Limit Response
|
||||
|
||||
When rate limit is exceeded, return:
|
||||
|
||||
```http
|
||||
HTTP/1.1 429 Too Many Requests
|
||||
Content-Type: application/problem+json
|
||||
Retry-After: 30
|
||||
|
||||
```
|
||||
|
||||
## Rate Limit Keys
|
||||
|
||||
### Primary Key: Tenant ID + Client ID
|
||||
|
||||
```
|
||||
rate_limit_key = "${tenant_id}:${client_id}"
|
||||
```
|
||||
|
||||
### Fallback Keys
|
||||
|
||||
1. Authenticated: `tenant:${tenant_id}:user:${user_id}`
|
||||
2. API Key: `apikey:${api_key_hash}`
|
||||
3. Anonymous: `ip:${client_ip}`
|
||||
|
||||
## Exemptions
|
||||
|
||||
### Exempt Endpoints
|
||||
|
||||
The following endpoints are exempt from rate limiting:
|
||||
|
||||
- `GET /health`
|
||||
- `GET /ready`
|
||||
- `GET /metrics`
|
||||
- `GET /.well-known/*`
|
||||
|
||||
### Exempt Clients
|
||||
|
||||
- Internal service mesh traffic (mTLS authenticated)
|
||||
- Localhost connections in development mode
|
||||
- Clients with `unlimited` tier
|
||||
|
||||
## Quota Management
|
||||
|
||||
### Tenant Quota Tracking
|
||||
|
||||
```yaml
|
||||
quota:
|
||||
tracking:
|
||||
storage: redis
|
||||
key_prefix: "stellaops:quota:"
|
||||
ttl: 3600 # 1 hour rolling window
|
||||
|
||||
dimensions:
|
||||
- tenant_id
|
||||
- endpoint_category
|
||||
- time_bucket
|
||||
```
|
||||
|
||||
### Quota Alerts
|
||||
|
||||
| Threshold | Action |
|
||||
|-----------|--------|
|
||||
| 80% consumed | Emit `quota.warning` event |
|
||||
| 95% consumed | Emit `quota.critical` event |
|
||||
| 100% consumed | Block requests, emit `quota.exceeded` event |
|
||||
|
||||
## Configuration
|
||||
|
||||
### Gateway Configuration
|
||||
|
||||
```yaml
|
||||
# gateway/rate-limits.yaml
|
||||
rateLimiting:
|
||||
enabled: true
|
||||
defaultTier: standard
|
||||
|
||||
tiers:
|
||||
free:
|
||||
requestsPerMinute: 60
|
||||
requestsPerHour: 1000
|
||||
burstLimit: 10
|
||||
standard:
|
||||
requestsPerMinute: 300
|
||||
requestsPerHour: 10000
|
||||
burstLimit: 50
|
||||
enterprise:
|
||||
requestsPerMinute: 1000
|
||||
requestsPerHour: 50000
|
||||
burstLimit: 200
|
||||
|
||||
endpoints:
|
||||
- pattern: "/api/risk/simulation/*"
|
||||
limit: 30
|
||||
window: 60s
|
||||
- pattern: "/api/risk/simulation/studio/*"
|
||||
limit: 10
|
||||
window: 60s
|
||||
- pattern: "/system/airgap/seal"
|
||||
limit: 5
|
||||
window: 3600s
|
||||
```
|
||||
|
||||
### Policy Engine Configuration
|
||||
|
||||
```csharp
|
||||
// PolicyEngineRateLimitOptions.cs
|
||||
public static class PolicyEngineRateLimitOptions
|
||||
{
|
||||
public const string PolicyName = "PolicyEngineRateLimit";
|
||||
|
||||
public static void Configure(RateLimiterOptions options)
|
||||
{
|
||||
options.AddTokenBucketLimiter(PolicyName, opt =>
|
||||
{
|
||||
opt.TokenLimit = 50;
|
||||
opt.QueueLimit = 10;
|
||||
opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
|
||||
opt.TokensPerPeriod = 5;
|
||||
opt.AutoReplenishment = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Metrics
|
||||
|
||||
| Metric | Type | Labels |
|
||||
|--------|------|--------|
|
||||
| `stellaops_rate_limit_requests_total` | Counter | tier, endpoint, status |
|
||||
| `stellaops_rate_limit_exceeded_total` | Counter | tier, endpoint |
|
||||
| `stellaops_rate_limit_remaining` | Gauge | tenant_id, tier |
|
||||
| `stellaops_rate_limit_queue_size` | Gauge | endpoint |
|
||||
|
||||
### Alerts
|
||||
|
||||
```yaml
|
||||
# prometheus/rules/rate-limiting.yaml
|
||||
groups:
|
||||
- name: rate_limiting
|
||||
rules:
|
||||
- alert: HighRateLimitExceeded
|
||||
expr: rate(stellaops_rate_limit_exceeded_total[5m]) > 10
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "High rate of rate limit exceeded events"
|
||||
```
|
||||
|
||||
## Integration with Web UI
|
||||
|
||||
### Client SDK Configuration
|
||||
|
||||
```typescript
|
||||
// stellaops-sdk/rate-limit-handler.ts
|
||||
interface RateLimitConfig {
|
||||
retryOnRateLimit: boolean;
|
||||
maxRetries: number;
|
||||
backoffMultiplier: number;
|
||||
maxBackoffSeconds: number;
|
||||
}
|
||||
|
||||
const defaultConfig: RateLimitConfig = {
|
||||
retryOnRateLimit: true,
|
||||
maxRetries: 3,
|
||||
backoffMultiplier: 2,
|
||||
maxBackoffSeconds: 60
|
||||
};
|
||||
```
|
||||
|
||||
### UI Rate Limit Display
|
||||
|
||||
The Web UI displays rate limit status in the console header with:
|
||||
- Current remaining requests
|
||||
- Time until reset
|
||||
- Visual indicator when approaching limit (< 20% remaining)
|
||||
|
||||
## Changelog
|
||||
|
||||
| Date | Version | Change |
|
||||
|------|---------|--------|
|
||||
| 2025-12-07 | 1.0.0 | Initial contract definition |
|
||||
|
||||
## References
|
||||
|
||||
- [API Governance Baseline](./api-governance-baseline.md)
|
||||
- [Web Gateway Architecture](../modules/gateway/architecture.md)
|
||||
- [Policy Engine Rate Limiting](../modules/policy/design/rate-limiting.md)
|
||||
## Changelog
|
||||
|
||||
| Date | Version | Change |
|
||||
|------|---------|--------|
|
||||
| 2025-12-07 | 1.0.0 | Initial contract definition |
|
||||
|
||||
## References
|
||||
|
||||
- [API Governance Baseline](./api-governance-baseline.md)
|
||||
- [Web Gateway Architecture](../modules/gateway/architecture.md)
|
||||
- [Policy Engine Rate Limiting](../modules/policy/design/rate-limiting.md)
|
||||
67
docs/contracts/redaction-defaults-decision.md
Normal file
67
docs/contracts/redaction-defaults-decision.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Redaction Defaults Decision
|
||||
|
||||
**Decision ID:** DECISION-SECURITY-001
|
||||
**Status:** DEFAULT-APPROVED
|
||||
**Effective Date:** 2025-12-06
|
||||
**48h Window Started:** 2025-12-06T00:00:00Z
|
||||
|
||||
## Decision
|
||||
|
||||
Notification and export pipelines use **restrictive redaction defaults** that redact PII, secrets, and cryptographic keys.
|
||||
|
||||
## Rationale
|
||||
|
||||
1. Security-first approach minimizes data exposure risk
|
||||
2. Users can opt-in to less restrictive settings via configuration
|
||||
3. Aligns with GDPR and data minimization principles
|
||||
4. Consistent with existing Evidence Locker redaction patterns
|
||||
|
||||
## Default Redaction Rules
|
||||
|
||||
### Always Redacted (HIGH)
|
||||
- Private keys (RSA, ECDSA, Ed25519)
|
||||
- API keys and tokens
|
||||
- Passwords and secrets
|
||||
- Database connection strings
|
||||
- JWT tokens
|
||||
|
||||
### Redacted by Default (MEDIUM) - Opt-out available
|
||||
- Email addresses
|
||||
- IP addresses (external)
|
||||
- File paths containing usernames
|
||||
- Environment variable values (not names)
|
||||
|
||||
### Not Redacted (LOW)
|
||||
- Package names and versions
|
||||
- CVE identifiers
|
||||
- Severity scores
|
||||
- Public key fingerprints
|
||||
|
||||
## Configuration
|
||||
|
||||
```yaml
|
||||
# etc/notify.yaml
|
||||
redaction:
|
||||
level: restrictive # Options: permissive, standard, restrictive
|
||||
custom_patterns:
|
||||
- pattern: "INTERNAL_.*"
|
||||
action: redact
|
||||
```
|
||||
|
||||
## Impact
|
||||
|
||||
- Tasks unblocked: ~5
|
||||
- Sprint files affected: SPRINT_0170, SPRINT_0171
|
||||
|
||||
## Reversibility
|
||||
|
||||
To change redaction defaults:
|
||||
1. Update `docs/security/redaction-and-privacy.md`
|
||||
2. Get Security Guild sign-off
|
||||
3. Update configuration schemas
|
||||
4. Ensure backward compatibility
|
||||
|
||||
## References
|
||||
|
||||
- [Redaction and Privacy](../security/redaction-and-privacy.md)
|
||||
- [SPRINT_0170 Notifications](../implplan/SPRINT_0170_0001_0001_notifications_telemetry.md)
|
||||
467
docs/contracts/web-gateway-tenant-rbac.md
Normal file
467
docs/contracts/web-gateway-tenant-rbac.md
Normal file
@@ -0,0 +1,467 @@
|
||||
# Web Gateway Tenant RBAC Contract
|
||||
|
||||
**Contract ID:** CONTRACT-GATEWAY-RBAC-001
|
||||
**Status:** APPROVED
|
||||
**Effective Date:** 2025-12-07
|
||||
**Owners:** Gateway Guild, Authority Guild, Web UI Guild
|
||||
|
||||
## Overview
|
||||
|
||||
This contract defines the tenant isolation and role-based access control (RBAC) model for the StellaOps Web Gateway, ensuring consistent authorization across all API endpoints and UI components.
|
||||
|
||||
## Tenant Model
|
||||
|
||||
### Tenant Hierarchy
|
||||
|
||||
```
|
||||
Organization (Org)
|
||||
├── Tenant A
|
||||
│ ├── Project 1
|
||||
│ │ └── Resources...
|
||||
│ └── Project 2
|
||||
│ └── Resources...
|
||||
└── Tenant B
|
||||
└── Project 3
|
||||
└── Resources...
|
||||
```
|
||||
|
||||
### Tenant Identification
|
||||
|
||||
Tenants are identified through:
|
||||
|
||||
1. **JWT Claims:** `tenant_id` or `stellaops:tenant` claim
|
||||
2. **Header:** `X-Tenant-Id` header (for service-to-service)
|
||||
3. **Path Parameter:** `/tenants/{tenantId}/...` routes
|
||||
|
||||
### Tenant Resolution Priority
|
||||
|
||||
```
|
||||
1. Path parameter (explicit)
|
||||
2. JWT claim (authenticated user context)
|
||||
3. X-Tenant-Id header (service-to-service)
|
||||
4. Default tenant (configuration fallback)
|
||||
```
|
||||
|
||||
## Role Definitions
|
||||
|
||||
### Built-in Roles
|
||||
|
||||
| Role | Description | Scope |
|
||||
|------|-------------|-------|
|
||||
| `org:admin` | Organization administrator | Org-wide |
|
||||
| `org:reader` | Organization read-only access | Org-wide |
|
||||
| `tenant:admin` | Tenant administrator | Single tenant |
|
||||
| `tenant:operator` | Can modify resources within tenant | Single tenant |
|
||||
| `tenant:viewer` | Read-only access to tenant | Single tenant |
|
||||
| `project:admin` | Project administrator | Single project |
|
||||
| `project:contributor` | Can modify project resources | Single project |
|
||||
| `project:viewer` | Read-only project access | Single project |
|
||||
| `policy:admin` | Policy management | Tenant-wide |
|
||||
| `scanner:operator` | Scanner operations | Tenant-wide |
|
||||
| `airgap:admin` | Air-gap operations | Tenant-wide |
|
||||
|
||||
### Role Hierarchy
|
||||
|
||||
```
|
||||
org:admin
|
||||
├── org:reader
|
||||
├── tenant:admin
|
||||
│ ├── tenant:operator
|
||||
│ │ └── tenant:viewer
|
||||
│ ├── policy:admin
|
||||
│ ├── scanner:operator
|
||||
│ └── airgap:admin
|
||||
└── project:admin
|
||||
├── project:contributor
|
||||
└── project:viewer
|
||||
```
|
||||
|
||||
## Scopes
|
||||
|
||||
### OAuth 2.0 Scopes
|
||||
|
||||
| Scope | Description | Required Role |
|
||||
|-------|-------------|---------------|
|
||||
| `policy:read` | Read policies and profiles | `tenant:viewer` |
|
||||
| `policy:edit` | Create/modify policies | `policy:admin` |
|
||||
| `policy:activate` | Activate policies | `policy:admin` |
|
||||
| `scanner:read` | View scan results | `tenant:viewer` |
|
||||
| `scanner:execute` | Execute scans | `scanner:operator` |
|
||||
| `airgap:seal` | Seal/unseal environment | `airgap:admin` |
|
||||
| `airgap:status:read` | Read sealed mode status | `tenant:viewer` |
|
||||
| `airgap:verify` | Verify bundles | `tenant:operator` |
|
||||
| `export:read` | Read exports | `tenant:viewer` |
|
||||
| `export:create` | Create exports | `tenant:operator` |
|
||||
| `admin:users` | Manage users | `tenant:admin` |
|
||||
| `admin:settings` | Manage settings | `tenant:admin` |
|
||||
|
||||
### Scope Inheritance
|
||||
|
||||
Child scopes are automatically granted when parent scope is present:
|
||||
|
||||
```yaml
|
||||
scope_inheritance:
|
||||
"policy:edit": ["policy:read"]
|
||||
"policy:activate": ["policy:read", "policy:edit"]
|
||||
"scanner:execute": ["scanner:read"]
|
||||
"export:create": ["export:read"]
|
||||
"admin:users": ["admin:settings"]
|
||||
```
|
||||
|
||||
## Resource Authorization
|
||||
|
||||
### Resource Types
|
||||
|
||||
| Resource Type | Tenant Scoped | Project Scoped | Description |
|
||||
|--------------|---------------|----------------|-------------|
|
||||
| `risk_profile` | Yes | No | Risk scoring profiles |
|
||||
| `policy_pack` | Yes | No | Policy bundles |
|
||||
| `scan_result` | Yes | Yes | Scan outputs |
|
||||
| `export` | Yes | Yes | Export jobs |
|
||||
| `finding` | Yes | Yes | Vulnerability findings |
|
||||
| `vex_document` | Yes | Yes | VEX statements |
|
||||
| `sealed_mode` | Yes | No | Air-gap state |
|
||||
| `user` | Yes | No | Tenant users |
|
||||
| `project` | Yes | No | Projects |
|
||||
|
||||
### Authorization Rules
|
||||
|
||||
```yaml
|
||||
# authorization-rules.yaml
|
||||
rules:
|
||||
- resource: risk_profile
|
||||
actions:
|
||||
read:
|
||||
required_scopes: [policy:read]
|
||||
tenant_isolation: strict
|
||||
create:
|
||||
required_scopes: [policy:edit]
|
||||
tenant_isolation: strict
|
||||
update:
|
||||
required_scopes: [policy:edit]
|
||||
tenant_isolation: strict
|
||||
activate:
|
||||
required_scopes: [policy:activate]
|
||||
tenant_isolation: strict
|
||||
delete:
|
||||
required_scopes: [policy:edit]
|
||||
tenant_isolation: strict
|
||||
require_role: policy:admin
|
||||
|
||||
- resource: scan_result
|
||||
actions:
|
||||
read:
|
||||
required_scopes: [scanner:read]
|
||||
tenant_isolation: strict
|
||||
project_isolation: optional
|
||||
create:
|
||||
required_scopes: [scanner:execute]
|
||||
tenant_isolation: strict
|
||||
delete:
|
||||
required_scopes: [scanner:execute]
|
||||
tenant_isolation: strict
|
||||
require_role: scanner:operator
|
||||
|
||||
- resource: sealed_mode
|
||||
actions:
|
||||
read:
|
||||
required_scopes: [airgap:status:read]
|
||||
tenant_isolation: strict
|
||||
seal:
|
||||
required_scopes: [airgap:seal]
|
||||
tenant_isolation: strict
|
||||
require_role: airgap:admin
|
||||
audit: required
|
||||
unseal:
|
||||
required_scopes: [airgap:seal]
|
||||
tenant_isolation: strict
|
||||
require_role: airgap:admin
|
||||
audit: required
|
||||
```
|
||||
|
||||
## Tenant Isolation
|
||||
|
||||
### Strict Isolation
|
||||
|
||||
All data access is tenant-scoped by default:
|
||||
|
||||
```sql
|
||||
-- Example: All queries include tenant filter
|
||||
SELECT * FROM findings
|
||||
WHERE tenant_id = @current_tenant_id
|
||||
AND deleted_at IS NULL;
|
||||
```
|
||||
|
||||
### Cross-Tenant Access
|
||||
|
||||
Cross-tenant access is prohibited except:
|
||||
|
||||
1. **Organization admins** can access all tenants in their org
|
||||
2. **Internal services** with explicit `cross_tenant` scope
|
||||
3. **Aggregation endpoints** with `org:reader` role
|
||||
|
||||
### Isolation Enforcement Points
|
||||
|
||||
| Layer | Enforcement |
|
||||
|-------|-------------|
|
||||
| Gateway | Validates tenant claim, injects X-Tenant-Id |
|
||||
| Service | Applies tenant filter to all queries |
|
||||
| Database | Row-level security (RLS) policies |
|
||||
| Cache | Tenant-prefixed cache keys |
|
||||
|
||||
## JWT Claims
|
||||
|
||||
### Required Claims
|
||||
|
||||
```json
|
||||
{
|
||||
"sub": "user-uuid",
|
||||
"aud": ["stellaops-api"],
|
||||
"iss": "https://auth.stellaops.io",
|
||||
"exp": 1701936000,
|
||||
"iat": 1701932400,
|
||||
"stellaops:tenant": "tenant-uuid",
|
||||
"stellaops:org": "org-uuid",
|
||||
"stellaops:roles": ["tenant:operator", "policy:admin"],
|
||||
"scope": "policy:read policy:edit scanner:read"
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Claims
|
||||
|
||||
| Claim | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `stellaops:tenant` | string | Current tenant UUID |
|
||||
| `stellaops:org` | string | Organization UUID |
|
||||
| `stellaops:roles` | string[] | Assigned roles |
|
||||
| `stellaops:projects` | string[] | Accessible projects |
|
||||
| `stellaops:tier` | string | Rate limit tier |
|
||||
|
||||
## Gateway Implementation
|
||||
|
||||
### Authorization Middleware
|
||||
|
||||
```csharp
|
||||
// AuthorizationMiddleware.cs
|
||||
public class TenantAuthorizationMiddleware
|
||||
{
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
// 1. Extract tenant from JWT/header/path
|
||||
var tenantId = ResolveTenantId(context);
|
||||
|
||||
// 2. Validate tenant access
|
||||
if (!await ValidateTenantAccess(context.User, tenantId))
|
||||
{
|
||||
context.Response.StatusCode = 403;
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Set tenant context for downstream
|
||||
context.Items["TenantId"] = tenantId;
|
||||
context.Request.Headers["X-Tenant-Id"] = tenantId;
|
||||
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Scope Authorization
|
||||
|
||||
```csharp
|
||||
// ScopeAuthorization.cs
|
||||
public static class ScopeAuthorization
|
||||
{
|
||||
public static IResult? RequireScope(HttpContext context, string requiredScope)
|
||||
{
|
||||
var scopes = context.User.FindFirst("scope")?.Value?.Split(' ') ?? [];
|
||||
|
||||
if (!scopes.Contains(requiredScope) && !HasInheritedScope(scopes, requiredScope))
|
||||
{
|
||||
return Results.Problem(
|
||||
title: "Forbidden",
|
||||
detail: $"Missing required scope: {requiredScope}",
|
||||
statusCode: 403);
|
||||
}
|
||||
|
||||
return null; // Access granted
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Web UI Integration
|
||||
|
||||
### Route Guards
|
||||
|
||||
```typescript
|
||||
// route-guards.ts
|
||||
export const TenantGuard: CanActivateFn = (route, state) => {
|
||||
const auth = inject(AuthService);
|
||||
const requiredRoles = route.data['roles'] as string[];
|
||||
|
||||
if (!auth.hasAnyRole(requiredRoles)) {
|
||||
return inject(Router).createUrlTree(['/unauthorized']);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Usage in routes
|
||||
{
|
||||
path: 'policy/studio',
|
||||
component: PolicyStudioComponent,
|
||||
canActivate: [TenantGuard],
|
||||
data: { roles: ['policy:admin', 'tenant:admin'] }
|
||||
}
|
||||
```
|
||||
|
||||
### Scope-Based UI Elements
|
||||
|
||||
```typescript
|
||||
// rbac.directive.ts
|
||||
@Directive({ selector: '[requireScope]' })
|
||||
export class RequireScopeDirective {
|
||||
@Input() set requireScope(scope: string) {
|
||||
this.updateVisibility(scope);
|
||||
}
|
||||
|
||||
private updateVisibility(scope: string): void {
|
||||
const hasScope = this.auth.hasScope(scope);
|
||||
this.viewContainer.clear();
|
||||
if (hasScope) {
|
||||
this.viewContainer.createEmbeddedView(this.templateRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage in templates
|
||||
<button *requireScope="'policy:activate'">Activate Policy</button>
|
||||
```
|
||||
|
||||
## Audit Trail
|
||||
|
||||
### Audited Operations
|
||||
|
||||
All write operations are logged with:
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2025-12-07T10:30:00Z",
|
||||
"actor": {
|
||||
"userId": "user-uuid",
|
||||
"tenantId": "tenant-uuid",
|
||||
"roles": ["policy:admin"],
|
||||
"ipAddress": "192.168.1.100"
|
||||
},
|
||||
"action": "policy.activate",
|
||||
"resource": {
|
||||
"type": "policy_pack",
|
||||
"id": "pack-123",
|
||||
"version": 5
|
||||
},
|
||||
"outcome": "success",
|
||||
"details": {
|
||||
"previousStatus": "approved",
|
||||
"newStatus": "active"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sensitive Operations
|
||||
|
||||
These operations require enhanced audit logging:
|
||||
|
||||
- `sealed_mode.seal` / `sealed_mode.unseal`
|
||||
- `policy.activate`
|
||||
- `export.create` (with PII)
|
||||
- `user.role.assign`
|
||||
- `tenant.settings.modify`
|
||||
|
||||
## Configuration
|
||||
|
||||
### Gateway RBAC Configuration
|
||||
|
||||
```yaml
|
||||
# gateway/rbac.yaml
|
||||
rbac:
|
||||
enabled: true
|
||||
strictTenantIsolation: true
|
||||
allowCrossTenantForOrgAdmin: true
|
||||
|
||||
defaultRole: tenant:viewer
|
||||
defaultScopes:
|
||||
- policy:read
|
||||
- scanner:read
|
||||
|
||||
roleBindings:
|
||||
tenant:admin:
|
||||
scopes:
|
||||
- policy:read
|
||||
- policy:edit
|
||||
- policy:activate
|
||||
- scanner:read
|
||||
- scanner:execute
|
||||
- airgap:status:read
|
||||
- export:read
|
||||
- export:create
|
||||
- admin:users
|
||||
- admin:settings
|
||||
|
||||
policy:admin:
|
||||
scopes:
|
||||
- policy:read
|
||||
- policy:edit
|
||||
- policy:activate
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
### 401 Unauthorized
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "https://stellaops.org/problems/unauthorized",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Authentication required."
|
||||
}
|
||||
```
|
||||
|
||||
### 403 Forbidden
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "https://stellaops.org/problems/forbidden",
|
||||
"title": "Forbidden",
|
||||
"status": 403,
|
||||
"detail": "You do not have permission to access this resource.",
|
||||
"requiredScope": "policy:activate",
|
||||
"currentScopes": ["policy:read"]
|
||||
}
|
||||
```
|
||||
|
||||
### 404 Not Found (Tenant Isolation)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "https://stellaops.org/problems/not-found",
|
||||
"title": "Not Found",
|
||||
"status": 404,
|
||||
"detail": "Resource not found."
|
||||
}
|
||||
```
|
||||
|
||||
Note: 404 is returned instead of 403 for resources in other tenants to prevent enumeration attacks.
|
||||
|
||||
## Changelog
|
||||
|
||||
| Date | Version | Change |
|
||||
|------|---------|--------|
|
||||
| 2025-12-07 | 1.0.0 | Initial contract definition |
|
||||
|
||||
## References
|
||||
|
||||
- [Auth Scopes Documentation](../security/auth-scopes.md)
|
||||
- [RBAC Documentation](../security/scopes-and-roles.md)
|
||||
- [Tenancy Overview](../security/tenancy-overview.md)
|
||||
- [Rate Limit Design](./rate-limit-design.md)
|
||||
Reference in New Issue
Block a user