Plugins/SOLIDServer.ps1
function Get-CurrentPluginType { 'dns-01' } function Add-DnsTxt { [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] [string]$RecordName, [Parameter(Mandatory,Position=1)] [string]$TxtValue, [Parameter(Mandatory,Position=2)] [pscredential]$SolidCredential, [Parameter(Mandatory,Position=3)] [string]$SolidAPIHost, [Parameter(Position=4)] [string]$SolidDNSServer, [Parameter(Position=5)] [string]$SolidView, [switch]$SolidIgnoreCert, [switch]$SolidTokenAuth, [Parameter(ValueFromRemainingArguments)] $ExtraParams ) # SOLIDServer's `dns_rr_add` allows us to add an FQDN and it will find the appropriate zone # assuming the supplied Server and View contain a matching zone. The add_flag=new_edit param # also allows us to do a blind add without checking if the record already exists first which # is extra nice. # WARNING: If the view is left empty and there are multiple zones that match in different views, # the record seems to get added to all of them. Not sure if this also applies to matching zones # on different servers. $queryParams = @{ APIHost = $SolidAPIHost Credential = $SolidCredential Endpoint = 'dns_rr_add' Method = 'POST' Body = @{ rr_name = $RecordName value1 = $TxtValue rr_type = 'TXT' rr_ttl = 30 add_flag = 'new_edit' check_value = 'yes' dns_name = $SolidDNSServer dnsview_name = $SolidView } IgnoreCert = $SolidIgnoreCert.IsPresent TokenAuth = $SolidTokenAuth.IsPresent } Write-Verbose "Removing TXT record for $RecordName with value $TxtValue" $resp = Invoke-SolidRequest @queryParams Write-Debug "Response: $($resp | ConvertTo-Json)" <# .SYNOPSIS Add a DNS TXT record to EfficientIP SOLIDServer. .DESCRIPTION Add a DNS TXT record to EfficientIP SOLIDServer. .PARAMETER RecordName The fully qualified name of the TXT record. .PARAMETER TxtValue The value of the TXT record. .PARAMETER SolidCredential The SOLIDServer Username and Password. .PARAMETER SolidAPIHost The EfficientIP SOLIDServer Hostname. .PARAMETER SolidDNSServer The EfficientIP SOLIDServer DNS server. .PARAMETER SolidView The EfficientIP SOLIDServer DNS view. .PARAMETER SolidIgnoreCert When set, certificate validation will be disabled for connections to the SOLIDServer. .PARAMETER SolidTokenAuth When set, the username and password in SolidCredential will be used as API Token and Secret .PARAMETER ExtraParams This parameter can be ignored and is only used to prevent errors when splatting with more parameters than this function supports. .EXAMPLE $cred = Get-Credential PS C:\>Add-DnsTxt '_acme-challenge.example.com' 'txt-value' $cred 'eip.local' 'smart.local' 'external' Adds a TXT record for the specified site with the specified value. #> } function Remove-DnsTxt { [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] [string]$RecordName, [Parameter(Mandatory,Position=1)] [string]$TxtValue, [Parameter(Mandatory,Position=2)] [pscredential]$SolidCredential, [Parameter(Mandatory,Position=3)] [string]$SolidAPIHost, [Parameter(Position=4)] [string]$SolidDNSServer, [Parameter(Position=5)] [string]$SolidView, [switch]$SolidIgnoreCert, [switch]$SolidTokenAuth, [Parameter(ValueFromRemainingArguments)] $ExtraParams ) # Find the rr_id of the record if it exists $queryParams = @{ APIHost = $SolidAPIHost Credential = $SolidCredential Endpoint = 'dns_rr_list' Method = 'GET' Body = @{ SELECT = 'rr_id,rr_full_name,rr_type,value1,dnsview_name,vdns_parent_id' WHERE = "dnszone_type='master' AND rr_full_name='$RecordName' AND rr_type='TXT' AND value1='$TxtValue'" } IgnoreCert = $SolidIgnoreCert.IsPresent TokenAuth = $SolidTokenAuth.IsPresent } # Add optional fields if ($SolidDNSServer) { $queryParams.Body.WHERE += " AND dns_name='$SolidDNSServer'" } if ($SolidView) { $queryParams.Body.WHERE += " AND dnsview_name='$SolidView'" } $resp = Invoke-SolidRequest @queryParams if ($resp.rr_id) { # In case we have multiple record matches, delete all of them $resp.rr_id | ForEach-Object { $queryParams.Endpoint = 'dns_rr_delete' $queryParams.Method = 'DELETE' $queryParams.Body = @{ rr_id = $_ } Write-Verbose "Removing TXT record rr_id $_ - $RecordName with value $TxtValue" $resp = Invoke-SolidRequest @queryParams Write-Debug "Response: $($resp | ConvertTo-Json)" } } else { Write-Debug "Record $RecordName with value $TxtValue doesn't exist. Nothing to do." } <# .SYNOPSIS Remove a DNS TXT record from EfficientIP SOLIDServer. .DESCRIPTION Remove a DNS TXT record from EfficientIP SOLIDServer. .PARAMETER RecordName The fully qualified name of the TXT record. .PARAMETER TxtValue The value of the TXT record. .PARAMETER SolidCredential The SOLIDServer Username and Password. .PARAMETER SolidAPIHost The EfficientIP SOLIDServer Hostname. .PARAMETER SolidDNSServer The EfficientIP SOLIDServer DNS server. .PARAMETER SolidView The EfficientIP SOLIDServer DNS view. .PARAMETER SolidIgnoreCert When set, certificate validation will be disabled for connections to the SOLIDServer. .PARAMETER SolidTokenAuth When set, the username and password in SolidCredential will be used as API Token and Secret .PARAMETER ExtraParams This parameter can be ignored and is only used to prevent errors when splatting with more parameters than this function supports. .EXAMPLE $cred = Get-Credential PS C:\>Remove-DnsTxt '_acme-challenge.example.com' 'txt-value' $cred 'eip.local' 'smart.local' 'external' Removes a TXT record for the specified site with the specified value. #> } function Save-DnsTxt { [CmdletBinding()] param( [Parameter(ValueFromRemainingArguments)] $ExtraParams ) <# .SYNOPSIS Not required. .DESCRIPTION This provider does not require calling this function to commit changes to DNS records. .PARAMETER ExtraParams This parameter can be ignored and is only used to prevent errors when splatting with more parameters than this function supports. #> } ############################ # Helper Functions ############################ # API docs are only available as PDF for customers function Invoke-SolidRequest { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$APIHost, [Parameter(Mandatory)] [pscredential]$Credential, [Parameter(Mandatory)] [string]$Endpoint, [string]$Method = 'GET', [hashtable]$Body, [switch]$IgnoreCert, [switch]$TokenAuth ) # build the base query $queryParams = @{ Uri = 'https://{0}/rest/{1}' -f $APIHost,$Endpoint Method = $Method Headers = @{ Accept = 'application/json' } ErrorAction = 'Stop' Verbose = $false } # add the body if necessary if ($Body) { if ($Method -eq 'GET') { # Passing a hashtable as-is to -Body would normally auto-encode into # key=value querystring pairs under the hood. But when using token auth, # we need to sign the full Uri+Querystring. So we're going to encode # the querystring ourselves and just append it to the Uri regardless # of the auth type. $querystring = ConvertTo-QueryString $Body $queryParams.Uri += '?{0}' -f $querystring } else { # Everything else is JSON $queryParams.ContentType = 'application/json; charset=utf-8' $queryParams.Body = $Body | ConvertTo-Json -Compress } } # Grab the plaintext password and build the auth header $pwdPlain = $Credential.GetNetworkCredential().Password if ($TokenAuth) { $timestamp = [DateTimeOffset]::Now.ToUnixTimeSeconds() Write-Debug "Using token auth with token prefix $($Credential.UserName.Substring(0,5)) at $timestamp" $strToHash = "{0}`n{1}`n{2}`n{3}" -f $pwdPlain,$timestamp,$Method,$queryParams.Uri $signature = Get-SHA3_256 $strToHash $queryParams.Headers.'X-SDS-TS' = $timestamp $queryParams.Headers.Authorization = 'SDS {0}:{1}' -f $Credential.UserName,$signature } else { Write-Debug "Using Basic auth with username $($Credential.UserName)" $basicAuth = [Convert]::ToBase64String( [Text.Encoding]::UTF8.GetBytes(("{0}:{1}" -f $Credential.Username, $pwdPlain)) ) $queryParams.Headers.Authorization = 'Basic {0}' -f $basicAuth } Write-Debug "$Method $($queryParams.Uri)" if ($Method -ne 'GET') { Write-Debug ($Body | ConvertTo-Json) } try { # ignore cert validation for the duration of the call if ($SolidIgnoreCert) { Set-CertIgnoreOn } $result = Invoke-RestMethod @queryParams @script:UseBasic } catch { $response = $_.Exception.Response # deal with bad credentials first if (401 -eq $response.StatusCode) { throw "SOLIDServer returned an Unauthorized error. Check for bad credentials." } # The web exception types thrown between PowerShell editions are different. # So we need to string match the type names in order to process each correctly. $exType = $_.Exception.GetType().FullName if ('System.Net.WebException' -eq $exType) { # Desktop edition # grab the raw response body from System.Net.HttpWebResponse $sr = [IO.StreamReader]::new($response.GetResponseStream()) $sr.BaseStream.Position = 0 $sr.DiscardBufferedData() $errBody = $sr.ReadToEnd() $sr.Close() } elseif ('Microsoft.PowerShell.Commands.HttpResponseException' -eq $exType) { # Core edition # Grab the "processed" response body $errBody = $_.ErrorDetails.Message } else { throw } Write-Debug "Response Code $($response.StatusCode.value__), Body: `n$errBody" try { $result = $errBody | ConvertFrom-Json } catch { throw "SOLIDServer returned a non-JSON error body." } } finally { # return cert validation back to normal if ($SolidIgnoreCert) { Set-CertIgnoreOff } } if ($result.errno -and $result.errno -gt 0) { $msg = $result.errmsg if (-not $msg) { $msg = $result.msg } throw "SOLIDServer returned error $($result.errno): $msg. (Enable debug output for full error body)" } $result } function Set-CertIgnoreOn { [CmdletBinding()] param() if ($script:SkipCertSupported) { # Core edition if (-not $script:UseBasic.SkipCertificateCheck) { # temporarily set skip to true $script:UseBasic.SkipCertificateCheck = $true # remember that we did $script:SolidUnsetIgnoreAfter = $true } } else { # Desktop edition [CertValidation]::Ignore() } } function Set-CertIgnoreOff { [CmdletBinding()] param() if ($script:SkipCertSupported) { # Core edition if ($script:SolidUnsetIgnoreAfter) { $script:UseBasic.SkipCertificateCheck = $false Remove-Variable SolidUnsetIgnoreAfter -Scope Script } } else { # Desktop edition [CertValidation]::Restore() } } function Get-SHA3_256 { [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] [string]$InputString ) # PowerShell 7.4+ (.NET 8+) have a native SHA3_256 class in System.Security.Cryptography. # However, it only works on Windows 11 23H2+ and Linux with OpenSSL 1.1.1+. # So for everything else, we'll try to use the BouncyCastle equivalent in # Org.BouncyCastle.Crypto.Digests.Sha3Digest that gets loaded by the module. $inputBytes = [Text.Encoding]::UTF8.GetBytes($InputString) # Try native first try { $sha3 = [System.Security.Cryptography.SHA3_256]::Create() Write-Debug "Using Native SHA3-256" $hashBytes = $sha3.ComputeHash($inputBytes) } catch { try { $sha3 = [Org.BouncyCastle.Crypto.Digests.Sha3Digest]::new(256) Write-Debug "Using BouncyCastle SHA3-256" $hashBytes = [byte[]]::new($sha3.GetDigestSize()) $sha3.BlockUpdate($inputBytes, 0, $inputBytes.Length) $null = $sha3.DoFinal($hashBytes, 0) } catch { throw "Unable to load SHA3_256 hashing library. Make sure Posh-ACME is imported." } } return [BitConverter]::ToString($hashBytes).Replace("-", "").ToLowerInvariant() } function ConvertTo-QueryString { [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] [hashtable]$InputData ) Write-Debug "Converting to querystring`n$($InputData | ConvertTo-Json)" $encodedPairs = foreach ($kvp in $InputData.GetEnumerator()) { '{0}={1}' -f [uri]::EscapeDataString($kvp.Key),[uri]::EscapeDataString($kvp.Value) } return ($encodedPairs -join '&') } |