Skip to content

Fix/azure tenancy - #96

Merged
ritamAN77 merged 11 commits into
authsec-stagingfrom
fix/azure-tenancy
Oct 20, 2026
Merged

ritamAN77 merged 11 commits into
authsec-stagingfrom
fix/azure-tenancy

Conversation

@ritamAN77

Copy link
Copy Markdown
Contributor

Description

Changes

Testing

  • Unit tests pass (go test -short ./tests/unit/)
  • go vet ./... clean
  • Manual testing (describe below)
  • Add label run-integration to this PR to trigger integration tests in CI (requires a live DB)

Checklist

  • Code follows the project's style guidelines
  • Self-reviewed the diff
  • No secrets or credentials committed
  • Updated documentation if needed
  • Added/updated tests for new behaviour

amansingh39 and others added 11 commits October 7, 2026 10:46
One AuthSec multi-tenant app registration, and the customer only sees
Microsoft's own screens: admin consent creates AuthSec's service
principal and grants its Graph application permissions; a delegated
sign-in (PKCE, no refresh token) gives the administrator's ARM token,
parked in Redis for at most 15 minutes. Connect then checks the service
principal and its Graph permissions (granted, then usable), checks the
signed-in user may bind the tenant, creates Reader on each selected
subscription WITH THE ADMINISTRATOR'S TOKEN -- AuthSec's principal never
holds roleAssignments/write -- and proves each one with AuthSec's own
app-only token, polling while Azure applies it.

A tenant is one cloud_connector row (scope_kind 'tenant', migration 042)
and each subscription one row under it. Verify re-proves either without
the customer; Revoke stops using a tenant and its subscriptions. No
credential is stored: auth_ref is "azure-app:<client id>" and the secret
stays in the deployment environment.

Resource Manager and app-only tokens go through the official Azure SDK
(azcore, azidentity, armsubscriptions, armauthorization), Graph through
the Microsoft Graph SDK. Discovery is not part of this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… their access

Tenant and subscription discovery passes: Entra objects, Microsoft Entra role assignments and definitions (built-in, custom, scoped), app and delegated permissions, Azure RBAC, Key Vault, federation, sign-ins and activity, Foundry, workloads. Agents from Microsoft Entra Agent ID, the Microsoft 365 agent registry, Foundry agent applications and project agents, Azure Bot Service, Logic Apps agent loops and the Teams app catalog, plus inference, each listed once and closed when its source no longer lists it. Scheduled rescans, custom-app onboarding with credentials in Vault, and projection into the shared iga_* graph behind IGA_GRAPH_PROJECTION.
Azure migrations renumbered after staging's (042-044 -> 138-140); the duplicate sortedKeys helper dropped in favour of staging's.
138-140 now leave a constraint that already admits their value as it is, so on a database that ran them under their old numbers (042-044), or whose bootstrap already lists every value, they never narrow it under rows a later source wrote.
The Azure projection states each row's scope in provider_attrs as Kubernetes and GitHub rows do (the tenant for Entra objects; the subscription, with its resource group or region, for resources and workloads), so /api/iga/v1/inventory lists provider azure with scope, native id and as_of from the Azure discovery run. /authsec/discovery/connections lists Azure tenants and subscriptions, with their latest run, coverage and a live graph state.
The shared connections list gives an Azure tenant as the connection and counts its subscriptions in its scope summary instead of listing each one. Every Azure inventory row is scoped to its tenant, with a resource's or workload's subscription as its sub-scope and its subscription id and resource group in its attrs, so filtering by the tenant connection includes them.
Own app: the client secret is stored only after every read it enables is proven, and destroyed on revoke or when the tenant moves to the AuthSec app. Its failures are the customer's (400, credential_invalid, unknown tenant, app not in tenant, named missing permissions), never AuthSec's deployment. Warnings for Graph permissions that can change data, Azure roles beyond Reader, and no readable subscription. A tenant switching between apps moves its other subscriptions to the new credentials. Verify checks revoked first; a passing Microsoft failure no longer stops scheduled discovery; discovery fails closed when the own app's credentials cannot be read; unexpected errors no longer return internals.
…ing took 138-140)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d joins, isolation tests

Brings feat/azure-multitenant-onboarding (PR #92) in line with the tenancy
rules on authsec-staging (CLAUDE.md §4, §7 AS-108):

- Every Azure statement runs in a transaction under RLS for one workspace
  (tenancy.InWorkspace / InWorkspaceRead): the Azure repositories, the
  graph reads, the twelve Azure services, and the graph projection (one
  transaction per pass). The 18 files are listed in
  scripts/tenant-rls-files.txt. Exempt, with reasons: the scheduler's scan
  of due tenants across workspaces, the run lease renew/finish fenced by run
  id and owner, and the controller's service constructor.
- Tenant ratchet back to 72 (was 104). RLS guard: no violations.
- Every join on an id now also matches the workspace (projection, graph
  reads, repository lists).
- IGA isolation check passes: cloud_permission, cloud_secret and
  cloud_resource are read-only seams of the Azure collected model the
  projection reads (ALLOWED_SHARED, with the reason; an ARM resource id
  names its subscription, so the AWS cloud_resource collision does not
  arise).
- Tests (tests/integration/flows/isolation_azure_test.go):
  Test_Isolation_AzureDiscovery -- workspace A gets 404 on every
  /azure/connectors/:id route of B, cannot revoke B's connector, and never
  sees B's rows in any list or graph read; B sees its own (control).
  Test_AzureProjectionUnderRLS -- a tenant and a subscription pass project
  under RLS as authsec_tenant and write only their own workspace's graph.

Behaviour notes: agent registration, inference, retirement and sign-in
origin attribution now commit per step (all or nothing) instead of row by
row; evidence is written in batches of 50 per transaction.

Schema parity: identical except idx_cloud_scan_schedule_tenancy_ws, which
plain authsec-staging misses too (staging's 139, not this branch).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An access policy on an Azure resource deploys as role assignments
(migration 144, iga_gov_azure_access, job azure_access):
- allow: AuthSec creates a built-in role assignment for the principal on
  the resource and deletes it at the end;
- block: AuthSec records and removes the principal's own assignments on the
  resource that grant the access, and restores them on End/Pause/expiry.
Access from a broader scope is a preview blocker (Azure has no deny).
Same routes, views and statuses as AWS; RLS on the new table; the file is
in the RLS list. TestAccessPolicyAzure covers block, allow and inherited.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ritamAN77
ritamAN77 merged commit 903defe into authsec-staging Oct 20, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants