Scripts/ValidateDNS_NicRu.ps1

#requires -Module Microsoft.PowerShell.Security
#noqa PSAvoidUsingPlainTextForPassword
<#
    .SYNOPSIS
        NIC.RU DNS Validation script for WinAcme
        SecureString used for credentials
    .DESCRIPTION
        Скрипт валидации DNS записей NIC.RU для WinAcme
 
        Credentials для работы предварительно должны быть сохранены в файле вручную с помошью New-NicCredential/Save-NicCredential из README
 
        SecureString использутся для хранения credentials запроса. Если это кажется небезопасным, необходимо поправить
        функции сохранения и восстановления под себя.
 
        Параметры вызова стандартные для скрипта - {Action} {Identifier} {RecordName} {Token}
 
        Необходимо также предварительно поправить под себя константы:
        $CredentialPath = "nic_credentials.txt"
        $DnsService = 'PRST-TEST-RU'
        $ZoneName = 'test.ru'
        Если есть желание передавать эти значения через параметры вызова, надо, соответственно,
        иcпользовать правильные --dnscreatescriptarguments
 
        Так как WinACME проверяет DNS сразу после вызова, а nic.ru раскидывает зоны не спеша, необходимо "поспать" перед выходом
        Это время можно уменьшить/увеличить, поправив
        $SLEEP_AFTER_UPDATE = 180
 
        Паралеллизм не поддерживается из-за того, что чтение/запись в файл не атомарные. Есть шанс попортить refresh_token.
    .LINK
        https://www.win-acme.com/reference/plugins/validation/dns/script
#>

[CmdletBinding()]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "CredentialPath")]
param(
    [string]$Action,
    [string]$Identifier,
    [string]$RecordName,
    [string]$Token,
    [string]$CredentialPath = "nic_credentials.txt",
    [string]$DnsService = 'PRST-TEST-RU',
    [string]$ZoneName = 'test.ru'
)
$SLEEP_AFTER_UPDATE = 180 # Секунд

function Save-NicCredential($path, $access_object) {
    Write-Verbose "Save credentials to $path"
    Set-Content -Path $path -Value $access_object.client_id,
        ($access_object.client_secret | ConvertFrom-SecureString),
        ($access_object.refresh_token | ConvertFrom-SecureString),
        ($access_object.access_token  | ConvertFrom-SecureString),
         $access_object.access_expires.ToFileTime() -ErrorAction Stop
}
function Restore-NicCredential($path) {
    Write-Verbose "Restore credentials from $path"
    $data = Get-Content -Path $path -ErrorAction Stop
    [PSCustomObject]@{
        client_id     = $data[0]
        client_secret = $data[1] | ConvertTo-SecureString
        refresh_token = $data[2] | ConvertTo-SecureString
        access_token  = if ($data[3]) { $data[3] | ConvertTo-SecureString } else { '' }
        access_expires = if ($data[4]) { [DateTime]::FromFileTime(([int64]$data[4])) } else { [DateTime]::MinValue }
    }
}

# Восстанавливаем параметры доступа из файла
$credentials = Restore-NicCredential -Path $CredentialPath
$TimeNow = Get-Date
if (-not ($credentials.client_id -and $credentials.client_secret -and $credentials.refresh_token)) {
    throw "Can't find valid credentials"
}
# Обновляем если надо
if ((-not $credentials.access_token) -or ($credentials.access_expires -le $TimeNow.AddSeconds(300))) {
    Write-Verbose "Request token"
    # Получаем свежий токен
    $t = Request-NicRuToken -Client_Id $credentials.client_id -Client_Secret $credentials.client_secret -RefreshToken $credentials.refresh_token
    if (-not $t) {
        throw "Can't refresh token"
    }
    # Тут же сохраняем обратно, потому что при получении access_token, refresh_token тоже сразу меняется
    $credentials.refresh_token = $t.refresh_token | ConvertTo-SecureString -AsPlainText -Force
    $credentials.access_token = $t.access_token | ConvertTo-SecureString -AsPlainText -Force
    $credentials.access_expires = (Get-Date).AddSeconds($t.expires_in)
    Save-NicCredential -Path $CredentialPath -Credential $credentials
}
else {
    Write-Verbose "Register saved token"
    Register-NicRuToken -RefreshToken $credentials.refresh_token -AccessToken $credentials.access_token
}

# Добавим финальную точку в запись, чтобы не заморачиваться с отрезанием зоны
$RecordName += '.'
# Работаем
if ($Action -eq 'create') {
    Write-Verbose "Action: Create"
    Add-NicRuDnsRecord -Service $DnsService -ZoneName $ZoneName -Record @{
        name = $RecordName
        type = 'TXT'
        text = $Token
        ttl  = 60
    } -ErrorAction Stop | Tee-Object -Variable acme
    Complete-NicRuDnsZoneChange -Service $DnsService -ZoneName $ZoneName -ErrorAction Stop
    # Подождать слегка пока DNS обновится, а то winacme очень гонит, nic.ru не успевает запись раскидать
    Start-Sleep -Seconds $SLEEP_AFTER_UPDATE
}
elseif ($Action -eq 'delete') {
    Write-Verbose "Action: Delete"
    $record = Get-NicRuDnsRecord -Service $DnsService -ZoneName $ZoneName -RecordType 'TXT'  -ErrorAction Stop | Where-Object {
        $RecordName.StartsWith($_.name) -and $_.txt.string -eq $Token
    }
    if (-not $record) {
        throw "Can't find dns record $RecordName"
    }
    Remove-NicRuDnsRecord -Service $DnsService -ZoneName $ZoneName -Id $record.Id  -ErrorAction Stop
    Complete-NicRuDnsZoneChange -Service $DnsService -ZoneName $ZoneName  -ErrorAction Stop
}
else {
    throw "Unknown action $Action"
}