Skip to main content

Challenge 03: Entra Agent ID + RBAC in Standard Mode

Scenario Brief

Industry: Enterprise SaaS | Regulatory Context: Zero Trust, NIST AI RMF GOVERN 1.2
Time Estimate: 60 minutes | Azure Cost: ~$2–4


What's at Stake​

Contoso Corp is preparing their AI agent for a SOC 2 Type II audit. The security team's finding:

"All 12 agents share a single service principal with Contributor access on the entire subscription. If one agent is compromised, the attacker has write access to all production databases."

You need to implement Entra Agent ID β€” each agent gets its own managed identity with least-privilege RBAC β€” before the audit in 6 weeks.


Skills Practiced​

  • Understanding Entra Agent ID (per-agent managed identity)
  • Assigning granular RBAC roles to individual agents
  • Configuring Standard mode with BYO Key Vault + Storage
  • Auditing agent identity activity in Azure Monitor

Architecture Decision​

ApproachRiskVerdict
Single service principal (shared)Blast radius = entire subscription❌ Reject
User-assigned managed identity (shared)Better, but still shared across agents⚠️ Insufficient
Entra Agent ID (per-agent)Each agent gets its own identity + RBAC scopeβœ… Required

🧰 Before You Start β€” Environment Setup​

This challenge is a least-privilege identity build: replace one over-privileged shared principal with per-agent managed identities scoped to exactly what each agent needs.

Prerequisites​

RequirementWhy you need itHow to check
Azure subscription + ability to assign RBACCreate identities and scope rolesaz account show
Azure CLIProvision agents, inspect role assignmentsaz version
Microsoft Entra rights to manage identitiesEntra Agent ID = per-agent managed identityEntra admin center
Azure AI Foundry Standard mode projectStandard mode enables BYO + per-agent identityAzure portal
A scoped data resource (e.g. a storage container)Somewhere to grant a narrow role and test the denyaz storage account list

Step 0 β€” Sign in and check current exposure (5 min)​

Where you run this: in a terminal with the Azure CLI signed in β€” unlike the local-only Python challenges, these az commands run against real resources in your Azure subscription.

az login
# See how broad your current agent principal is β€” this is the problem you're fixing:
az role assignment list --assignee <current-sp-id> -o table

βœ… Done when the list prints β€” note any broad roles (e.g. Contributor, Storage Blob Data Contributor at subscription scope). That breadth is your blast radius.

Step 1 β€” Decide the least-privilege map, then enable Entra Agent ID (10 min) β€” the "where do I go"​

First, write down each agent β†’ the single narrowest role it needs (e.g. Storage Blob Data Reader on one container). Least privilege is a design decision, not an afterthought:

AgentResourceNarrowest role
Reader agentcontainer-reportsStorage Blob Data Reader
Writer agentcontainer-draftsStorage Blob Data Contributor

Then enable per-agent identity. Entra Agent ID gives each agent its own managed identity automatically when the project runs in Standard mode:

  1. In ai.azure.com, confirm your project is Standard mode (hub-based) β€” see agent identity concepts.
  2. Follow the Entra Agent ID guided setup to view each agent's identity in the Entra admin center.
  3. Assign each identity only its row from the table above with az role assignment create --scope <resource-id> --role "<role>" --assignee <agent-identity-id>.

βœ… Done when each agent identity appears in Entra and az role assignment list --assignee <agent-id> -o table shows exactly one narrowly-scoped role.

🟦 Microsoft-first note: this is a pure Microsoft identity exercise β€” Microsoft Entra Agent ID, Azure RBAC scoped roles, BYO Key Vault + Storage, and Azure Monitor for identity auditing. No third-party IAM is involved.

Common fixes: no per-agent identity β†’ project is not Standard mode. AuthorizationFailed assigning roles β†’ you need Owner or User Access Administrator on the target resource scope.

The path through this challenge​

  1. Task 1 β€” enable Standard mode with BYO Key Vault + Storage.
  2. Task 2 β€” give each agent its own Entra Agent ID.
  3. Task 3 β€” assign the narrowest RBAC role per agent.
  4. Task 4 β€” audit identity activity in Azure Monitor.
  5. Success Criteria β€” prove an agent can read but cannot write (403).
  6. Adapt to Your Business β€” apply per-agent identity to your fleet.

⏱️ Time budget: ~60 minutes. The 403 write-deny test (Success Criteria) is the proof that least privilege actually holds.


Your Tasks​

Task 1: Understand Entra Agent ID​

When you create a Hosted Agent in Standard mode, Foundry automatically provisions an Entra Agent ID β€” a system-assigned managed identity tied to that specific agent instance.

from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential

client = AIProjectClient(
endpoint=os.environ["PROJECT_ENDPOINT"],
credential=DefaultAzureCredential()
)

# Create the agent β€” Entra Agent ID is auto-provisioned
agent = client.agents.create_agent(
model="gpt-4o",
name="contoso-invoice-processor",
instructions="Process invoices from Azure Blob Storage. Read only from the invoices container.",
)

print(f"Agent ID: {agent.id}")
print(f"Entra Agent ID (Principal ID): {agent.identity.principal_id}")
print(f"Tenant ID: {agent.identity.tenant_id}")

Task 2: Assign Least-Privilege RBAC​

# Only grant Storage Blob Data Reader on the specific container
# NOT Contributor on the subscription

AGENT_PRINCIPAL_ID=$(az ai agent show \
--agent-id <agent-id> \
--project-name contoso-project \
--query "identity.principalId" -o tsv)

STORAGE_ACCOUNT_ID=$(az storage account show \
--name stcontosoai \
--resource-group rg-contoso-ai \
--query id -o tsv)

# Read-only on invoices container only
az role assignment create \
--assignee-object-id $AGENT_PRINCIPAL_ID \
--assignee-principal-type ServicePrincipal \
--role "Storage Blob Data Reader" \
--scope "$STORAGE_ACCOUNT_ID/blobServices/default/containers/invoices"

echo "Agent can now read from invoices container only"

Task 3: Verify Identity in Agent Code​

# The agent automatically uses its Entra Agent ID when calling Azure services
# No credentials in code β€” identity is resolved by the Foundry runtime

from azure.storage.blob import BlobServiceClient
from azure.identity import ManagedIdentityCredential

def read_invoice(blob_name: str) -> str:
"""Agent uses its own managed identity β€” no shared secrets."""
# ManagedIdentityCredential resolves to the agent's Entra Agent ID at runtime
credential = ManagedIdentityCredential()

blob_client = BlobServiceClient(
account_url="https://stcontosoai.blob.core.windows.net",
credential=credential
).get_blob_client(container="invoices", blob=blob_name)

return blob_client.download_blob().readall().decode("utf-8")

Task 4: Audit Agent Identity Activity​

# Query Azure Monitor for agent identity activity
az monitor activity-log list \
--caller $AGENT_PRINCIPAL_ID \
--start-time 2025-01-01 \
--output table \
--query "[].{Time:eventTimestamp, Operation:operationName.value, Status:status.value, Resource:resourceId}"

Task 5: Implement Access Review​

# List all role assignments for this agent
az role assignment list \
--assignee $AGENT_PRINCIPAL_ID \
--all \
--output table \
--query "[].{Role:roleDefinitionName, Scope:scope}"

Success Criteria​

  • Agent created and identity.principal_id is populated (not null)
  • Agent's managed identity has Storage Blob Data Reader on invoices container only β€” NOT broader scope
  • Agent can successfully read from the invoices container
  • Agent cannot write to storage (test: az storage blob upload using agent identity should fail with 403)
  • Role assignment audit shows no Contributor or Owner roles

πŸ” Adapt This to Your Own Business​

The scenario is a SOC 2 audit, but any organization running multiple agents on a shared, over-privileged identity has the same blast-radius problem. Per-agent identity + least-privilege RBAC is a Zero Trust baseline everywhere.

Step 1 β€” Find your shared-identity blast radius​

Organization typeThe shared-identity riskWhat a compromise reaches
Enterprise SaaSOne SP for all agents/servicesEvery prod database
Financial servicesShared automation identityPayment + customer systems
HealthcareBroad app registrationAll PHI stores
RetailShared integration principalOrders, payments, inventory
Any regulated orgContributor/Owner on the subscriptionEverything, on one leak

Step 2 β€” Map the building blocks to your stack (Microsoft-first)​

In this challengeIn your project β€” use
Per-agent identityMicrosoft Entra Agent ID (managed identity per agent)
Narrow permissionsAzure RBAC roles scoped to a single resource
Secret/storage isolationBYO Key Vault + Storage in Standard mode
Identity auditingAzure Monitor + Entra sign-in / audit logs
Policy enforcementAzure Policy to forbid Owner/Contributor on agents

Step 3 β€” The 5-question implementation checklist​

  1. Do multiple agents share one identity? If yes β†’ that's your blast radius; split them.
  2. Does any agent have Contributor/Owner? If yes β†’ replace with a data-plane role scoped to one resource.
  3. Is the role scoped to a resource, not the subscription? Scope down to the container/database.
  4. Can you prove an agent is denied out-of-scope actions? A 403 on write is your evidence.
  5. Are identity actions audited? If not β†’ send Entra + resource logs to Azure Monitor.

Step 4 β€” A 1-week rollout plan​

DayActionOwner
Day 1Inventory every agent and its current permissionsSecurity
Day 2Map each agent to its single least-privilege roleSecurity + eng
Day 3Create per-agent Entra Agent IDsCloud eng
Day 4Assign scoped RBAC; run the 403 deny testCloud eng
Day 5Wire identity audit logs; add an Azure Policy guardSRE + governance

Step 5 β€” Prove the ROI​

  • Blast-radius reduction β€” max resources reachable by any one identity (target: 1 scope).
  • Over-privilege count β€” agents with Owner/Contributor (target: 0).
  • Deny verification β€” out-of-scope actions return 403 (target: 100%).

πŸ’‘ Rule of thumb: if one leaked credential can reach everything, you don't have an agent-security problem β€” you have an identity-architecture problem. One identity per agent, one narrow role each.

Doing this solo (no team, portfolio-first)​

No team, no budget? Least-privilege agent identity is pure Zero-Trust judgment β€” and Entra + RBAC cost nothing to demo. Run the week solo:

  • Mon–Tue β€” inventory your agents and map each to exactly one least-privilege role.
  • Wed–Thu β€” create per-agent Entra Agent IDs (or managed identities) and assign resource-scoped RBAC.
  • Fri β€” run the deny test: attempt an out-of-scope action and screenshot the 403.

πŸ“¦ Ship this artifact: a least-privilege role map (table/diagram) + the 403-on-out-of-scope screenshot. Resume bullet: "Cut agent blast radius to a single resource scope β€” 0 over-privileged identities, 100% of out-of-scope actions denied (verified 403)."

πŸ†“ Free-tier path: Entra ID and Azure RBAC are free; one free-tier storage or Cosmos resource is enough to prove the deny.


πŸ“‹ Regulatory mapping β€” NIST Β· SOC 2 Β· Zero Trust
RequirementRegulationEnforcement
Least privilegeNIST AI RMF GOVERN 1.2Per-agent RBAC scoped to minimum required resources
Non-repudiationSOC 2 CC6.1Each action logged under unique agent principal ID
Identity separationZero Trust principleEntra Agent ID per agent β€” no shared identities
Access reviewsSOC 2 CC6.3Quarterly RBAC audit via az role assignment list

πŸ’‘ Hints (try to solve first)
  1. Entra Agent ID is only available in Standard mode: In Basic mode, agents share Foundry's system identity. Another reason to always use Standard.
  2. --assignee-principal-type ServicePrincipal: Always specify this flag when assigning roles to managed identities β€” it avoids unnecessary Azure AD lookups and prevents role assignment errors.
  3. Container-scoped RBAC: The scope pattern is {storageAccountId}/blobServices/default/containers/{containerName}. This is more restrictive than account-level and is what auditors expect.
  4. Test the negative: Explicitly test that the agent cannot write to storage. Security controls are only meaningful if you verify the deny path too.

Knowledge Check​

  1. What is the difference between Entra Agent ID and a user-assigned managed identity?
  2. Why is per-agent identity isolation important from a security blast radius perspective?
  3. What RBAC role would you assign if an agent needs to read from AND write to Azure Cosmos DB?
  4. How do you verify that an agent's managed identity was used for a specific storage operation (vs. a human operator)?

Cleanup​

# Remove role assignment first, then delete agent
az role assignment delete --assignee $AGENT_PRINCIPAL_ID --role "Storage Blob Data Reader"
# Delete agent via SDK or portal