Challenge 03: Entra Agent ID + RBAC in Standard Mode
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β
| Approach | Risk | Verdict |
|---|---|---|
| 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β
| Requirement | Why you need it | How to check |
|---|---|---|
| Azure subscription + ability to assign RBAC | Create identities and scope roles | az account show |
| Azure CLI | Provision agents, inspect role assignments | az version |
| Microsoft Entra rights to manage identities | Entra Agent ID = per-agent managed identity | Entra admin center |
| Azure AI Foundry Standard mode project | Standard mode enables BYO + per-agent identity | Azure portal |
| A scoped data resource (e.g. a storage container) | Somewhere to grant a narrow role and test the deny | az 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:
| Agent | Resource | Narrowest role |
|---|---|---|
| Reader agent | container-reports | Storage Blob Data Reader |
| Writer agent | container-drafts | Storage 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:
- In ai.azure.com, confirm your project is Standard mode (hub-based) β see agent identity concepts.
- Follow the Entra Agent ID guided setup to view each agent's identity in the Entra admin center.
- 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.
AuthorizationFailedassigning roles β you need Owner or User Access Administrator on the target resource scope.
The path through this challengeβ
- Task 1 β enable Standard mode with BYO Key Vault + Storage.
- Task 2 β give each agent its own Entra Agent ID.
- Task 3 β assign the narrowest RBAC role per agent.
- Task 4 β audit identity activity in Azure Monitor.
- Success Criteria β prove an agent can read but cannot write (403).
- 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_idis populated (not null) - Agent's managed identity has
Storage Blob Data Readeroninvoicescontainer only β NOT broader scope - Agent can successfully read from the
invoicescontainer - Agent cannot write to storage (test:
az storage blob uploadusing 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 type | The shared-identity risk | What a compromise reaches |
|---|---|---|
| Enterprise SaaS | One SP for all agents/services | Every prod database |
| Financial services | Shared automation identity | Payment + customer systems |
| Healthcare | Broad app registration | All PHI stores |
| Retail | Shared integration principal | Orders, payments, inventory |
| Any regulated org | Contributor/Owner on the subscription | Everything, on one leak |
Step 2 β Map the building blocks to your stack (Microsoft-first)β
| In this challenge | In your project β use |
|---|---|
| Per-agent identity | Microsoft Entra Agent ID (managed identity per agent) |
| Narrow permissions | Azure RBAC roles scoped to a single resource |
| Secret/storage isolation | BYO Key Vault + Storage in Standard mode |
| Identity auditing | Azure Monitor + Entra sign-in / audit logs |
| Policy enforcement | Azure Policy to forbid Owner/Contributor on agents |
Step 3 β The 5-question implementation checklistβ
- Do multiple agents share one identity? If yes β that's your blast radius; split them.
- Does any agent have Contributor/Owner? If yes β replace with a data-plane role scoped to one resource.
- Is the role scoped to a resource, not the subscription? Scope down to the container/database.
- Can you prove an agent is denied out-of-scope actions? A 403 on write is your evidence.
- Are identity actions audited? If not β send Entra + resource logs to Azure Monitor.
Step 4 β A 1-week rollout planβ
| Day | Action | Owner |
|---|---|---|
| Day 1 | Inventory every agent and its current permissions | Security |
| Day 2 | Map each agent to its single least-privilege role | Security + eng |
| Day 3 | Create per-agent Entra Agent IDs | Cloud eng |
| Day 4 | Assign scoped RBAC; run the 403 deny test | Cloud eng |
| Day 5 | Wire identity audit logs; add an Azure Policy guard | SRE + 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
| Requirement | Regulation | Enforcement |
|---|---|---|
| Least privilege | NIST AI RMF GOVERN 1.2 | Per-agent RBAC scoped to minimum required resources |
| Non-repudiation | SOC 2 CC6.1 | Each action logged under unique agent principal ID |
| Identity separation | Zero Trust principle | Entra Agent ID per agent β no shared identities |
| Access reviews | SOC 2 CC6.3 | Quarterly RBAC audit via az role assignment list |
π‘ Hints (try to solve first)
- Entra Agent ID is only available in Standard mode: In Basic mode, agents share Foundry's system identity. Another reason to always use Standard.
--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.- Container-scoped RBAC: The scope pattern is
{storageAccountId}/blobServices/default/containers/{containerName}. This is more restrictive than account-level and is what auditors expect. - 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β
- What is the difference between Entra Agent ID and a user-assigned managed identity?
- Why is per-agent identity isolation important from a security blast radius perspective?
- What RBAC role would you assign if an agent needs to read from AND write to Azure Cosmos DB?
- 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