Infrastructure
Microsoft Entra Workload ID: diagnose Conditional Access before excluding a CI pipeline
A production runbook for qualifying a Conditional Access block on a workload identity with service principal logs, policy scope, risk, CI identity, KQL evidence, bounded exception and rollback.
A Conditional Access policy applied to workload identities can break a critical pipeline without involving any interactive user. The symptom usually looks like a CI outage: Terraform can no longer read Azure, an Azure DevOps release cannot retrieve a secret, a GitHub Actions job fails against Microsoft Graph, or an operations automation loses access exactly when it is supposed to repair production.
The reflex request is simple: exclude the service principal from the control. That may be necessary, but it should rarely be the first action. This runbook qualifies the block before adding an exception by separating policy, real identity, target resource, workload risk, sign-in evidence and rollback.
Frame the Identity Incident
Start by freezing the scope. A workload identity can represent an app registration, a service principal used by a pipeline, an OIDC federation or an automation connector. Managed identities and some applications do not always follow the same Conditional Access targeting rules; do not assume the name visible in the pipeline is the identity evaluated by Entra.
Incident to qualify
Calling system: CI pipeline, deployment job or automated runbook
Platform: Azure DevOps, GitHub Actions, AWX or internal orchestrator
Expected identity: spn-iac-prod-deploy
Credential type: OIDC federation, certificate or client secret
Called resource: Azure Resource Manager, Microsoft Graph, Key Vault or internal API
Observed error: access blocked due to Conditional Access policies
Recent change: new policy, new scope, credential change, egress IP change
Expected decision
Fix the policy
Fix the targeted identity
Add a bounded exception
Return to report-only
Roll back the policy deployment This framing prevents two common mistakes: excluding the wrong application, or disabling a broad protection to solve a local pipeline issue.
Prove Conditional Access Is the Blocker
The pipeline error is not enough. It may hide an expired secret, an invalid OIDC audience, a missing Graph permission or a network issue against the authentication endpoint. The evidence must come from Entra sign-in logs, on the service principal side.
let Lookback = 6h;
let AppName = "spn-iac-prod-deploy";
SigninLogs
| where TimeGenerated > ago(Lookback)
| where ServicePrincipalName == AppName or AppDisplayName == AppName
| project TimeGenerated,
AppDisplayName,
ServicePrincipalId,
ResourceDisplayName,
IPAddress,
ResultType,
ResultDescription,
ConditionalAccessStatus,
ConditionalAccessPolicies,
CorrelationId
| order by TimeGenerated desc Depending on your log routing, events may live in a dedicated service principal sign-in table rather than SigninLogs. The goal is unchanged: find the Conditional Access evaluation, the target resource and the CorrelationId that connects the pipeline run to Entra.
Read the Applied Policy, Not Only Its Name
A policy can apply because the identity is explicitly targeted, because an application group is included, or because a broad rule covers all tenant-owned workload identities. The Conditional Access tab in the sign-in log is often more reliable than a quick pass over the policy list.
Fields to capture from the sign-in log
Applied policy: name, id, active or report-only state
Result: success, failure, notApplied or reportOnlyFailure
Triggering condition: workload identity, location, risk, cloud resource, client app
Required control: block, require compliant network, equivalent grant control
Existing exclusions: service principal, directory, group, named location
Last modification: author, date, ticket or IaC commit
Security questions
Does the policy block the intended identity type?
Does the detected condition match the expected risk?
Should the CI identity have been in a pilot group?
Does a temporary exception have an expiry date and owner? If the policy is in report-only, it probably did not block the pipeline. If it is enforced and the sign-in carries the Conditional Access failure, move to targeting diagnostics.
Verify the Pipeline’s Effective Identity
The service connection name or secret name in the pipeline is not identity evidence. Capture the client ID, tenant, OIDC subject when applicable, then compare them with the service principal seen in the logs.
ci_identity:
pipeline: deploy-prod-network
provider: Azure DevOps or GitHub Actions
expected_client_id: 00000000-0000-0000-0000-000000000000
expected_tenant_id: 11111111-1111-1111-1111-111111111111
credential_type: federated_identity
oidc_subject: repo:platform/iac:environment:prod
entra_log_match:
service_principal_id: 22222222-2222-2222-2222-222222222222
app_display_name: spn-iac-prod-deploy
resource: Azure Resource Manager
conditional_access_status: failure
correlation_id: copy-from-signin-log
mismatch_checks:
- wrong tenant selected by tool
- old service connection still used by stage
- fallback client secret in variable group
- app registration duplicated for staging and prod
- federated credential subject not aligned with branch or environment If the effective identity is not the expected one, do not add an exclusion. Fix the pipeline, service connection, federation or variables that select the wrong principal first.
Separate Legitimate Block From Operational False Positive
A Conditional Access block can be healthy. A deployment identity used from an unknown location, a stolen credential, or an app calling an unexpected resource should remain blocked. The runbook must qualify risk before restoring the flow.
Probably legitimate block
Unknown egress IP or outside expected runner
Called resource outside pipeline scope
Rarely used service principal suddenly active
Failure from an unapproved country or network
Old credential, shared secret or untracked certificate
Multiple failures across different resources
Likely operational false positive
New policy rolled out without pilot group
Documented runner egress IP change
Recent OIDC migration with same repo and environment
Called resource matches the deployment runbook
Clear correlation between policy rollout and failures
No other workload risk signal The useful question is not “how do we make the pipeline pass?”. It is “which control can be relaxed without allowing a compromised identity to act?”.
Prepare a Bounded Exception
If the pipeline must resume, avoid a broad and permanent exclusion. An operable exception needs an owner, duration, identity scope, resource scope, evidence and rollback. The safest shape is often to limit it to the exact service principal and expected cloud resource, then move back to a corrected policy.
{
"exception": "ci-prod-deploy-workload-identity",
"reason": "Production deployment blocked by workload identity Conditional Access policy",
"identity_scope": {
"service_principal_id": "22222222-2222-2222-2222-222222222222",
"app_display_name": "spn-iac-prod-deploy"
},
"resource_scope": ["Azure Resource Manager"],
"allowed_conditions": {
"runner_egress": ["documented-nat-ip-or-named-location"],
"credential": "federated_identity_only",
"environment": "prod"
},
"expires_at": "2026-07-31T18:00:00Z",
"owner": "platform-security",
"evidence": ["signin_correlation_id", "pipeline_run_id", "policy_change_id"],
"rollback": "remove exclusion and restore policy enforcement"
} An exception without expiry quietly becomes parallel architecture. If it must last, turn it into a reviewed and tested rule, not a hidden hole in the policy.
Validate Before Replaying Production
Do not validate only with a green pipeline. Check that the sign-in used the expected path, that other resources remain blocked, and that the behavior is visible in logs.
let Lookback = 2h;
let ServicePrincipalId = "22222222-2222-2222-2222-222222222222";
SigninLogs
| where TimeGenerated > ago(Lookback)
| where ServicePrincipalId == ServicePrincipalId
| project TimeGenerated,
AppDisplayName,
ResourceDisplayName,
IPAddress,
ResultType,
ResultDescription,
ConditionalAccessStatus,
ConditionalAccessPolicies,
CorrelationId
| summarize attempts=count(), failures=countif(ResultType != 0), resources=make_set(ResourceDisplayName, 10), ips=make_set(IPAddress, 10), caStates=make_set(ConditionalAccessStatus, 10) by bin(TimeGenerated, 15m)
| order by TimeGenerated desc Add a negative test: same identity from an unauthorized source, or same source against an unexpected resource. If everything passes, the exception is too broad.
Decide Fix Or Rollback
The final decision must be readable by operations, not only by the identity team. Record what was restored, what remains blocked, and what must be removed after the incident.
Allow temporarily
The block is confirmed as workload identity Conditional Access
The effective identity matches the expected service principal
The called resource is in the pipeline scope
No workload risk signal is observed
The exception is bounded by service principal, resource, source and expiry
The negative test proves the exception does not open everything
Fix without exception
The pipeline uses the wrong identity
OIDC federation does not match the environment
The policy targets the wrong pilot group
The called resource is not expected
The policy is report-only and is not the real blocker
Roll back
The block follows an unvalidated policy rollout
Several critical workloads are cut outside intended scope
Logs cannot explain the evaluation
The required exception would be too broad
A compromised credential signal exists Rollback can mean removing the policy, returning it to report-only, deleting the exception or temporarily switching to a prevalidated break-glass identity. It must be tested as a production action, not improvised in the portal.
Conclusion
Conditional Access for workload identities is a strong control when it remains explainable. It becomes dangerous when the team no longer knows which identity was evaluated, which policy blocked it, and which exception restores only the expected flow.
Before excluding a CI pipeline, prove the block in sign-in logs, align the effective identity, qualify risk, bound the exception and validate with a negative test. The best outcome is not simply making the job pass; it is making the right job pass, with the right identity, and a rollback already ready.