public/Connect-Unraid.ps1
|
function Connect-Unraid { <# .SYNOPSIS Connects to an Unraid server via Web Session or API Key. .DESCRIPTION Establishes a session with an Unraid server. Supports legacy web-login (scraping CSRF) and API Key. .PARAMETER ComputerName Server hostname or IP address. .PARAMETER Credential Username and password. .PARAMETER ProvideAPIKey Prompts for an API key. .PARAMETER UseStoredAuth Uses saved credentials/key from local storage. .PARAMETER SkipCertificateCheck Skips SSL validation (useful for self-signed) certs. .PARAMETER NoDefaultSession Returns the session object but doesn't set it as the default session. .PARAMETER SaveAuth Saves auth details for reuse. .OUTPUTS [UnraidSession] #> [CmdletBinding(DefaultParameterSetName = 'Credential')] [OutputType('UnraidSession')] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline)] [string]$ComputerName, [Parameter(Mandatory, Position = 1, ParameterSetName = 'Credential')] [pscredential]$Credential, [Parameter(Mandatory, ParameterSetName = 'ApiKey')] [switch]$ProvideAPIKey, [Parameter(Mandatory, ParameterSetName = 'StoredAuth')] [switch]$UseStoredAuth, [Parameter()] [switch]$SkipCertificateCheck, [Parameter()] [switch]$NoDefaultSession, [Parameter()] [switch]$SaveAuth ) process { # Normalize the URL schema if ($ComputerName -notmatch "^http(s)?://") { $baseUrl = "https://$ComputerName" } else { $baseUrl = $ComputerName $ComputerName = $ComputerName -replace "^http(s)?://", "" } $graphqlUrl = "$baseUrl/graphql" $loginUrl = "$baseUrl/login" # Check where we're getting auth info from if ($UseStoredAuth) { Write-Verbose "Checking local storage for $ComputerName..." $storedAuth = Get-UnraidAuth -ServerName $ComputerName if (!$storedAuth) { throw "No stored auth for '$ComputerName'. You need to connect once with -SaveAuth first." } if ($storedAuth.AuthType -eq 'ApiKey') { $ApiKey = $storedAuth.ApiKey } else { $Credential = $storedAuth.Credential } } elseif ($ProvideAPIKey) { $secureKey = Read-Host -Prompt "Enter valid $ComputerName API key" -AsSecureString $ptr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureKey) $ApiKey = [Runtime.InteropServices.Marshal]::PtrToStringAuto($ptr) } if ($ApiKey) { $authType = 'ApiKey' } else { $authType = 'Credential' } Write-Verbose "Connecting to $baseUrl ($authType)" $requestParams = @{ Uri = $graphqlUrl Method = "Post" ContentType = "application/json" ErrorAction = "Stop" } # SSL cert trust handling if ($SkipCertificateCheck) { if ($PSVersionTable.PSVersion.Major -ge 6) { $requestParams.Add('SkipCertificateCheck', $true) } # PS 5.1/legacy cert bypass else { [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } } } try { if ($authType -eq 'ApiKey') { $requestParams.Headers = @{ "x-api-key" = $ApiKey } } else { $webSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession $loginBody = @{ username = $Credential.UserName password = $Credential.GetNetworkCredential().Password } $loginParams = @{ Uri = $loginUrl Method = "Post" Body = $loginBody WebSession = $webSession ErrorAction = "Stop" } if ($SkipCertificateCheck -and $PSVersionTable.PSVersion.Major -ge 6) { $loginParams.Add('SkipCertificateCheck', $true) } Write-Verbose "Logging in at $loginUrl..." $loginResponse = Invoke-WebRequest @loginParams if ($loginResponse.Content -match "Invalid username or password") { throw [UnraidAuthException]::new("Incorrect credentials; server rejected login.") } # Try to find CSRF token in the HTML # This is incredibly fragile - but seems to be the only way to use creds for now # API key seems to be the preferred per the API docs, but keeping this in for now for convenience $csrfToken = Get-UnraidCsrfToken -HtmlContent $loginResponse.Content -BaseUrl $baseUrl -WebSession $webSession -SkipCertificateCheck $SkipCertificateCheck.IsPresent $requestParams.WebSession = $webSession $requestParams.Headers = @{ "x-csrf-token" = $csrfToken } } # Verify connection with a test query $testPayload = @{ query = "query { __typename }" } | ConvertTo-Json $requestParams.Body = $testPayload Write-Verbose "Testing the line..." $gqlResponse = Invoke-RestMethod @requestParams if (!$gqlResponse.data) { throw [UnraidAuthException]::new("Connection verified but data was empty. Something is wrong.") } # Build the Unraid session obj if ($authType -eq 'ApiKey') { $newSession = [UnraidSession]::new($ComputerName, $graphqlUrl, $ApiKey, $SkipCertificateCheck.IsPresent) } else { $newSession = [UnraidSession]::new($ComputerName, $graphqlUrl, $Credential.UserName, $csrfToken, $webSession, $SkipCertificateCheck.IsPresent) } # Persist session unless told not to with switch if (!$NoDefaultSession) { $Script:DefaultUnraidSession = $newSession Write-Verbose "Default session set to $ComputerName" } if ($SaveAuth) { if ($authType -eq 'ApiKey') { Save-UnraidAuth -ServerName $ComputerName -ApiKey $ApiKey -Force } else { Save-UnraidAuth -ServerName $ComputerName -Credential $Credential -Force } Write-Verbose "Credentials saved." } Write-Information "Connected to $ComputerName" -InformationAction Continue return $newSession } catch { $thrownError = $_.Exception # Throw a quasi helpful error for SSL issues if (($thrownError -is [System.Net.WebException] -and $thrownError.Status -eq 'TrustFailure') -or $thrownError.Message -match "The remote certificate is invalid" -or $thrownError.Message -match "SSL connection could not be established") { Write-Error "SSL Trust Error: The certificate for '$ComputerName' isn't trusted.`nUse -SkipCertificateCheck to bypass untrusted cert." } elseif ($thrownError -is [UnraidAuthException]) { throw $_ } else { $msg = "Connection failed to $ComputerName" if ($thrownError -is [System.Net.WebException] -and $thrownError.Response) { $msg += " [HTTP $($thrownError.Response.StatusCode)]" } throw "$msg. Details: $($thrownError.Message)" } } } } |