Plugins/ISPConfig.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)] [pscredential]$ISPConfigCredential, [Parameter(Mandatory)] [string]$ISPConfigEndpoint, [switch]$ISPConfigIgnoreCert, [Parameter(ValueFromRemainingArguments)] $ExtraParams ) $restSplat = @{ Credential = $ISPConfigCredential Endpoint = $ISPConfigEndpoint } try { # ignore cert validation for the duration of the call if ($ISPConfigIgnoreCert) { Set-ISPConfigCertIgnoreOn } # find the zone/server details $zoneID,$zoneName,$serverID = Find-ISPConfigZone $RecordName @restSplat if ($zoneID) { Write-Debug "id: $zoneID, name: $zoneName, server ID: $serverID" } else { throw "Zone not found for record $RecordName" } # separate the portion of the name that doesn't contain the zone name $recShort = ("$RecordName." -ireplace [regex]::Escape($zoneName), [string]::Empty).TrimEnd('.') if ($recShort -eq '') { $recShort = "$RecordName." } # check for an existing record $rec = Get-ISPConfigZoneTxtRecord $recShort $TxtValue $zoneID @restSplat if ($rec) { Write-Debug "Record $RecordName already contains $TxtValue. Nothing to do." } else { try { $queryParams = @{ Function = 'dns_txt_add' BodyParams = @{ client_id = $null update_serial = $true params = @{ server_id = $serverID zone = $zoneID name = $recShort type = 'TXT' data = $TxtValue active = 'Y' ttl = 60 } } } Write-Verbose "Adding record '$recShort' with value '$TxtValue'" Invoke-ISPConfigRest @queryParams @restSplat | Out-Null } catch { throw } } } finally { # return cert validation back to normal if ($ISPConfigIgnoreCert) { Set-ISPConfigCertIgnoreOff } } <# .SYNOPSIS Add a DNS TXT record to ISPConfig .DESCRIPTION Add a DNS TXT record to ISPConfig .PARAMETER RecordName The fully qualified name of the TXT record. .PARAMETER TxtValue The value of the TXT record. .PARAMETER ISPConfigCredential Username and password the has access to create the necessary TXT records in ISPConfig. .PARAMETER ISPConfigEndpoint URL for the ISPConfig instance's remote API (e.g. https://example.com/remote/json.php) .PARAMETER ISPConfigIgnoreCert Use this switch to prevent certificate errors when your ISPConfig server is using a self-signed or other untrusted SSL certificate. .PARAMETER ExtraParams This parameter can be ignored and is only used to prevent errors when splatting with more parameters than this function supports. .EXAMPLE Add-DnsTxt '_acme-challenge.example.com' 'txt-value' -ISPConfigCredential (Get-Credential) -ISPConfigEndpoint https://example.com/remote/json.php 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)] [pscredential]$ISPConfigCredential, [Parameter(Mandatory)] [string]$ISPConfigEndpoint, [switch]$ISPConfigIgnoreCert, [Parameter(ValueFromRemainingArguments)] $ExtraParams ) $restSplat = @{ Credential = $ISPConfigCredential Endpoint = $ISPConfigEndpoint } try { # ignore cert validation for the duration of the call if ($ISPConfigIgnoreCert) { Set-ISPConfigCertIgnoreOn } # find the zone/server details $zoneID,$zoneName,$serverID = Find-ISPConfigZone $RecordName @restSplat if ($zoneID) { Write-Debug "id: $zoneID, name: $zoneName, server ID: $serverID" } else { throw "Zone not found for record $RecordName" } # separate the portion of the name that doesn't contain the zone name $recShort = ("$RecordName." -ireplace [regex]::Escape($zoneName), [string]::Empty).TrimEnd('.') if ($recShort -eq '') { $recShort = "$RecordName." } # check for an existing record $rec = Get-ISPConfigZoneTxtRecord $recShort $TxtValue $zoneID @restSplat if ($rec) { try { $queryParams = @{ Function = 'dns_txt_delete' BodyParams = @{ primary_id = $rec.id update_serial = $true } } Write-Verbose "Deleting record id $($rec.id), '$($rec.name)' with value '$($rec.data)'" Invoke-ISPConfigRest @queryParams @restSplat | Out-Null } catch { throw } } else { Write-Debug "Record $RecordName with value $TxtValue doesn't exist. Nothing to do." } } finally { # return cert validation back to normal if ($ISPConfigIgnoreCert) { Set-ISPConfigCertIgnoreOff } } <# .SYNOPSIS Remove a DNS TXT record from ISPConfig. .DESCRIPTION Remove a DNS TXT record from ISPConfig. .PARAMETER RecordName The fully qualified name of the TXT record. .PARAMETER TxtValue The value of the TXT record. .PARAMETER ISPConfigCredential Username and password the has access to create the necessary TXT records in ISPConfig. .PARAMETER ISPConfigEndpoint URL for the ISPConfig instance's remote API (e.g. https://example.com/remote/json.php) .PARAMETER ISPConfigIgnoreCert Use this switch to prevent certificate errors when your ISPConfig server is using a self-signed or other untrusted SSL certificate. .PARAMETER ExtraParams This parameter can be ignored and is only used to prevent errors when splatting with more parameters than this function supports. .EXAMPLE Remove-DnsTxt '_acme-challenge.example.com' 'txt-value' -ISPConfigCredential (Get-Credential) -ISPConfigEndpoint https://example.com/remote/json.php 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 ############################ # https://git.ispconfig.org/ispconfig/ispconfig3/-/tree/develop/remoting_client/API-docs # https://github.com/m42e/certbot-dns-ispconfig/blob/master/certbot_dns_ispconfig/dns_ispconfig.py function Invoke-ISPConfigRest { [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] [pscredential]$Credential, [Parameter(Mandatory,Position=1)] [string]$Endpoint, [Parameter(Mandatory)] [string]$Function, [hashtable]$BodyParams ) if ($Function -eq 'login') { # build login params $uri = "{0}?login" -f $Endpoint $pass = $ISPConfigCredential.GetNetworkCredential().Password $body = @{ username = $ISPConfigCredential.UserName password = $pass } | ConvertTo-Json $bodySanitized = $body.Replace($pass,'XXXXXXXX') } else { if (-not $script:ISPConfigSessionID) { # recurse and login first try { Write-Debug "recursing to login" $script:ISPConfigSessionID = Invoke-ISPConfigRest $Credential $Endpoint -Function 'login' } catch { Write-Debug "login recurse failed"; throw } if (-not $script:ISPConfigSessionID) { throw "no session ID found after logging in" } } # build function params with the session ID $uri = "{0}?{1}" -f $Endpoint,$Function $BodyParams.session_id = $script:ISPConfigSessionID $body = $BodyParams | ConvertTo-Json -Depth 5 $bodySanitized = $body } # build rest splat $restParams = @{ Uri = $uri Method = 'POST' Body = $body Verbose = $false } try { Write-Debug "POST $uri`n$bodySanitized" $response = Invoke-RestMethod @restParams @script:UseBasic } catch { throw } if ($response.code -eq 'ok') { return $response.response } elseif ($response.code -eq 'remote_fault') { # re-try if session expired "The Session is expired or does not exist." if ($response.message -like '*expired*') { Write-Debug "session expired, retrying with fresh login" $script:ISPConfigSessionID = $null try { return Invoke-ISPConfigRest @PSBoundParameters } catch { throw } } else { throw "ISPConfig Error: $($response.message)" } } else { throw "Unexpected error with ISPConfig:`n$($response | ConvertTo-Json)" } } function Find-ISPConfigZone { [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] [string]$RecordName, [Parameter(Mandatory,Position=1)] [pscredential]$Credential, [Parameter(Mandatory,Position=2)] [string]$Endpoint ) # setup a module variable to cache the record to zone mapping # so it's quicker to find later if (!$script:ISPConfigRecordZones) { $script:ISPConfigRecordZones = @{} } # check for the record in the cache if ($script:ISPConfigRecordZones.ContainsKey($RecordName)) { return $script:ISPConfigRecordZones.$RecordName } # Find the closest/deepest sub-zone that would hold the record. $pieces = $RecordName.Split('.') for ($i=0; $i -lt ($pieces.Count-1); $i++) { $zoneTest = $pieces[$i..($pieces.Count-1)] -join '.' Write-Debug "Checking $zoneTest" try { $queryParams = @{ Function = 'dns_zone_get' BodyParams = @{ primary_id = @{ origin = "$zoneTest." } } Credential = $Credential Endpoint = $Endpoint } $response = Invoke-ISPConfigRest @queryParams } catch { throw } if ($response) { $script:ISPConfigRecordZones.$RecordName = $response.id,$response.origin,$response.server_id return $response.id,$response.origin,$response.server_id } } return $null } function Get-ISPConfigZoneTxtRecord { [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] [string]$RecordShort, [Parameter(Mandatory,Position=1)] [string]$TxtValue, [Parameter(Mandatory,Position=2)] [string]$ZoneID, [Parameter(Mandatory,Position=3)] [pscredential]$Credential, [Parameter(Mandatory,Position=4)] [string]$Endpoint ) try { $queryParams = @{ Function = 'dns_rr_get_all_by_zone' BodyParams = @{ zone_id = $ZoneID } Credential = $Credential Endpoint = $Endpoint } Invoke-ISPConfigRest @queryParams | Where-Object { $_.name -eq $RecordShort -and $_.type -eq 'TXT' -and $_.data -eq $TxtValue } | Select-Object id,name,type,'data' } catch { throw } } function Set-ISPConfigCertIgnoreOn { [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:ISPConfigUnsetIgnoreAfter = $true } } else { # Desktop edition [CertValidation]::Ignore() } } function Set-ISPConfigCertIgnoreOff { [CmdletBinding()] param() if ($script:SkipCertSupported) { # Core edition if ($script:ISPConfigUnsetIgnoreAfter) { $script:UseBasic.SkipCertificateCheck = $false Remove-Variable ISPConfigUnsetIgnoreAfter -Scope Script } } else { # Desktop edition [CertValidation]::Restore() } } |