Providers/Okta/Private/Import-OktaAppCredential.ps1
|
function Import-OktaAppCredential { <# .SYNOPSIS Reads back the service app credential written by Export-OktaAppCredential .DESCRIPTION Resolves whichever of the three storage modes the file records and returns a uniform object with the private JWK attached, so callers never branch on storage. Schema version 1 is still accepted. Those files hold the JWK as plain JSON, from before the credential was encrypted at rest, and refusing them would strand anybody who seeded an environment with an earlier build. They are read, used, and reported with a warning naming the command that upgrades them - a warning being the right level because the credential still works and the run should not stop for it. The shape is validated rather than trusted, because the failure mode of a truncated or hand-edited file is an opaque 401 from the token endpoint several calls later, and that is a bad place to start debugging from. .PARAMETER Path The credential file to read .PARAMETER VaultPassword Password used to unlock the SecretStore vault, when the credential lives in one .OUTPUTS PSCustomObject with orgUrl, clientId, appId, label, scopes, protection and privateJwk .EXAMPLE $credential = Import-OktaAppCredential -Path $path .NOTES Author: Jeffrey Stuhr Version: 2.0.0 Last Updated: 2026-08-07 #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Path, [Parameter()] [System.Security.SecureString]$VaultPassword ) if (-not (Test-Path -Path $Path)) { throw ("No service app credential at '$Path'. Run New-OktaServiceApp while " + 'connected with an SSWS token to create one.') } $json = [System.Text.Encoding]::UTF8.GetString([System.IO.File]::ReadAllBytes($Path)) # A UTF-8 BOM is legal in a file and illegal in JSON, and something else may have written # this file. Strip it rather than failing on it. $json = $json.TrimStart([char]0xFEFF) $credential = $null try { $credential = $json | ConvertFrom-Json } catch { throw "'$Path' is not valid JSON: $($_.Exception.Message)" } foreach ($required in @('orgUrl', 'clientId', 'appId')) { if (-not $credential.PSObject.Properties[$required] -or -not $credential.$required) { throw "'$Path' is missing the required '$required' field and cannot be used." } } # Absent on version 1 files, which predate encryption at rest. $protection = if ($credential.PSObject.Properties['protection']) { $credential.protection } else { 'None' } switch ($protection) { 'SecretStore' { if (-not $credential.vaultName -or -not $credential.secretName) { throw "'$Path' says the key is in a vault but does not name the vault or secret." } $vaultArgs = @{ VaultName = $credential.vaultName; SecretName = $credential.secretName } if ($VaultPassword) { $vaultArgs.VaultPassword = $VaultPassword } $jwkJson = Get-TestVaultSecret @vaultArgs } 'DPAPI' { if (-not $credential.privateJwkProtected) { throw "'$Path' is marked DPAPI-protected but carries no protected key." } $jwkJson = Unprotect-OktaSecret -Method 'DPAPI' -Value $credential.privateJwkProtected } default { # Version 1, or a host where DPAPI was unavailable at write time. if ($credential.PSObject.Properties['privateJwk'] -and $credential.privateJwk) { $jwkJson = $credential.privateJwk | ConvertTo-Json -Depth 10 -Compress if (-not $credential.PSObject.Properties['schemaVersion'] -or $credential.schemaVersion -lt 2) { Write-Warning ("'$Path' stores the private key unencrypted, in the format " + 'used before this module encrypted credentials at rest. Re-run ' + 'New-OktaServiceApp -Force with an SSWS token to replace it.') } } elseif ($credential.PSObject.Properties['privateJwkProtected']) { $jwkJson = $credential.privateJwkProtected } else { throw "'$Path' carries no private key in any recognised form." } } } $privateJwk = $null try { $privateJwk = $jwkJson | ConvertFrom-Json } catch { throw "The stored private key in '$Path' is not valid JSON: $($_.Exception.Message)" } return [PSCustomObject]@{ orgUrl = $credential.orgUrl clientId = $credential.clientId appId = $credential.appId label = $credential.label scopes = @($credential.scopes) protection = $protection vaultName = if ($credential.PSObject.Properties['vaultName']) { $credential.vaultName } else { $null } secretName = if ($credential.PSObject.Properties['secretName']) { $credential.secretName } else { $null } createdUtc = if ($credential.PSObject.Properties['createdUtc']) { $credential.createdUtc } else { $null } privateJwk = $privateJwk path = $Path } } |