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.
11 KiB
11 KiB
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:
- JWT Claims:
tenant_idorstellaops:tenantclaim - Header:
X-Tenant-Idheader (for service-to-service) - 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:
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
# 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:
-- 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:
- Organization admins can access all tenants in their org
- Internal services with explicit
cross_tenantscope - Aggregation endpoints with
org:readerrole
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
{
"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
// 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
// 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
// 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
// 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:
{
"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.unsealpolicy.activateexport.create(with PII)user.role.assigntenant.settings.modify
Configuration
Gateway RBAC Configuration
# 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
{
"type": "https://stellaops.org/problems/unauthorized",
"title": "Unauthorized",
"status": 401,
"detail": "Authentication required."
}
403 Forbidden
{
"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)
{
"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 |