feat(lwpreflight): validate Entra ID directory roles in Azure preflight - #1859
Draft
lokesh-vadlamudi wants to merge 7 commits into
Draft
feat(lwpreflight): validate Entra ID directory roles in Azure preflight#1859lokesh-vadlamudi wants to merge 7 commits into
lokesh-vadlamudi wants to merge 7 commits into
Conversation
Discovery previously checked only ARM RBAC (and skipped even that for
Owner/Contributor callers), so a service principal missing Application
Administrator or Privileged Role Administrator sailed through preflight
with Errors: {} and then failed deployment on the azuread resources.
The ARM access token's wids claim already lists the caller's active
directory role template IDs, so no Graph call or new dependency is
needed. New CheckDirectoryRoles task requires:
- Application Administrator (or Cloud Application Administrator or
Global Administrator) whenever an Entra ID application is created:
always for agentless, and for config/activity log unless an existing
AD application is reused
- Privileged Role Administrator (or Global Administrator) for config/
activity log, which assign the Directory Readers role to the new app
Params.UseExistingAdApplication lets callers waive the config/activity
log checks when reusing an existing AD application, exposed on the CLI
as lacework preflight azure --existing-ad-application. The human output
relabels Admin as Subscription Owner/Contributor and adds the directory
role count.
The trailing sentence made each message ~200 chars and wrapped badly in the deployment wizard's error banner without telling the user anything they act on differently. The missing role name is the actionable part.
The directory-role check read only the wids claim, which lists directory roles. An app-only principal can hold the equivalent Microsoft Graph application permission instead, so a caller with Application.ReadWrite.OwnedBy and RoleManagement.ReadWrite.Directory was blocked even though deployment would have succeeded. Graph application permissions arrive in the roles claim of a token issued for Graph, never in the ARM token the caller check already decodes, so the credential is asked for a second token. That is a token request, not a Graph API call, and needs no permission of its own. The accepted set stays narrow, and Directory.ReadWrite.All is left out on purpose: a missing entry costs a false failure the caller can fix by assigning a directory role, while a wrong entry waves a caller through to a deployment that then fails. When the Graph token cannot be read at all the check falls back to directory roles and says so in the error, since in silent or JSON output the verbose line goes nowhere.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Azure preflight only checked ARM RBAC on the subscription, and skipped even that for Owner/Contributor callers (
IsAdmin). Entra ID directory roles were never checked, so a service principal missing Application Administrator or Privileged Role Administrator passed discovery withErrors: {}and then failed deployment on theazureadTerraform resources.Fixes CAD-2145.
Change
The ARM access token already decoded for
oid/tidalso carries awidsclaim: the template IDs of directory roles actively assigned to the caller. The newCheckDirectoryRolestask reads it and runs unconditionally because subscription Owner/Contributor is orthogonal to directory roles.Per requested integration type:
Application.ReadWrite.OwnedBy/Application.ReadWrite.AllGraph permission)RoleManagement.ReadWrite.DirectoryGraph permission)Agentless always creates its own Entra ID application but never assigns a directory role. Config and Activity Log create an application and assign Directory Readers to it, which is what needs Privileged Role Administrator.
Params.UseExistingAdApplicationis keyed by integration type, so mixed requests are represented correctly: reusing an app for Config does not waive checks for an Activity Log integration that creates one, or vice versa. The CLI keeps--existing-ad-applicationas shorthand for both and also exposes--config-existing-ad-applicationand--activity-log-existing-ad-applicationfor mixed runs.Either mechanism satisfies a requirement. Entra ID grants these capabilities two ways, and
widssees only the first: an app-only principal can hold a Microsoft Graph application permission instead of a directory role, and those arrive in therolesclaim of a token issued for Graph, never in the ARM token the caller check already decodes. So the credential is asked for a second token. That is a token request, not a Graph API call, so it needs no permission of its own, and it succeeds for a principal with no Graph grants at all, returning an emptyrolesclaim rather than an error.The accepted permission set is deliberately narrow.
Directory.ReadWrite.Allis documented as sufficient forazuread_directory_role_assignmentbut is left out: a missing entry costs a false failure the caller can fix by assigning a directory role, while a wrong entry waves a caller through to a deployment that then fails. When the Graph token cannot be read at all, the check falls back to directory roles alone and appends that to the error, because in silent or JSON output the verbose line goes nowhere.No new dependency, no Graph API call, and no additional caller permission is required.
Verification
widsandrolesparsing, each Graph permission alone, a capability satisfied by a directory role while the other is satisfied by a Graph permission, and the unreadable-permissions message.--agentless --config --activity-log--existing-ad-application--agentless --config --activity-log--agentless --config --activity-logThe first row is the ticket scenario: subscription Contributor, no directory roles, previously
Errors: {}. A fresh SP carries one baseline directory role in its token (Directory Readers), which satisfies none of the checks.The Graph path was checked separately against the dev tenant. Both tokens were minted from the same client credentials and decoded: the ARM token and the Graph token carry the same
wids, and neither carriesroles, since that principal holds its privileges as directory roles. The branch CLI reportsDirectory roles: 3andGraph application permissions: 0and passes all three integrations. What is not covered live is a positive Graph-only run, which needsApplication.ReadWrite.Alladmin-consented to a throwaway principal; the unit tests cover that path.Known limitations
Preflight validates what a service principal can hold: Entra ID directory roles and Microsoft Graph application permissions. That is the whole picture for onboarding, because self-deployment discovery always passes a client ID and secret, and CLI onboarding never runs preflight at all. Only
lacework preflight azurewithout--client-idfalls back to the signed-in user, and there the Application Developer role and the tenant default that lets any user register applications also permit app creation and are not recognized here.One gap remains for a service principal, and it fails in the safe direction, reporting a requirement the caller would in fact have met: a PIM-eligible role that has not been activated does not appear in
wids, so an administrator who has not activated is treated as not holding the role.Follow-ups (separate PRs)
use_existing_ad_applicationvalue from the discovery request intoazure.Params.Related PRs
CAD-2145 ships across three repos. Merge order is go-sdk, then services, then rainbow.
UseExistingAdApplicationparams and CLI flags.use_existing_ad_applicationfrom the discovery request into the preflight params.