Skip to content

SecOps Least Privilege Audit — LPRIV-001, LPRIV-002, LPRIV-003

Audit of SecOps platform app registration permissions, managed identity RBAC, and PostgreSQL access. Completed April 2026 as part of the LPRIV least privilege workstream.


LPRIV-001 — SecOps App Registration Graph Permissions

App: cirius-findings-tracker (App ID: cc42af53-8eef-4705-a1d6-227c3313175d)

Date audited: April 15, 2026

The cirius-findings-tracker app registration is used by the SecOps Container App (ca-secops-prod) for Azure AD SSO (MSAL PKCE) and for group membership lookups that drive role authorization.

Graph Permissions

PermissionTypeFeature / Code PathStatus
User.ReadDelegated (Scope)MSAL PKCE sign-in flow — reads the signed-in user's basic profile✅ Required
GroupMember.Read.AllApplication (Role)RBAC — checks whether the signed-in user is a member of the SecOps Entra group (ffdf1dcb) to determine access level✅ Required
User.Read.AllApplication (Role)Known-good rule attribution — resolves Entra object IDs to UPNs when displaying rule approvers in the UI✅ Required

Finding: 3 permissions assigned (not 19+ as estimated at story creation). All 3 have clear, documented owners. No orphaned permissions detected.

Verdict: PASS — permissions are minimal and appropriate.


LPRIV-001b — SecOps Bot App Registration Graph Permissions

App: Cirius SecOps Bot (App ID: 7a3ea16d-a903-4252-9203-1159136a7eac)

This is a separate app registration used by the Teams bot only — not the SecOps web app.

PermissionTypeFeature / Code PathStatus
User.Read.AllApplication (Role)/policies and /accept bot commands — resolves Teams aadObjectId → Entra UPN via Graph GET /users/{id}✅ Required

Verdict: PASS — single permission, clear owner.


LPRIV-002 — SecOps Managed Identity RBAC

Identity: id-secops-prod (Principal ID: a0d94b27-21ab-48d0-8d98-9b0fe915cb3d) Client ID: 3e2ed6a2-d2d7-4aec-a289-a6ea493ad93aResource group: rg-logging-logs

Date audited: April 15, 2026

RBAC Role Assignments

RoleScope TypeResourceFeatureStatus
AcrPullResourceciriusagentsprod (Container Registry)Container App image pull at startup✅ Required
Key Vault Secrets UserResourcecirius-openai-kv-prod (Key Vault)Runtime secret access (API keys, DB URL, bot tokens)✅ Required

Findings:

  • No Contributor or Owner assignments detected.
  • Both roles are scoped to the specific resource, not resource group or subscription.
  • AcrPull is the minimum role for image pull — no push access.
  • Key Vault Secrets User is read-only (GET secret versions) — no set/delete/manage.

Verdict: PASS — RBAC posture is minimal and correctly scoped.


LPRIV-003 — SecOps PostgreSQL Access Role

Database: psql-secops-prod.postgres.database.azure.com (Azure PostgreSQL Flexible Server) Database name: secops

Date audited: April 15, 2026

Finding: Application Connects as Database Administrator

The SecOps Container App connects to psql-secops-prod using the administrator login (secopsadmin) — the same account set as administrator_login in Terraform. This is the highest-privilege database account.

Connection string (from Key Vault secops-db-url):

postgresql+asyncpg://secopsadmin:***@psql-secops-prod.postgres.database.azure.com/secops?ssl=require

Risk: If the Container App is compromised, an attacker has full database administrator access — including the ability to read all tables, drop tables, create new superuser accounts, or exfiltrate all data. The principle of least privilege requires the application to connect as a role with only the permissions it needs.

Minimum Required Permissions

Based on the SecOps application's SQLAlchemy models and API endpoints:

Schema ObjectRequired Operations
All application tables (incidents, findings, known_good, changes, etc.)SELECT, INSERT, UPDATE, DELETE
public schemaUSAGE, CREATE (for Alembic migrations)
Sequences (for auto-increment IDs)USAGE, SELECT
No access neededpg_catalog system tables beyond what implicit grants cover

Remediation Required

Create a scoped secops_app PostgreSQL role and update the connection string. Rory's sign-off required before implementation — this change requires:

  1. Connecting to psql-secops-prod as secopsadmin
  2. Running CREATE ROLE, GRANT, ALTER DEFAULT PRIVILEGES
  3. Updating secops-db-url secret in Key Vault with the new credentials
  4. Restarting ca-secops-prod to pick up the new connection string

Verdict: FAIL — application connects as database administrator. Remediation tracked in LPRIV-003.

Proposed Remediation SQL

sql
-- Create scoped application role
CREATE ROLE secops_app WITH LOGIN PASSWORD '<strong-random-password>';

-- Grant schema usage
GRANT USAGE, CREATE ON SCHEMA public TO secops_app;

-- Grant DML on all existing tables
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO secops_app;

-- Grant sequence usage (for auto-increment PKs)
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO secops_app;

-- Ensure future tables created by Alembic are accessible
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO secops_app;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO secops_app;

After running, update Key Vault secret secops-db-url with the new credentials and restart the Container App revision.



LPRIV-004 — Key Vault Access Policy Scope

Identity: id-secops-prod via Key Vault Secrets User on cirius-openai-kv-prod

Date audited: April 15, 2026

Built-in Role: Key Vault Secrets User

Azure RBAC role definition (Key Vault Secrets User) grants exactly two data actions:

Data ActionScope
Microsoft.KeyVault/vaults/secrets/getSecret/actionGet secret value
Microsoft.KeyVault/vaults/secrets/readMetadata/actionRead secret metadata

No access to:

  • set / delete / purge / backup / restore on secrets
  • Keys (encrypt, decrypt, sign, verify, wrap, unwrap)
  • Certificates (get, list, import, create, delete)

Finding: The managed identity has read-only access to secrets only. No key, certificate, or write operations are permitted.

Verdict: PASS — Key Vault access is minimal and correctly scoped.


LPRIV-005 — Table Storage Access

Date audited: April 15, 2026

The SecOps security agents use Azure Table Storage (ciriussecuritymemprod, table SecurityMemory) for IOC deduplication and memory. The web app (ca-secops-prod) does not access Table Storage.

Two managed identities exist in the SecOps platform:

IdentityUsed ByPurpose
id-secops-prodca-secops-prod Container App (web app)Web app identity — KV secrets, ACR pull
id-agents-prodContainer App Jobs (orchestrator, AWS, DDE)Agent identity — LAW, Table Storage, Blob, OpenAI

id-agents-prod RBAC Assignments (Agent Identity)

RoleScopeFeature
Key Vault Secrets Usercirius-openai-kv-prod (resource)Agent runtime secrets (API keys, tokens)
Log Analytics Readercirius-logging-law-central (resource)KQL queries across all security agents
AcrPullciriusagentsprod (resource)Container image pull for agent jobs
Storage Table Data Contributorciriussecuritymemprod (storage account)IOC memory table — SecurityMemory
Storage Blob Data Contributorciriusarchiveprod (storage account)Evidence archive uploads
Cognitive Services OpenAI Usercirius-openai-prod (resource)SecurityAgent / LLM triage
ReaderLogging subscriptionAzure resource discovery for agent inventory
Security ReaderLogging subscriptionDefender for Cloud findings

Table Storage Finding

Storage Table Data Contributor on ciriussecuritymemprod is the most specific scope achievable in Azure RBAC — Azure does not support per-table role assignments. Since ciriussecuritymemprod is a dedicated storage account whose sole purpose is the SecurityMemory table, this is effectively table-scoped.

Storage Table Data Contributor is a data plane role. It grants entity-level operations only — it does NOT grant management plane operations like deleting the storage account or modifying its firewall rules.

Finding: id-secops-prod (web app) has no Table Storage access — not applicable. id-agents-prod has Storage Table Data Contributor on the dedicated ciriussecuritymemprod account — correctly scoped, not broad Storage Account Contributor.

Verdict: PASS for both identities — Table Storage access is minimal and correctly scoped.


Summary

StoryComponentResultAction
LPRIV-001cirius-findings-tracker Graph permissions✅ PASS — 3 permissions, all requiredNo action needed
LPRIV-001Cirius SecOps Bot Graph permissions✅ PASS — 1 permission, requiredNo action needed
LPRIV-002id-secops-prod managed identity RBAC✅ PASS — 2 roles, minimal scopeNo action needed
LPRIV-003PostgreSQL access role❌ FAIL — connecting as DB adminCreate scoped secops_app role
LPRIV-004Key Vault access scope (id-secops-prod)✅ PASS — Key Vault Secrets User (read-only secrets)No action needed
LPRIV-005Table Storage access✅ PASS — Storage Table Data Contributor on dedicated accountNo action needed

Document History

DateChangeAuthor
April 2026Added LPRIV-004 (Key Vault scope) and LPRIV-005 (Table Storage) — both PASS; discovered and documented id-agents-prod identity with 8 RBAC rolesKobe
April 2026Initial audit — LPRIV-001, LPRIV-002, LPRIV-003Kobe

Internal use only — Cirius Group