public/Save-MsecContext.ps1
|
function Save-MsecContext { <# .SYNOPSIS Remembers the current sessions under a name, so Set-MsecContext can put them all back together later. .DESCRIPTION SNAPSHOTS WHAT IS CONNECTED RIGHT NOW, which is why it takes almost no parameters. Connect everything the way you want it - Azure context, Connect-Msec, and Connect-MsecAdmin if you write in this tenant - then name it. Typing the subscription, vault and admin account again by hand is how they drift apart. THE ADMIN ACCOUNT IS THE POINT, not an extra. Connect-MgGraph cannot choose an identity; the browser reuses whichever it remembers. With two tenants and two identities the wrong one is picked silently, and surfaces much later as a write into the tenant you did not mean, or as AADSTS500213 - a cross-tenant policy error that names nothing about accounts. Recording the account here lets Set-MsecContext refuse a mismatch instead of discovering it afterwards. NO SECRET IS STORED. Subscription, tenant, cloud, vault name, client id, certificate name and a UPN - the same things you would type on a command line. msec signs with a key that never leaves Key Vault, so there is nothing here worth having. Saved at the cache ROOT, not under a tenant folder: a named context exists in order to pick a tenant, so it cannot be filed under one. .PARAMETER Name What to call it. Re-saving an existing name overwrites it. .PARAMETER AdminAccount The account the write session must use in this context. Defaults to whatever Connect-MsecAdmin is currently signed in as, or to nothing if there is no write session - in which case Set-MsecContext will not try to establish one. .PARAMETER Force Overwrite an existing context of the same name without asking. .EXAMPLE Connect-Msec -KeyVaultName kv-prod Connect-MsecAdmin Save-MsecContext -Name prod The usual flow: get it right once, then name it. .EXAMPLE Get-MsecContext -List What has been saved. .OUTPUTS The saved context, PSTypeName 'MsecSavedContext'. #> [CmdletBinding(SupportsShouldProcess)] [OutputType([PSCustomObject])] param( [Parameter(Mandatory, Position = 0)] [ValidatePattern('^[A-Za-z0-9][A-Za-z0-9 ._-]*$')] [string] $Name, [string] $AdminAccount, [switch] $Force ) $az = Get-AzContext -ErrorAction SilentlyContinue if (-not $az) { throw 'No Azure context to save. Run Connect-AzAccount, then Set-AzContext -Subscription <name> to select the one you want saved - a context without a subscription cannot be switched back to.' } if (-not $script:MsecSession) { throw 'No msec app session to save. Run Connect-Msec -KeyVaultName <vault> first, so the saved context knows which vault to reconnect with.' } $store = Read-MsecContextStore if ($store.Contains($Name) -and -not $Force -and -not $PSCmdlet.ShouldContinue("A context named '$Name' already exists. Replace it?", 'Save-MsecContext')) { return } # Defaults to the live write session. Absent is meaningful: it means this context does not # write, and Set-MsecContext will not start a sign-in for it. $admin = if ($PSBoundParameters.ContainsKey('AdminAccount')) { $AdminAccount } elseif ($script:MsecAdminSession) { [string] $script:MsecAdminSession.Account } else { $null } # THE SESSION DOES NOT CARRY THE CERTIFICATE NAME - it holds the Key Vault KEY name # resolved from the certificate's metadata, which is a different thing and not what # Connect-Msec takes. The tenant profile has it; absent, Connect-Msec's own default # applies, so an empty value here is recorded as empty rather than guessed at. $tp = Get-MsecTenantProfile -TenantId ([string] $script:MsecSession.TenantId) $certName = if ($tp -and $tp.CertificateName) { [string] $tp.CertificateName } else { $null } $ctx = [PSCustomObject]@{ PSTypeName = 'MsecSavedContext' Name = $Name SubscriptionId = [string] $az.Subscription.Id Subscription = [string] $az.Subscription.Name AzAccount = [string] $az.Account.Id # THE CLOUD, because Azure needs a separate sign-in per cloud and switching between # two contexts in different ones fails with an Az error that never mentions the cloud. Environment = [string] $az.Environment.Name TenantId = [string] $script:MsecSession.TenantId KeyVaultName = [string] $script:MsecSession.KeyVaultName ClientId = [string] $script:MsecSession.ClientId CertificateName = $certName AdminAccount = $admin SavedUtc = [datetime]::UtcNow.ToString('yyyy-MM-dd HH:mm:ss') } if ($PSCmdlet.ShouldProcess($Name, 'Save msec context')) { $store[$Name] = $ctx $null = Write-MsecContextStore -Contexts $store } $ctx } |