Access Control Model
|
|
| Status |
IMPLEMENTED (policy module, RLS, OIDC BFF, roles from role grants and identity-provider groups or app roles). With Entra ID, examples/terraform/azure/modules/entra creates the groups and app-role assignments (ADR-031). |
| Decision records |
ADR-013, ADR-014, ADR-022 |
| Enforcement |
API policy module (primary) + PostgreSQL RLS (defence in depth, tested T5–T9) |
1. Principals
| Principal |
Authenticates via |
Notes |
| Workforce user |
Your OpenID Connect identity provider (BFF); MFA and device policy enforced by the provider |
Roles from role grants (to people or identity-provider groups) and app roles in the token |
| Service client |
OIDC client credentials (bearer token), app roles |
e.g. Reports.Read for BI tooling |
| Platform processes |
Workload identity (managed identity / service account) |
One database login per process (aap_api_login, aap_worker_login, aap_hook_login, aap_reporting_login); scoped storage/secret roles |
| GitHub (webhooks) |
HMAC |
Can only enqueue |
| Break-glass |
A dedicated identity-provider account, monitored |
An alert in your SIEM on any use |
2. Roles and identity-provider groups
| Platform role |
Group (convention) |
Scope source |
| engineer |
aap-role-engineer (default for engineering staff) |
Team membership (Admin page, or a GitHub team linked to the unit) → owning team's apps |
| champion |
aap-role-champion-<team> |
Team |
| app_owner |
Derived from application.yaml owners (resolved identities) |
Owned apps |
| unit_lead |
aap-role-unit-lead-<unit> |
The unit's subtree (any level from config/organization.yaml) |
| appsec_engineer |
aap-role-appsec |
Org-wide |
| governance_analyst |
aap-role-governance |
Org-wide |
| security_leadership |
aap-role-security-leadership |
Org-wide |
| executive |
aap-role-executive |
Org-wide aggregates only |
| auditor |
aap-role-auditor (time-bound assignment) |
Org-wide read |
| platform_admin |
aap-role-platform-admin (just-in-time elevation recommended) |
Platform operations, no application data |
3. Permission matrix (excerpt; every operation declares its permission as x-permission in schemas/api/openapi.yaml)
Permission (x-permission) |
engineer |
champion |
app_owner |
unit_lead |
appsec_engineer |
governance_analyst |
security_leadership |
executive |
auditor |
platform_admin |
| application.read |
team |
team |
owned |
unit subtree |
org |
org |
org |
– |
org |
– |
| application.settings.write |
– |
team |
owned |
unit subtree |
– |
– |
– |
– |
– |
– |
| application.register |
– |
team |
– |
unit subtree |
org |
– |
– |
– |
– |
– |
| evidence.read |
team |
team |
owned |
unit subtree |
org |
org |
org |
– |
org |
– |
| evidence.raw.read |
– |
team |
– |
– |
org |
– |
org |
– |
org |
– |
| finding.read |
team |
team |
owned |
unit subtree |
org |
org |
org |
– |
org |
– |
| finding.transition (triage P3/P4) |
team |
team |
– |
– |
org |
– |
– |
– |
– |
– |
| finding.fp.confirm (P1/P2) |
– |
– |
– |
– |
org (≠ marker) |
– |
– |
– |
– |
– |
| finding.priority.override (approve) |
– |
– |
– |
– |
org (≠ requester) |
– |
– |
– |
– |
– |
| exception.request |
team |
team |
owned |
– |
– |
– |
– |
– |
– |
– |
| exception.decide |
– |
– |
co-sign T1/2 |
per threshold |
per threshold |
– |
per threshold |
– |
– |
– |
| classification.decide |
– |
– |
– |
– |
org (≠ requester, ∉ owning unit chain) |
– |
org |
– |
– |
– |
| assessment.create / assessment.answer |
– |
team |
– |
unit subtree |
org |
– |
– |
– |
– |
– |
| assessment.approve |
– |
– |
– |
– |
org (≠ lead assessor; M2 only if outside the owning unit chain, else M1) |
org (org-scope) |
org |
– |
– |
– |
| vulnerability.impact.read |
team scope |
team scope |
owned |
unit subtree |
org |
org |
org |
– |
org |
– |
| vulnerability.impact.export |
– |
– |
– |
unit subtree |
org |
org |
org |
– |
org |
– |
| maturity.rollup.read |
– |
– |
– |
unit subtree |
org |
org |
org |
org |
org |
– |
| report.org.read |
– |
– |
– |
– |
org |
org |
org |
org |
org |
– |
| report.export |
– |
– |
– |
unit subtree |
org |
org |
org |
– |
org |
– |
org.admin (units, membership, role grants; runs as aap_admin) |
– |
– |
– |
– |
– |
– |
– |
– |
– |
org (never self; never platform_admin) |
| catalogue.read |
✓ |
✓ |
✓ |
✓ |
✓ |
✓ |
✓ |
✓ |
✓ |
✓ |
| connector / job admin |
– |
– |
– |
– |
– |
– |
– |
– |
– |
✓ |
platform_admin has no default access to finding or evidence detail. Investigations needing data access use an auditor/appsec assignment, which is approved and logged.
4. Rules
| ID |
Rule |
| ACM-01 |
Deny by default. Every API operation declares x-permission (validated in CI). |
| ACM-02 |
Out-of-scope resources return 404 (no enumeration) |
| ACM-03 |
SoD: requester ≠ approver. M2 reviewer ∉ owning team. FP marker ≠ confirmer. Override requester ≠ approver. Classification decider ≠ the person who registered the application, and not a member of the owning unit, a unit below it, or a unit above it other than the organization root (IMPLEMENTED: services/onboarding.sod_problem; refusals are audited). Enforced in code and DB constraints where expressible. |
| ACM-04 |
Org-wide roles are reviewed quarterly (Admin → Role grants, plus your identity provider's access reviews). Auditor access is time-bound (≤ 90 days). |
| ACM-05 |
platform_admin is assigned in the identity provider, never in the platform. Just-in-time elevation (≤ 4 h, approval, justification), for example Entra PIM, is recommended; its activation log goes to your SIEM from the identity provider. |
| ACM-06 |
Database: each process connects with its own NOINHERIT login that can switch only to its own roles (the API: aap_api + the narrow sign-in role aap_auth; never aap_worker). The administrator login runs migrations only. Application roles have no UPDATE/DELETE on ledgers. Partitions are not directly accessible. Sessions are RLS-scoped per transaction. |
| ACM-07 |
Storage: only the worker writes evidence; the API never does. Short-lived download links for raw evidence files, issued after an authorization check, are planned. |
| ACM-08 |
Group membership and app roles are read from the token at sign-in, so removal from a group takes effect at the next sign-in: within 30 minutes of inactivity, and at most 8 hours (the absolute session limit). Role grants made on the Admin page take effect on the next request. |