🚪

Apexドメインに発行された Azure Front Door 証明書の自動更新を行う

はじめに

Apexドメインに発行されたAzure Front Door証明書が180日でドメイン検証が失効し、手動でドメイン検証を行っていませんか。私は行っていました。

SSL証明書の更新漏れを防ぐため、ApexドメインについてもAzure Automationにて更新を行うスクリプトを用意しました。

Apexドメインとは

Apexドメインとは、wwwやmailなどのホスト名(サブドメイン)を含まない、ドメイン名そのもののことを指します。

実は、Apexドメインのみ、Azure Front Doorによるドメインの再検証は自動で行われません。そのため、証明書の有効期限が切れる前に手動、またはスクリプトでドメイン所有権を再検証する必要があります。

Microsoftの公式ドキュメントにも、以下のように記載されています。

Azure Front Door マネージド証明書を使用すると、Azure Front Door によって証明書の自動的なローテーション (更新) が試みられます。 そうする前に、Azure Front Door では、DNS CNAME レコードがまだ Azure Front Door エンドポイントを指しているかどうかを確認します。 apex ドメインには Azure Front Door エンドポイントを指す CNAME レコードがないため、ドメインの所有権が再検証されるまで、マネージド証明書の自動ローテーションは失敗します。

https://learn.microsoft.com/ja-jp/azure/frontdoor/apex-domain#azure-front-door-managed-tls-certificate-rotation

ApexドメインでのCNAMEレコードの制約

RFC 1912で規定されているCNAMEレコードの制約により、ApexドメインではCNAMEレコードを用いたAzure Front Doorへの関連付けは行えません。

そのため、AレコードおよびAAAAレコードを使用して接続することになり、前述のAzure Front Doorのドメイン検証仕様も相まって、証明書の自動ローテーションが行われない原因となっています。

自動化スクリプト

180日ごとに手動で更新するのが面倒な方に向けて、Azure Automationで利用できるPowerShellスクリプトを作成しました。コメントを豊富につけていますので、自由に編集・利用してください。

update-afdapexdomainvalidation.ps1
<#
.SYNOPSIS
Azure Front Door apex ドメインの TXT レコード自動更新とドメイン検証スクリプト

.DESCRIPTION
このスクリプトは、Azure Automation を使用して Azure Front Door の apex ドメイン (カスタムドメイン) の SSL 証明書自動更新に必要な TXT レコードの更新とドメイン検証を自動化します。
apex ドメインでは Azure Front Door マネージド証明書の自動更新がサポートされないため、このスクリプトを定期的に実行することで、証明書の有効期限切れを防ぎます。

.PARAMETER ResourceGroupName
Azure Front Door リソースがデプロイされているリソースグループ名

.PARAMETER FrontDoorName
Azure Front Door プロファイル名

.PARAMETER CustomDomainName
Azure Front Door に設定しているカスタムドメイン名 (例: example.com)

.PARAMETER DnsZoneName
Azure DNS ゾーン名 (例: example.com)
#>
param (
    [Parameter(Mandatory = $true, HelpMessage = "Resource Group Name where Front Door is deployed")]
    [string]$ResourceGroupName,

    [Parameter(Mandatory = $true, HelpMessage = "Azure Front Door Profile Name")]
    [string]$FrontDoorName,

    [Parameter(Mandatory = $true, HelpMessage = "Custom Domain Name (e.g., example-com)")]
    [string]$CustomDomainName,

    [Parameter(Mandatory = $true, HelpMessage = "Azure DNS Zone Name (e.g., example.com)")]
    [string]$DnsZoneName
)

# 1. Azureへの接続 (マネージドIDを使用)
try {
    Write-Verbose -Message "Azureに接続しています..."
    Connect-AzAccount -Identity
    Write-Verbose -Message "Azureへの接続に成功しました。"
}
catch {
    Write-Error -Message "Azureの接続に失敗しました。: $_"
    throw
}

# 2. Front Door カスタムドメインの検証用TXTレコード情報を取得
try {
    Write-Verbose -Message "Front Door カスタムドメインの状態を取得しています..."
    $customDomain = Get-AzFrontDoorCdnCustomDomain -ResourceGroupName $ResourceGroupName -ProfileName $FrontDoorName -CustomDomainName $CustomDomainName
    Write-Verbose -Message "Front Door カスタムドメインの状態を取得しました。"
}
catch {
    Write-Error -Message "Front Door カスタムドメイン情報の取得に失敗しました。: $_"
    throw
}

# 3. 既存のTXTレコードを確認
try {
    $dnsAuthRecordName = "_dnsauth"
    Write-Verbose -Message "既存の TXT レコード '$dnsAuthRecordName' を確認します..."
    $oldTxtRecord = Get-AzDnsRecordSet -ResourceGroupName $ResourceGroupName -ZoneName $DnsZoneName -Name $dnsAuthRecordName -RecordType Txt
    $oldValidationToken = $oldTxtRecord.Records[0].Value
    Write-Verbose -Message "既存の TXT レコード値 '$oldValidationToken' を確認しました。"
}
catch {
    Write-Warning -Message "既存の TXT レコードが見つからないか、確認に失敗しました: $_"
    # 既存レコードがない場合は処理を続行
    $oldValidationToken = $null
}

# 4. Front Door カスタムドメインの検証用トークンを再生成
try {
    Write-Verbose -Message "Front Door カスタムドメインの検証トークンを再生成しています..."
    $validationRequest = Send-AzFrontDoorCdnCustomDomainValidationTokenRequest -ResourceGroupName $ResourceGroupName -ProfileName $FrontDoorName -CustomDomainName $CustomDomainName
    Write-Verbose -Message "Front Door カスタムドメインの検証トークンを再生成しました。"
}
catch {
    Write-Error -Message "Front Door カスタムドメインの検証トークン再生成に失敗しました。: $_"
    throw
}

# 5. 再生成後のTXTレコード情報を取得
try {
    Write-Verbose -Message "再生成後の TXT レコード情報を取得します..."
    $refreshedDomain = Get-AzFrontDoorCdnCustomDomain -ResourceGroupName $ResourceGroupName -ProfileName $FrontDoorName -CustomDomainName $CustomDomainName
    $newValidationToken = $refreshedDomain.ValidationProperty.ValidationToken
    Write-Verbose -Message "新しい TXT レコード値 '$newValidationToken' を取得しました。"
}
catch {
    Write-Error -Message "再生成後の TXT レコード情報取得に失敗しました: $_"
    throw
}

# 6. 新しいTXTレコードを作成または更新
try {
    Write-Verbose -Message "新しい TXT レコードを作成/更新します..."
    $recordSet = New-AzDnsRecordSet -Name $dnsAuthRecordName -RecordType TXT -ZoneName $DnsZoneName -ResourceGroupName $ResourceGroupName -Ttl 3600 -Overwrite
    $recordSet.Records.Add((New-AzDnsRecordConfig -Value $newValidationToken))
    Set-AzDnsRecordSet -RecordSet $recordSet
    Write-Verbose -Message "TXT レコードの作成/更新に成功しました。"
}
catch {
    Write-Error -Message "TXT レコードの作成/更新に失敗しました: $_"
    throw
}

# 7. Front Door カスタムドメインの検証が完了するまで待機
try {
    Write-Verbose -Message "Front Door カスタムドメイン '$CustomDomainName' の検証を待機しています..."
    $timeout = New-TimeSpan -Minutes 30
    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()

    while ($stopwatch.Elapsed -lt $timeout) {
        $domain = Get-AzFrontDoorCdnCustomDomain -ResourceGroupName $ResourceGroupName -ProfileName $FrontDoorName -CustomDomainName $CustomDomainName
        if ($domain.DomainValidationState -eq 'Approved') {
            Write-Verbose -Message "Front Door カスタムドメインの検証が完了しました。"
            break
        }
        Write-Verbose -Message "検証状態: $($domain.DomainValidationState)... 30秒待機します。"
        Start-Sleep -Seconds 30
    }

    if ($domain.DomainValidationState -ne 'Approved') {
        throw "タイムアウト時間内にドメイン検証が完了しませんでした。"
    }
}
catch {
    Write-Error -Message "Front Door カスタムドメインの検証待機中にエラーが発生しました: $_"
    throw
}

Write-Host "スクリプトは正常に完了しました。"

Discussion