Classic/VPC環境で利用できます。
win-acme(ACMEv2クライアント)を使用して Certificate Manager ACME機能で TLS証明書を自動的に発行・更新する方法を説明します。DNS-01チャレンジの検証に Ncloud Global DNS APIを使用し、Windows Task Schedulerで自動更新を構成します。
Certificate Manager ACMEは RSA 2048キーのみ許可します。win-acmeのプライマリキーサイズは RSA 3072のため、設定を変更しないと DNS-01検証に成功しても証明書の発行に失敗します。必ず settings.jsonでキーサイズを2048に変更してください。
サービス仕様
| 項目 | 内容 |
|---|---|
| サポート OS | Windows Server 2019, Windows Server 2022 |
| 推奨クライアント | win-acme v2.2.9以上(Standalone、64-bit) |
| 検証方法 | DNS-01 (Ncloud Global DNS API) |
| キーアルゴリズム | RSA 2048 / SHA256withRSA(必須) |
| 証明書の保存場所 | Windows Certificate Store (WebHosting) |
| ACMEのエンドポイント | https://acme.navercloudtrust.com/acme/directory |
始める前に
このガイドを進める前に、以下の項目を準備しておく必要があります。
| 項目 | 確認内容 |
|---|---|
| Ncloud IAM APIキー | Access Key/Secret Key発行完了、Global DNS権限付与確認 |
| EABキー | Certificate Managerコンソールで EAB Key ID/HMAC Key発行完了 |
| Ncloud Global DNS | 証明書を発行するドメインが Global DNSにゾーンとして登録されている |
| 管理者権限 | wacs.exeの実行および Windows証明書の保存場所へアクセスするためのローカル管理者権限 |
| IIS | HTTPS証明書を適用するサイト構成完了 |
| PowerShell実行ポリシー | RemoteSigned以上の設定を確認 |
External Account Binding(EAB)キーは、ACMEアカウント登録時に1度のみ使用されます。1度登録されたアカウントは、その後の更新に自動的に再利用されるため、キーを再発行する必要がありません。
ステップ1: win-acmeインストールと設定
win-acmeインストール
- win-acme公式リリースページで v2.2.9以上の release、trimmed、standalone、64-bitバージョンをダウンロードします。
- 目的のパスに圧縮ファイルを解凍します。
settings.json変更
win-acmeインストールディレクトリの settings.jsonファイルを開き、以下の2つの項目を必ず変更します。
1.ACMEのエンドポイントを変更
Acmeセクションの DefaultBaseUri値を以下のように変更します。win-acmeのデフォルト値は Let's Encryptのため、必ず変更します。
"Acme": {
"DefaultBaseUri": "https://acme.navercloudtrust.com/acme/directory"
}
2.RSAキーサイズを変更(必須)
Csrセクションを探し、以下のように変更します。
"Csr": {
"Rsa": {
"KeyBits": 2048,
"SignatureAlgorithm": "SHA256withRSA"
}
}
KeyBitsデフォルト値は 3072です。この値を変更しないと、DNS-01検証に成功しても CAが orderを invalid処理し、証明書の発行を拒否します。
設定完了後の重要な項目は、次の通りです。
| キーパス | 設定値 |
|---|---|
Acme.DefaultBaseUri |
https://acme.navercloudtrust.com/acme/directory |
Csr.Rsa.KeyBits |
2048 |
Csr.Rsa.SignatureAlgorithm |
SHA256withRSA |
ScheduledTask.RenewalDays |
45(証明書の有効期間が198日の場合の例。有効期限の1/3前から更新試行を推奨) |
ステップ2: DNSフックスクリプトを準備
win-acmeは DNS-01チャレンジ実行時にユーザーが準備した外部のスクリプトを呼び出し、DNS TXTレコードを作成・削除します。DNSフックスクリプトは別途提供しないため、直接作成します。Ncloud Global DNS APIを使用する例は、Ncloud Global DNSフックスクリプトの例をご参照ください。
フックスクリプトのシステム要件
win-acmeは DNS-01チャレンジ時に、以下の2つのスクリプトを順に呼び出します。各スクリプトは以下のロールを実行する必要があります。
作成スクリプト(dns-create)
win-acmeがチャレンジを開始する際に呼び出されます。以下を処理する必要があります。
- 受け取ったドメインに対応する DNS zoneを検索します。
_acme-challenge.<도메인>TXTレコードを作成します。- DNSの変更内容を反映します。
- DNS伝播が完了するまで、十分に待機します。(推奨: 60秒以上)
削除スクリプト(dns-delete)
チャレンジ検証が完了した後、呼び出されます。以下を処理する必要があります。
- 作成スクリプトで作成した TXTレコードを削除します。
- DNSの変更内容を反映します。
スクリプト呼び出しインターフェース
win-acmeはスクリプトを呼び出す際に、以下の引数を順に渡します。スクリプト作成時は、この形式に従います。
| 引数の順序 | 値 | 説明 |
|---|---|---|
| 1 | create / delete |
実行する動作 |
| 2 | ドメイン名 | 証明書を発行するドメイン(例: example.com) |
| 3 | レコード名 | TXTレコードの全体の名前(例: _acme-challenge.example.com) |
| 4 | トークン値 | CAが発行したチャレンジトークン(TXTレコード値に設定) |
Ncloud Global DNS API連携時の注意点
Ncloud Global DNS APIを使用する場合は、以下の事項を考慮してスクリプトを作成します。
- 全ての APIリクエストには、HMAC-SHA256署名ヘッダが必要です。以下の3つのヘッダを含める必要があります。
| ヘッダ | 値 |
|---|---|
x-ncp-apigw-timestamp |
リクエスト時間(Unixミリ秒) |
x-ncp-iam-access-key |
Ncloud IAM Access Key |
x-ncp-apigw-signature-v2 |
HMAC-SHA256署名値(Base64エンコード) |
- レコード作成・削除後は、必ず Apply APIを呼び出すことで DNSに反映されます。
- ワイルドカード証明書(
*.example.com)発行時は、ドメインの先頭の*.を削除し、zoneを検索します。 - サブドメイン証明書(
sub.example.com)発行時は、_acme-challenge.sub形式でホスト名を計算します。
スクリプトファイルは、必ず UTF-8 BOMエンコードで保存します。ANSI(CP949)エンコードで保存すると、PowerShell実行時に文字化けが発生し、一部の環境では誤動作します。
ステップ3: 証明書発行
スクリプトの準備が完了すると、管理者権限の PowerShellで以下のコマンドを実行します。括弧([ ])内の項目は、実際の値に置き換えます。
wacs.exe `
--source manual `
--host "[証明書を発行するドメイン]" `
--validation dnsscript `
--dnscreatescript "[作成スクリプトのパス]" `
--dnsdeletescript "[削除スクリプトのパス]" `
--store certificatestore `
--certificatestore WebHosting `
--installation iis `
--siteid [IISサイト ID] `
--eab-key-identifier "[EAB_KEY_ID]" `
--eab-key "[EAB_HMAC_KEY]" `
--emailaddress "[担当者のメールアドレス]" `
--accepttos `
--baseuri "https://acme.navercloudtrust.com/acme/directory"
主要パラメータの説明
| パラメータ | 説明 |
|---|---|
--source manual |
ドメインを直接指定 |
--host |
証明書を発行するドメイン |
--validation dnsscript |
スクリプトベースの DNS-01検証を使用 |
--dnscreatescript |
TXTレコード作成スクリプトのパス |
--dnsdeletescript |
TXTレコード削除スクリプトのパス |
--certificatestore WebHosting |
証明書を WebHostingストアに保存 |
--installation iis / --siteid |
IISサイトに証明書を自動バインド |
--eab-key-identifier |
EAB Key ID |
--eab-key |
EAB HMAC Key |
--accepttos |
ACME約款への自動同意 |
--baseuri |
ACMEのエンドポイント |
発行の流れ
| ステップ | 内容 |
|---|---|
| 1 | ACME CA接続確認 |
| 2 | EABキーで ACMEアカウントを登録 |
| 3 | 証明書の order作成、DNS-01チャレンジトークンの受信 |
| 4 | 作成スクリプトを呼び出し → TXTレコード作成 → DNS伝播待機 |
| 5 | CAが TXTレコードを確認 → 検証完了 |
| 6 | 削除スクリプトを呼び出す → TXTレコード削除 |
| 7 | RSA 2048 CSRを提出 → CA署名 |
| 8 | 証明書ダウンロード → WebHostingストアに保存 |
| 9 | IISサイトの 443バインドを自動的に置き換え |
| 10 | renewal.json保存(以降の自動更新に使用) |
問題が発生する場合、コマンドに --verboseオプションを追加すると、CAとの HTTPリクエスト・レスポンスを含む詳細ログが出力されます。初回登録または EAB置き換え後の再登録時に使用することを推奨します。
ステップ4: 自動更新設定
証明書登録後、管理者権限の PowerShellで以下のコマンドを実行し、Task Schedulerの自動更新タスクを登録します。
$action = New-ScheduledTaskAction `
-Execute "wacs.exeのパス全体" `
-Argument '--renew --baseuri "https://acme.navercloudtrust.com/acme/directory"'
$trigger = New-ScheduledTaskTrigger -Daily -At "09:00"
$principal = New-ScheduledTaskPrincipal `
-UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 2) `
-StartWhenAvailable
Register-ScheduledTask `
-TaskName "win-acme daily renew" `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Settings $settings `
-Force
登録されたタスクは毎日09:00に実行され、更新するかどうかを自動的に判断します。更新が必要な場合、DNS-01チャレンジを実行して、IISバインドを無停止に変更します。
更新ステータスを確認するには、以下のコマンドを実行します。
Get-ScheduledTaskInfo -TaskName "win-acme daily renew" |
Select-Object LastRunTime, LastTaskResult, NextRunTime
| LastTaskResult | 意味 |
|---|---|
0 |
更新成功 |
267011 |
更新不要(正常) |
| その他 | 失敗(ログ確認が必要) |
更新ログは、win-acmeデータディレクトリ内の Log\log-YYYYMMDD.txtファイルで確認できます。
証明書確認
発行された証明書は、Windows Certificate Storeの WebHosting ストレージに保存されます。以下の方法で確認します。
# IISバインドの証明書の thumbprintを確認
Get-WebBinding | Where-Object { $_.protocol -eq "https" } |
Select-Object bindingInformation, certificateHash
# WebHostingストアの証明書リストを照会
Get-ChildItem "Cert:\LocalMachine\WebHosting" |
Select-Object Subject, Thumbprint, NotAfter, Issuer
または Windows証明書の管理者(certlm.msc) → 証明書(ローカルコンピュータ) → ウェブホスト → 証明書で確認できます。
EABキーの置き換え
EABキーの置き換えが必要な場合、以下の手順で行います。既存の ACMEアカウントファイルが残っている場合、新しい EABキーは無視され、既存のアカウントが再利用されるため、必ずアカウントファイルを先に削除します。
- スクリプトまたは設定ファイルの EAB値を、新しく発行した値に置き換えます。
- win-acmeデータディレクトリ内の
Registration_v2、Signer_v2、*.renewal.jsonファイルを削除します。 - ステップ3の発行コマンドを再実行します。
成功すると、新しいアカウントで証明書が発行され、IISバインドが自動的に置き換えられます。Task Schedulerタスクは別途変更する必要はありません。
手動更新
テストまたは緊急対応が必要な場合は、以下のコマンドを使用します。
# 一般更新(期限切れ条件を満たした場合のみ実行)
wacs.exe --renew --baseuri "https://acme.navercloudtrust.com/acme/directory"
# 強制更新(条件に関係なく直ちに実行)
wacs.exe --renew --baseuri "https://acme.navercloudtrust.com/acme/directory" --force
# 詳細ログを出力して強制更新
wacs.exe --renew --baseuri "https://acme.navercloudtrust.com/acme/directory" --force --verbose
# Task Schedulerタスクを直ちに実行
Start-ScheduledTask -TaskName "win-acme daily renew"
# 登録された renewalリストを確認
wacs.exe --list --baseuri "https://acme.navercloudtrust.com/acme/directory"
トラブルシューティング
| 症状 | 原因 | 対応 |
|---|---|---|
order invalid(DNS検証は valid) |
RSAキーサイズが一致しない | settings.json → Csr.Rsa.KeyBits: 2048を確認 |
Authorization pending後に失敗 |
DNS伝播の遅延 | 作成スクリプトの待ち時間を90~120秒に延長 |
| TXTレコード検証に失敗 | Zone検索に失敗 | コンソールで当該ドメインが Global DNSに Zoneとして登録されているか確認 |
| API 401/403エラー | APIキーエラーまたは権限不足 | Ncloud IAMで Access Keyの有効性および Global DNS権限を再確認 |
| 韓国語ログの文字化け | フックファイルのエンコードエラー | フックファイルを UTF-8 BOMで再保存 |
| エラーなしで静かに失敗 | --verbose未使用 |
--verboseオプション追加後に再実行 |
| EAB置き換え後、既存のアカウントを再利用 | Registration_v2未削除 |
EABキーの置き換えの手順を参照 |
| Task Schedulerに失敗 | SYSTEMアカウント権限不足 | タスクプロパティ → 最高権限で実行がチェックされているか確認 |
| IISバインドの未更新 | 証明書の保存場所が一致しない | --certificatestore WebHostingパラメータを確認 |
ログを確認するには、以下のコマンドを実行します。
Get-Content "win-acmeデータのパス\Log\log-$(Get-Date -Format yyyyMMdd).txt" -Tail 100
セキュリティに関する勧告
- APIキーと EABキーが含まれた設定ファイルは、Gitリポジトリや共有フォルダに配置しません。
- Ncloud APIキーには、Global DNSサービスのみに対して最小権限を付与することを推奨します。
- win-acmeデータディレクトリ(ACMEアカウントの署名キーを含む)へのアクセス権限を、管理者アカウントに限定します。
- 証明書のプライベートキーが外部に漏れないようご注意ください。
- win-acmeおよび PowerShellは定期的に最新バージョンに保ちます。
Ncloud Global DNSフックスクリプトの例
以下は、Ncloud Global DNS APIを使用する DNSフックスクリプトの例です。
以下のスクリプトは参考例です。運用環境に合わせて十分に検討し、テストした上で使用します。スクリプトの変更・設定・実行結果に関する責任はユーザーにあります。
APIキーの設定ファイル: config.ps1
# ACMEサーバ(変更不要)
$ACME_SERVER = "https://acme.navercloudtrust.com/acme/directory"
# EABキー — Certificate Managerコンソールで発行された値に置き換え
$EAB_KID = "[EAB_KEY_ID]"
$EAB_HMAC_KEY = "[EAB_HMAC_KEY]"
# NCP Global DNS APIキー — NCP IAMコンソールで発行(Global DNSの権限が必要)
$NCP_ACCESS_KEY = "[NCP_ACCESS_KEY]"
$NCP_SECRET_KEY = "[NCP_SECRET_KEY]"
$NCP_DNS_API = "https://globaldns.apigw.ntruss.com"
# DNS伝播待ち時間(秒)。検証に失敗した場合は、90~120に延長します。
$DNS_PROPAGATION_WAIT = 60
# ACMEアカウントのメールアドレス
$ACME_EMAIL = "[ADMIN_EMAIL]"
TXTレコード作成フック: dns-create.ps1
# dns-create.ps1 — win-acme DNS-01作成フック(NCP Global DNS)
# UTF-8 BOMエンコードで保存すること
param(
[string]$Action,
[string]$Domain,
[string]$RecordName,
[string]$Token
)
$ConfigPath = Join-Path $PSScriptRoot "config.ps1"
. $ConfigPath
function Get-NcpHeaders {
param([string]$Method, [string]$Uri)
$timestamp = [long]([datetimeoffset]::UtcNow.ToUnixTimeMilliseconds())
$message = "$Method $Uri`n$timestamp`n$NCP_ACCESS_KEY"
$hmac = New-Object System.Security.Cryptography.HMACSHA256
$hmac.Key = [System.Text.Encoding]::UTF8.GetBytes($NCP_SECRET_KEY)
$sig = [Convert]::ToBase64String(
$hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($message))
)
return @{
"x-ncp-apigw-timestamp" = "$timestamp"
"x-ncp-iam-access-key" = $NCP_ACCESS_KEY
"x-ncp-apigw-signature-v2" = $sig
"Content-Type" = "application/json"
}
}
# ワイルドカード処理
$Domain = $Domain -replace "^\*\.", ""
# Zone検索
$zoneUri = "/v1/ncpdns/domain?page=1&size=100"
$zones = (Invoke-RestMethod -Uri "$NCP_DNS_API$zoneUri" `
-Headers (Get-NcpHeaders "GET" $zoneUri)).domainList
$matchedZone = $zones | Where-Object {
$Domain -eq $_.name -or $Domain.EndsWith(".$($_.name)")
} | Select-Object -First 1
if (-not $matchedZone) {
Write-Error "[エラー] NCP Global DNSで「$Domain」に対応する zoneが見つかりません。"
exit 1
}
$zoneId = $matchedZone.domainId
$zoneName = $matchedZone.name
# TXTレコードのホスト名を計算
if ($Domain -eq $zoneName) {
$hostName = "_acme-challenge"
} else {
$sub = $Domain -replace "\.$([regex]::Escape($zoneName))$", ""
$hostName = "_acme-challenge.$sub"
}
# TXTレコード作成
$createUri = "/v1/ncpdns/record/$zoneId"
$body = @{ type = "TXT"; host = $hostName; content = $Token; ttl = 60 } | ConvertTo-Json
$response = Invoke-RestMethod -Uri "$NCP_DNS_API$createUri" -Method POST `
-Headers (Get-NcpHeaders "POST" $createUri) -Body $body
if (-not $response.recordId) {
Write-Error "[エラー] TXTレコードの作成に失敗しました。"
exit 1
}
# レコード IDの一時保存(削除フックで使用)
$tmpFile = "$env:TEMP\ncp_sid_$($Domain -replace '\*','_').json"
@{ recordId = $response.recordId; domainId = $zoneId } | ConvertTo-Json |
Set-Content $tmpFile -Encoding UTF8
# DNS反映
$applyUri = "/v1/ncpdns/domain/$zoneId/apply"
Invoke-RestMethod -Uri "$NCP_DNS_API$applyUri" -Method PUT `
-Headers (Get-NcpHeaders "PUT" $applyUri) | Out-Null
Write-Host "DNS TXTレコード作成完了。$DNS_PROPAGATION_WAIT秒待機中…"
Start-Sleep -Seconds $DNS_PROPAGATION_WAIT
TXTレコード削除フック: dns-delete.ps1
# dns-delete.ps1 — win-acme DNS-01削除フック(NCP Global DNS)
# UTF-8 BOMエンコードで保存すること
param(
[string]$Action,
[string]$Domain,
[string]$RecordName,
[string]$Token
)
$ConfigPath = Join-Path $PSScriptRoot "config.ps1"
. $ConfigPath
function Get-NcpHeaders {
param([string]$Method, [string]$Uri)
$timestamp = [long]([datetimeoffset]::UtcNow.ToUnixTimeMilliseconds())
$message = "$Method $Uri`n$timestamp`n$NCP_ACCESS_KEY"
$hmac = New-Object System.Security.Cryptography.HMACSHA256
$hmac.Key = [System.Text.Encoding]::UTF8.GetBytes($NCP_SECRET_KEY)
$sig = [Convert]::ToBase64String(
$hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($message))
)
return @{
"x-ncp-apigw-timestamp" = "$timestamp"
"x-ncp-iam-access-key" = $NCP_ACCESS_KEY
"x-ncp-apigw-signature-v2" = $sig
}
}
$Domain = $Domain -replace "^\*\.", ""
$tmpFile = "$env:TEMP\ncp_sid_$($Domain -replace '\*','_').json"
$sid = Get-Content $tmpFile -ErrorAction SilentlyContinue | ConvertFrom-Json
if (-not $sid) {
Write-Warning "[警告] 削除するレコード情報が見つかりません。"
exit 0
}
# TXTレコード削除
$deleteUri = "/v1/ncpdns/record/$($sid.domainId)/$($sid.recordId)"
Invoke-RestMethod -Uri "$NCP_DNS_API$deleteUri" -Method DELETE `
-Headers (Get-NcpHeaders "DELETE" $deleteUri) | Out-Null
# DNS反映
$applyUri = "/v1/ncpdns/domain/$($sid.domainId)/apply"
Invoke-RestMethod -Uri "$NCP_DNS_API$applyUri" -Method PUT `
-Headers (Get-NcpHeaders "PUT" $applyUri) | Out-Null
Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
Write-Host "DNS TXTレコード削除完了。"