Files
git.stella-ops.org/docs/contracts/web-gateway-tenant-rbac.md
StellaOps Bot e53a282fbe
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
feat: Add native binary analyzer test utilities and implement SM2 signing tests
- 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.
2025-12-07 13:12:41 +02:00

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:

  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:

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:

  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

{
  "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.unseal
  • policy.activate
  • export.create (with PII)
  • user.role.assign
  • tenant.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

References