Provision the CLI service principal via Agent 365 on first tenant use - #481
Open
Krishnadheeraj (DheerajPannala) wants to merge 1 commit into
Open
Provision the CLI service principal via Agent 365 on first tenant use#481Krishnadheeraj (DheerajPannala) wants to merge 1 commit into
Krishnadheeraj (DheerajPannala) wants to merge 1 commit into
Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
In a newly onboarded tenant the CLI service principal does not exist yet, so setup fails at sign-in until an administrator creates it by hand. The CLI cannot create it itself: service principals are provisioned just in time only for applications that authenticate with a credential, and the CLI is a public client. The CLI now asks the Agent 365 service to provision it, once per tenant per process, from BootstrapConfigResolver.ResolveAsync - the shared entry point behind the setup, cleanup and publish flows. The call is best effort: it never throws, is deduplicated per tenant, and can be disabled with A365_DISABLE_SP_PROVISIONING=true. Requires a corresponding service-side change before it has any effect. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f8ba1b67-3303-43fd-935f-81941a9e9ef3
Krishnadheeraj (DheerajPannala)
force-pushed
the
kpannala/a365-cli-sp-provisioning
branch
from
July 28, 2026 20:48
46d0d82 to
669b6b5
Compare
Krishnadheeraj (DheerajPannala)
marked this pull request as ready for review
August 31, 2026 19:32
Copilot started reviewing on behalf of
Krishnadheeraj (DheerajPannala)
August 31, 2026 19:33
View session
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a best-effort, once-per-tenant-per-process bootstrap hook that asks the Agent 365 service (via the Power Platform gateway) to provision the CLI’s service principal on first use in a tenant, reducing manual admin setup for newly onboarded tenants.
Changes:
- Introduces
ServicePrincipalProvisioningServiceto POST to a tenant-scoped provisioning route using a delegated Power Platform token, with opt-out viaA365_DISABLE_SP_PROVISIONING. - Hooks provisioning into
BootstrapConfigResolver.ResolveAsyncso setup/cleanup/publish flows all benefit from the shared bootstrap entry point. - Adds constants for the provisioning base URL and route format, plus new unit tests and a
[Unreleased]CHANGELOG entry.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Services/ServicePrincipalProvisioningServiceTests.cs | Adds unit coverage for status mapping, request shape, dedupe, skipping, and opt-out. |
| src/Microsoft.Agents.A365.DevTools.Cli/Services/ServicePrincipalProvisioningService.cs | New service implementing best-effort provisioning call + in-flight dedupe. |
| src/Microsoft.Agents.A365.DevTools.Cli/Services/BootstrapConfigResolver.cs | Calls provisioning after config resolution when a tenant is known. |
| src/Microsoft.Agents.A365.DevTools.Cli/Program.cs | Registers the provisioning service in DI. |
| src/Microsoft.Agents.A365.DevTools.Cli/Constants/ConfigConstants.cs | Adds provisioning base URL + route constants and endpoint override helper. |
| CHANGELOG.md | Documents the new behavior and opt-out env var. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+137
to
+140
| var key = parsedTenantId.ToString(); | ||
|
|
||
| // One attempt per tenant per process; concurrent callers share the same attempt. | ||
| return _inFlight.GetOrAdd(key, _ => ProvisionAsync(parsedTenantId, userId, ct)); |
Comment on lines
+258
to
+273
| var status = payload?.Status switch | ||
| { | ||
| "Provisioned" => ServicePrincipalProvisioningStatus.Provisioned, | ||
| "AlreadyProvisioned" => ServicePrincipalProvisioningStatus.AlreadyProvisioned, | ||
| "Disabled" => ServicePrincipalProvisioningStatus.Skipped, | ||
| "Failed" => ServicePrincipalProvisioningStatus.Failed, | ||
| _ => ServicePrincipalProvisioningStatus.Provisioned, | ||
| }; | ||
|
|
||
| _logger.LogDebug( | ||
| "Agent 365 CLI service principal provisioning for tenant {TenantId} returned {Status}.", | ||
| tenantId, | ||
| status); | ||
|
|
||
| return new ServicePrincipalProvisioningResult( | ||
| status, payload?.ServicePrincipalObjectId, payload?.Detail); |
Comment on lines
+167
to
+171
| var authToken = await _authService.GetAccessTokenAsync( | ||
| PowerPlatformConstants.PowerPlatformApiIdentifierUri, | ||
| tenantId.ToString(), | ||
| userId: userId, | ||
| ct: ct); |
| **Option B — CLI** (`a365 setup admin`) has been removed in this release. Use Option A above, or copy the PowerShell instructions printed in the `a365 setup all` summary output. | ||
|
|
||
| ### Added | ||
| - The CLI now asks the Agent 365 service to register its own service principal the first time it runs against a tenant, removing a manual admin step that previously caused sign-in to fail in newly onboarded tenants. Set `A365_DISABLE_SP_PROVISIONING=true` to opt out. |
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.
Draft. Paired with a corresponding service-side change; this PR cannot function until that
ships, so please treat it as design review rather than merge-ready.
Problem
In a newly onboarded tenant, the CLI's service principal does not yet exist, so
a365 setupfails at sign-in until an administrator creates it by hand. That manual step is easy to miss and
the resulting error is not self-explanatory.
The CLI cannot create it itself: service principals are provisioned just-in-time only for
applications that authenticate with a credential, and the CLI is a public client by design.
Change
ServicePrincipalProvisioningServiceasks the Agent 365 service to perform the provisioning onthe CLI's behalf, and the CLI calls it the first time it operates against a tenant.
The call is made from
BootstrapConfigResolver.ResolveAsync, the shared entry point behind thesetup, cleanup and publish flows, so one insertion point covers every command.
Behaviour:
ConcurrentDictionaryofin-flight tasks, so concurrent callers share a single attempt
own errors; provisioning is a convenience, not a precondition
A365_DISABLE_SP_PROVISIONING=true, for CI and restricted environmentsToken used — feedback welcome
The request carries a delegated user token with audience
https://api.powerplatform.com.The client id falls through to
AuthenticationConstants.PowershellClientId, the existing defaultin
AuthenticationService. That is deliberate: the CLI cannot authenticate as itself here,because its service principal not yet existing is precisely the condition being resolved. Azure
PowerShell is present in every tenant, so it is usable before the CLI is.
Two review questions:
default? It is load-bearing here, and a silent default makes that invisible. I lean yes.
clean tenant? If not this returns
AADSTS65001, and the CLI would need a different actor.For context, this matches existing behaviour:
TeamsGraphBackendConfiguratorand other callersalso rely on the same default.
Testing
ServicePrincipalProvisioningServiceTestsCovered: status mapping, request shape (route,
api-version, bearer scheme), once-per-tenantdeduplication, invalid tenant ids skipping without any HTTP call,
403and transport failuresdegrading without throwing, and the opt-out switch.
Not covered: end-to-end. The service-side dependency is not deployed, so no part of this has
run against a live tenant. That is the main risk in this change.
Notes for reviewers
HttpClientandHttpResponseMessageare disposed viausingthroughoutA365_PROVISIONING_ENDPOINT,A365_PROVISIONING_ENDPOINT_{ENV})[Unreleased]