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