public/Connect-MsecGraphSdk.ps1
|
function Connect-MsecGraphSdk { <# .SYNOPSIS Signs the Microsoft.Graph PowerShell SDK in using the msec session's token, so Get-Mg* commands run as the msec app without its private key ever leaving Key Vault. .DESCRIPTION Hands Connect-MgGraph the access token Connect-MsecServiceSession already holds. That is the only handoff that preserves msec's central property: the certificate's private key stays in Key Vault and signing happens there. THE USUAL CERTIFICATE ROUTE CANNOT WORK HERE, and that is the point. Connect-MgGraph -ClientId -TenantId -CertificateThumbprint needs the private key present on the machine. Anything that ships a PFX or a base64 certificate to a build agent is putting the key somewhere it can be copied; this command exists so that is never necessary. THE TOKEN IS NOT REFRESHED. Connect-MgGraph is given a static token, so the SDK cannot renew it - unlike msec's own commands, which re-acquire as needed. Tokens last about an hour. A script that runs longer must call this again; -MinimumMinutes is how a long report asserts it has enough time before it starts rather than failing in the middle. APP-ONLY, NEVER DELEGATED. There is no signed-in user, so anything /me-shaped fails by design - Get-MgContext reports AppOnly. And the SDK can only do what the app was consented for: msec's roles are all *.Read.All, so every New-Mg*, Update-Mg* and Remove-Mg* answers 403. That is a guarantee rather than a limitation. .PARAMETER MinimumMinutes Fail unless the token has at least this long left. Default 5. A long-running report should ask for the time it needs - a token with four minutes on it will connect happily and then start failing partway through the run, which is far harder to diagnose than a refusal up front. .PARAMETER PassThru Emit the resulting Graph context. .EXAMPLE Connect-Msec -KeyVaultName kv-msec -TenantId <guid> -ClientId <guid> Connect-MsecGraphSdk Get-MgUser -Top 5 .EXAMPLE # A report that will run for half an hour, refusing to start without the headroom. Connect-MsecGraphSdk -MinimumMinutes 30 Get-MgGroup -All | ForEach-Object { ... } .OUTPUTS With -PassThru, the Microsoft.Graph authentication context. .NOTES Needs Connect-Msec first, and the Microsoft.Graph.Authentication module - which is NOT a dependency of msec. It is imported only when this command is called, so the module installs and runs normally on a machine that has never heard of the Graph SDK. The cloud is taken from the msec session, not assumed. The SDK's environment names differ from Azure's (China, not AzureChinaCloud), so they are matched on the Graph endpoint itself rather than by name. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [ValidateRange(0, 60)] [int] $MinimumMinutes = 5, [switch] $PassThru ) Assert-MsecSession if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Authentication)) { throw 'Microsoft.Graph.Authentication is required for Connect-MsecGraphSdk. Install with: Install-Module Microsoft.Graph.Authentication -Scope CurrentUser' } Import-Module Microsoft.Graph.Authentication -ErrorAction Stop $resource = if ($script:MsecSession.Endpoints -and $script:MsecSession.Endpoints.GraphResource) { $script:MsecSession.Endpoints.GraphResource } else { 'https://graph.microsoft.com' } # Matched on the ENDPOINT, not the name. The SDK calls the Chinese cloud 'China' where Az # calls it 'AzureChinaCloud', so mapping by name would need a lookup table that goes stale # every time a sovereign cloud is added - and the endpoint is what actually has to agree. $environment = 'Global' try { $match = @(Get-MgEnvironment -ErrorAction Stop | Where-Object { $_.GraphEndpoint -and $_.GraphEndpoint.TrimEnd('/') -eq $resource.TrimEnd('/') }) if ($match.Count -eq 1) { $environment = $match[0].Name } elseif ($match.Count -gt 1) { $environment = $match[0].Name Write-Warning "$($match.Count) Graph SDK environments share the endpoint '$resource'; using '$environment'." } else { # Better to say so than to sign in silently against the wrong cloud, which fails # later as a wall of 401s that look like a permission problem. Write-Warning "No Microsoft.Graph SDK environment matches the endpoint '$resource'. Falling back to 'Global', which is probably wrong for this cloud." } } catch { Write-Warning "Could not list the Graph SDK environments, so 'Global' is assumed: $($_.Exception.Message)" } $token = Get-MsecAccessToken -Resource $resource # Checked AFTER acquisition, because acquiring is what refreshes a stale one - testing the # cache first would refuse over a token the very next line would have replaced. $expiry = $script:MsecSession.Tokens[$resource].ExpiresOn if ($expiry) { $left = [int] ($expiry - [DateTimeOffset]::UtcNow).TotalMinutes if ($left -lt $MinimumMinutes) { throw "The Graph token has $left minute(s) left, which is under the $MinimumMinutes requested. The SDK is handed a static token and cannot renew it, so a longer run would start working and then fail partway through. Run Connect-Msec again for a fresh token." } Write-Verbose "Graph token valid for about $left more minute(s)." } $secure = ConvertTo-SecureString -String $token -AsPlainText -Force Connect-MgGraph -AccessToken $secure -Environment $environment -NoWelcome -ErrorAction Stop Write-Verbose "Microsoft.Graph SDK connected to $environment as app $($script:MsecSession.ClientId) (app-only)." if ($PassThru) { Get-MgContext } } |