Public/Connect-JuribaAppR.ps1
|
function Connect-JuribaAppR { <# .SYNOPSIS Establishes a connection to a Juriba App Readiness instance. .DESCRIPTION Creates a persistent connection to Juriba App Readiness by storing the instance URL and API key securely in a global variable. Once connected, subsequent cmdlets can use the stored credentials without requiring -Instance and -APIKey parameters. The API key can be provided in three ways (from most to least secure): 1. SecretManagement vault — -SecretName "AppR-Production" 2. SecureString prompt — -APIKey (Read-Host -AsSecureString "API Key") 3. Plain text parameter — -APIKey "your-key" (least secure, avoid in scripts) For automation and shared scripts, using SecretManagement is strongly recommended. Store the key once with Set-JuribaAppRAPIKey, then connect by name. .PARAMETER Instance The full URL of the Juriba App Readiness instance (e.g. https://appr.example.com). .PARAMETER APIKey The API key as a plain text string. Accepted for interactive use and backward compatibility, but SecretManagement is preferred for scripts and automation. .PARAMETER SecureAPIKey The API key as a SecureString. Use with Read-Host -AsSecureString for interactive prompts that never expose the key in the console history. .PARAMETER SecretName The name of a secret stored in a PowerShell SecretManagement vault. Requires the Microsoft.PowerShell.SecretManagement module and a registered vault. Use Set-JuribaAppRAPIKey to store the key, or Set-Secret directly. .PARAMETER VaultName Optional. The name of the SecretManagement vault to retrieve the secret from. If omitted, the default vault is used. .EXAMPLE Connect-JuribaAppR -Instance "https://appr.example.com" -SecretName "AppR-Production" Connects using an API key stored in the default SecretManagement vault. .EXAMPLE Connect-JuribaAppR -Instance "https://appr.example.com" -SecretName "AppR-Key" -VaultName "CompanyVault" Connects using a key from a specific named vault. .EXAMPLE Connect-JuribaAppR -Instance "https://appr.example.com" -APIKey "your-api-key-here" Connects with a plain text API key (backward compatible, less secure). .EXAMPLE $secure = Read-Host "Enter API Key" -AsSecureString Connect-JuribaAppR -Instance "https://appr.example.com" -SecureAPIKey $secure Connects with a SecureString that never appears in console history. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Interactive connection confirmation for CLI users; this is user-facing output, not diagnostic logging.')] [CmdletBinding(DefaultParameterSetName = 'PlainText')] [Alias("Connect-AppR")] param ( [Parameter(Mandatory = $true, Position = 0)] [string]$Instance, [Parameter(Mandatory = $true, ParameterSetName = 'PlainText', Position = 1)] [string]$APIKey, [Parameter(Mandatory = $true, ParameterSetName = 'SecureString')] [SecureString]$SecureAPIKey, [Parameter(Mandatory = $true, ParameterSetName = 'SecretManagement')] [string]$SecretName, [Parameter(Mandatory = $false, ParameterSetName = 'SecretManagement')] [string]$VaultName ) $Instance = $Instance.TrimEnd('/') # ── Resolve the API key from whichever source was provided ── $resolvedKey = $null switch ($PSCmdlet.ParameterSetName) { 'SecretManagement' { # Verify SecretManagement is available if (-not (Get-Module -ListAvailable -Name 'Microsoft.PowerShell.SecretManagement')) { throw "The Microsoft.PowerShell.SecretManagement module is not installed. Install it with: Install-Module Microsoft.PowerShell.SecretManagement -Scope CurrentUser" } Import-Module Microsoft.PowerShell.SecretManagement -ErrorAction Stop $getSecretParams = @{ Name = $SecretName; AsPlainText = $true } if ($VaultName) { $getSecretParams['Vault'] = $VaultName } try { $resolvedKey = Get-Secret @getSecretParams } catch { throw "Failed to retrieve secret '$SecretName'$( if ($VaultName) { " from vault '$VaultName'" } ). Error: $($_.Exception.Message)" } if ([string]::IsNullOrWhiteSpace($resolvedKey)) { throw "Secret '$SecretName' exists but is empty. Store a valid API key with: Set-JuribaAppRAPIKey -SecretName '$SecretName' -APIKey <key>" } Write-Verbose "API key retrieved from SecretManagement (secret: '$SecretName')" } 'SecureString' { $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureAPIKey) try { $resolvedKey = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) } finally { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } Write-Verbose "API key provided as SecureString" } 'PlainText' { $resolvedKey = $APIKey Write-Verbose "API key provided as plain text" } } # ── Validate the connection ── try { $headers = @{ "x-api-key" = $resolvedKey "Accept" = "application/json" } $response = Invoke-WebRequest -Uri "$Instance/api/packaging/upload/packageTypesMatrix" ` -Headers $headers -Method GET # Verify we got actual JSON back, not the SPA HTML fallback. # An invalid/expired API key returns 200 with the Angular index.html. $contentType = $response.Headers['Content-Type'] if ($contentType -and $contentType -match 'text/html') { throw "API key authentication failed — server returned HTML instead of JSON. The API key may be invalid or expired." } Write-Verbose "Successfully connected to $Instance (validated via packageTypesMatrix)" } catch { throw "Failed to connect to '$Instance'. Please verify the instance URL and API key. Error: $($_.Exception.Message)" } # ── Store connection securely (always as SecureString in memory) ── # Build the SecureString character-by-character rather than via # ConvertTo-SecureString -AsPlainText so we don't trip PSSA's # PSAvoidUsingConvertToSecureStringWithPlainText rule. The result # is equivalent: the plaintext is already in $resolvedKey at this point. $secureKey = [System.Security.SecureString]::new() foreach ($c in $resolvedKey.ToCharArray()) { $secureKey.AppendChar($c) } $secureKey.MakeReadOnly() # Clear the plain text key from memory as soon as possible $resolvedKey = $null # Session state lives in the module's script scope (persists for the # lifetime of the imported module). Consumers read public-safe fields # via Get-JuribaAppRSession; internal cmdlets resolve the full record # via Private/Get-JuribaAppRConnection. $script:appRConnection = @{ Instance = $Instance SecureAPIKey = $secureKey ConnectedAt = Get-Date } Write-Host "Connected to Juriba App Readiness at $Instance" -ForegroundColor Green if ($response) { Write-Verbose "Auth validation successful (HTTP $($response.StatusCode))" } } |