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.
468 lines
11 KiB
Markdown
468 lines
11 KiB
Markdown
# 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)
|