diff --git a/Posh-ACME/Plugins/SimplyCom.ps1 b/Posh-ACME/Plugins/SimplyCom.ps1 new file mode 100644 index 00000000..02a5157b --- /dev/null +++ b/Posh-ACME/Plugins/SimplyCom.ps1 @@ -0,0 +1,287 @@ +function Get-CurrentPluginType { 'dns-01' } + +function Add-DnsTxt { + [CmdletBinding(DefaultParameterSetName='Secure')] + param( + [Parameter(Mandatory,Position=0)] + [string]$RecordName, + [Parameter(Mandatory,Position=1)] + [string]$TxtValue, + [Parameter(Mandatory,Position=2)] + [string]$SimplyAccount, + [Parameter(ParameterSetName='Secure',Mandatory,Position=3)] + [securestring]$SimplyAPIKey, + [Parameter(ParameterSetName='DeprecatedInsecure',Mandatory,Position=3)] + [string]$SimplyAPIKeyInsecure, + [string]$SimplyAPIRoot = 'https://api.simply.com/2', + [Parameter(ValueFromRemainingArguments)] + $ExtraParams + ) + + if ('Secure' -eq $PSCmdlet.ParameterSetName) { + $SimplyAPIKeyInsecure = [pscredential]::new('a',$SimplyAPIKey).GetNetworkCredential().Password + } + + # Setup Basic Auth creds + $encodedCreds = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($SimplyAccount):$($SimplyAPIKeyInsecure)")) + $authHeader = @{Authorization = "Basic $encodedCreds"} + + $zoneID,$rec = Get-SimplyTXTRecord $RecordName $TxtValue $authHeader $SimplyAPIRoot + + if ($rec) { + Write-Verbose "Record $RecordName already contains $TxtValue. Nothing to do." + } else { + + # build the new record object + $body = @{ + name = $RecordName # Simply allows FQDNs here even though they return short names + type = 'TXT' + data = $TxtValue + ttl = 60 + } | ConvertTo-Json + + Write-Verbose "Adding a TXT record for $RecordName with value $TxtValue" + try { + $postParams = @{ + Uri = "{0}/my/products/{1}/dns/records" -f $SimplyAPIRoot,$zoneID + Method = 'POST' + Headers = $authHeader + Body = $body + ContentType = 'application/json' + ErrorAction = 'Stop' + Verbose = $false + } + Write-Debug "POST $($postParams.Uri)`n$body" + Invoke-RestMethod @postParams @script:UseBasic | Out-Null + } + catch { + Write-Debug $_ + throw + } + + } + + + <# + .SYNOPSIS + Add a DNS TXT record to Simply. + .DESCRIPTION + Use Simply api to add a TXT record to a Simply DNS zone. + .PARAMETER RecordName + The fully qualified name of the TXT record. + .PARAMETER TxtValue + The value of the TXT record. + .PARAMETER SimplyAccount + The account name of the account used to connect to Simply API (e.g. S123456) + .PARAMETER SimplyAPIKey + The API Key associated with the account as a SecureString value. + .PARAMETER SimplyAPIKeyInsecure + (DEPRECATED) The API Key associated with the account as a standard string value. + .PARAMETER SimplyAPIRoot + The base URL for the Simply v2 API + .PARAMETER ExtraParams + This parameter can be ignored and is only used to prevent errors when splatting with more parameters than this function supports. + .EXAMPLE + $key = Read-Host 'API Key' -AsSecureString + Add-DnsTxt '_acme-challenge.example.com' 'txt-value' 'S123456' $key + + Adds a TXT record for the specified site with the specified value. + #> +} + +function Remove-DnsTxt { + [CmdletBinding(DefaultParameterSetName='Secure')] + param( + [Parameter(Mandatory,Position=0)] + [string]$RecordName, + [Parameter(Mandatory,Position=1)] + [string]$TxtValue, + [Parameter(Mandatory,Position=2)] + [string]$SimplyAccount, + [Parameter(ParameterSetName='Secure',Mandatory,Position=3)] + [securestring]$SimplyAPIKey, + [Parameter(ParameterSetName='DeprecatedInsecure',Mandatory,Position=3)] + [string]$SimplyAPIKeyInsecure, + [string]$SimplyAPIRoot = 'https://api.simply.com/2', + [Parameter(ValueFromRemainingArguments)] + $ExtraParams + ) + + if ('Secure' -eq $PSCmdlet.ParameterSetName) { + $SimplyAPIKeyInsecure = [pscredential]::new('a',$SimplyAPIKey).GetNetworkCredential().Password + } + + # Setup Basic Auth creds + $encodedCreds = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($SimplyAccount):$($SimplyAPIKeyInsecure)")) + $authHeader = @{Authorization = "Basic $encodedCreds"} + + $zoneID,$rec = Get-SimplyTXTRecord $RecordName $TxtValue $authHeader $SimplyAPIRoot + + if ($rec) { + + Write-Verbose "Removing TXT record for $RecordName with value $TxtValue" + try { + $delParams = @{ + Uri = "{0}/my/products/{1}/dns/records/{2}" -f $SimplyAPIRoot,$zoneID,$rec.record_id + Headers = $authHeader + Method = 'DELETE' + ErrorAction = 'Stop' + Verbose = $false + } + Write-Debug "DELETE $($delParams.Uri)" + Invoke-RestMethod @delParams @script:UseBasic | Out-Null + } + catch { + Write-Debug $_ + throw + } + + } else { + Write-Debug "Record $RecordName with value $TxtValue doesn't exist. Nothing to do." + } + + <# + .SYNOPSIS + Removes a DNS TXT record from Simply. + .DESCRIPTION + Use Simply API to remove a TXT record from a Simply DNS zone. + .PARAMETER RecordName + The fully qualified name of the TXT record. + .PARAMETER TxtValue + The value of the TXT record. + .PARAMETER SimplyAccount + The account name of the account used to connect to Simply API (e.g. S123456) + .PARAMETER SimplyAPIKey + The API Key associated with the account as a SecureString value. + .PARAMETER SimplyAPIKeyInsecure + (DEPRECATED) The API Key associated with the account as a standard string value. + .PARAMETER SimplyAPIRoot + The base URL for the Simply v2 API + .PARAMETER ExtraParams + This parameter can be ignored and is only used to prevent errors when splatting with more parameters than this function supports. + .EXAMPLE + $key = Read-Host 'API Key' -AsSecureString + Remove-DnsTxt '_acme-challenge.example.com' 'txt-value' 'S123456' $key + + Removes a TXT record from 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: +# https://www.simply.com/en/docs/api/ + +function Get-SimplyTXTRecord { + [CmdletBinding()] + param( + [Parameter(Mandatory,Position=0)] + [string]$RecordName, + [Parameter(Mandatory,Position=1)] + [string]$TxtValue, + [Parameter(Mandatory,Position=2)] + [hashtable]$AuthHeader, + [Parameter(Mandatory,Position=3)] + [string]$ApiRoot + ) + + # setup a module variable to cache the record to zone mapping + # so it's quicker to find later + if (!$script:SimplyRecordZones) { $script:SimplyRecordZones = @{} } + + # check for the record in the cache + #$zone,$zoneID = $script:SimplyRecordZones.$RecordName + $zoneID,$recShort = $script:SimplyRecordZones.$RecordName + + if (-not $zoneID) { + + # query all of the domains on the account + try { + $getParams = @{ + Uri = '{0}/my/products' -f $ApiRoot + Headers = $AuthHeader + ErrorAction = 'Stop' + Verbose = $false + } + Write-Debug "GET $($getParams.Uri)" + $products = (Invoke-RestMethod @getParams @script:UseBasic).products + } catch { throw } + + # find the zone for the closest/deepest sub-zone that would contain 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" + + $match = $products | Where-Object { $zoneTest -eq $_.domain.name_idn } | Select-Object -First 1 + if ($match) { + Write-Debug "Matched object:`n$($match | ConvertTo-Json -Depth 5)" + # To query the records, we need the "object" id of the zone which is currently + # the non-punycode version of the domain name. But that's not guaranteed to always + # be the case. So just treat it like an arbitrary ID value. + $zoneID = $match.object + + # derive the short record name + if ($RecordName -eq $match.domain.name_idn) { + $recShort = '@' + } else { + $recShort = ($RecordName -ireplace [regex]::Escape($match.domain.name_idn), [string]::Empty).TrimEnd('.') + } + + $script:SimplyRecordZones.$RecordName = $zoneID,$recShort + break + } + } + } + + # query the zone records and check for the one we care about + try { + $getParams = @{ + Uri = '{0}/my/products/{1}/dns/records' -f $ApiRoot,$zoneID + Headers = $AuthHeader + ErrorAction = 'Stop' + Verbose = $false + } + Write-Debug "GET $($getParams.Uri)" + $response = Invoke-RestMethod @getParams @script:UseBasic + } + catch { + Write-Debug "$_" + throw + } + + if ($response.status -eq 200) { + + $rec = $response.records | Where-Object { + $_.type -eq 'TXT' -and + $_.name -eq $recShort -and + $_.data -eq $TxtValue + } + + # return the zone name and the record + return $zoneID,$rec + + } else { + Write-Debug "Simply Response: `n$($response | ConvertTo-Json)" + throw "Unexpected response from Simply: $($response.message)." + } + +} diff --git a/Posh-ACME/Private/Import-PluginDetail.ps1 b/Posh-ACME/Private/Import-PluginDetail.ps1 index 7838d29f..ace82071 100644 --- a/Posh-ACME/Private/Import-PluginDetail.ps1 +++ b/Posh-ACME/Private/Import-PluginDetail.ps1 @@ -73,6 +73,7 @@ function Import-PluginDetail { 'Selectel' = [pscustomobject]@{PSTypeName = 'PoshACME.PAPluginDetail'; ChallengeType = 'dns-01'; Path = ''; Name = 'Selectel'} 'SimpleDNSPlus' = [pscustomobject]@{PSTypeName = 'PoshACME.PAPluginDetail'; ChallengeType = 'dns-01'; Path = ''; Name = 'SimpleDNSPlus'} 'Simply' = [pscustomobject]@{PSTypeName = 'PoshACME.PAPluginDetail'; ChallengeType = 'dns-01'; Path = ''; Name = 'Simply'} + 'SimplyCom' = [pscustomobject]@{PSTypeName = 'PoshACME.PAPluginDetail'; ChallengeType = 'dns-01'; Path = ''; Name = 'SimplyCom'} 'SSHProxy' = [pscustomobject]@{PSTypeName = 'PoshACME.PAPluginDetail'; ChallengeType = 'dns-01'; Path = ''; Name = 'SSHProxy'} 'TotalUptime' = [pscustomobject]@{PSTypeName = 'PoshACME.PAPluginDetail'; ChallengeType = 'dns-01'; Path = ''; Name = 'TotalUptime'} 'UKFast' = [pscustomobject]@{PSTypeName = 'PoshACME.PAPluginDetail'; ChallengeType = 'dns-01'; Path = ''; Name = 'UKFast'} diff --git a/docs/Plugins/Simply.md b/docs/Plugins/Simply.md index 0713e75d..3ce6acfb 100644 --- a/docs/Plugins/Simply.md +++ b/docs/Plugins/Simply.md @@ -4,6 +4,9 @@ title: Simply This plugin works against the [Simply.com](https://www.simply.com/) DNS provider. It is assumed that you have already setup an account and created the DNS zone(s) you will be working against. +!!! warning + The provider has requested this plugin be renamed to `SimplyCom`. So a clone was created called `SimplyCom` that should be used instead. This version will be removed from the module in the next major release of the module. + ## Setup Using the Simply.com API requires only your account name or account number and API Key which can be found in your [Control Panel](https://www.simply.com/controlpanel/) on the Account page. diff --git a/docs/Plugins/SimplyCom.md b/docs/Plugins/SimplyCom.md new file mode 100644 index 00000000..4189140e --- /dev/null +++ b/docs/Plugins/SimplyCom.md @@ -0,0 +1,24 @@ +title: SimplyCom + +# How To Use the Simply.com DNS Plugin + +This plugin works against the [Simply.com](https://www.simply.com/) DNS provider. It is assumed that you have already setup an account and created the DNS zone(s) you will be working against. + +## Setup + +Using the Simply.com API requires only your account name or account number and API Key which can be found in your [Control Panel](https://www.simply.com/controlpanel/) on the Account page. + +## Using the Plugin + +Your account name/number is used with the `SimplyAccount` parameter. The API key is used with the `SimplyAPIKey` SecureString parameter. + +!!! warning + The `SimplyAPIKeyInsecure` parameter is deprecated and will be removed in the next major module version. If you are using it, please migrate to the Secure parameter set. + +```powershell +$pArgs = @{ + SimplyAccount = 'S123456' + SimplyAPIKey = (Read-Host 'Enter Key' -AsSecureString) +} +New-PACertificate example.com -Plugin Simply -PluginArgs $pArgs +``` diff --git a/docs/Plugins/index.md b/docs/Plugins/index.md index bebef980..807ba7e0 100644 --- a/docs/Plugins/index.md +++ b/docs/Plugins/index.md @@ -73,7 +73,8 @@ RFC2136 | [RFC 2136](https://tools.ietf.org/html/rfc2136) | [Usage Guide](RFC213 Route53 | [AWS Route53](https://aws.amazon.com/route53/) | [Usage Guide](Route53.md) | :white_check_mark: Selectel | [Selectel.ru](https://selectel.ru/en/services/additional/dns/) | [Usage Guide](Selectel.md) | :white_check_mark: SimpleDNSPlus | [Simple DNS Plus](https://simpledns.com/) | [Usage Guide](SimpleDNSPlus.md) | :white_check_mark: -Simply | [Simply.com](https://www.simply.com/) (formerly UnoEuro) | [Usage Guide](Simply.md) | :white_check_mark: +Simply | [Simply.com](https://www.simply.com/) (deprecated) | [Usage Guide](Simply.md) | :white_check_mark: +SimplyCom | [Simply.com](https://www.simply.com/) (formerly UnoEuro) | [Usage Guide](SimplyCom.md) | :white_check_mark: SSHProxy | Custom SSH Script | [Usage Guide](SSHProxy.md) | :white_check_mark: TotalUptime | [TotalUptime](https://totaluptime.com/solutions/cloud-dns-service/) | [Usage Guide](TotalUptime.md) | :white_check_mark: UKFast | [UKFast](https://ukfast.co.uk) | [Usage Guide](UKFast.md) | :white_check_mark: