Documentation Index

Fetch the complete documentation index at: https://guide.ncloud-docs.com/llms.txt

Use this file to discover all available pages before exploring further.

win-acmeベース Windows Server証明書の自動化

Prev Next

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インストール

  1. win-acme公式リリースページで v2.2.9以上の release、trimmed、standalone、64-bitバージョンをダウンロードします。
  2. 目的のパスに圧縮ファイルを解凍します。

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がチャレンジを開始する際に呼び出されます。以下を処理する必要があります。

  1. 受け取ったドメインに対応する DNS zoneを検索します。
  2. _acme-challenge.<도메인> TXTレコードを作成します。
  3. DNSの変更内容を反映します。
  4. DNS伝播が完了するまで、十分に待機します。(推奨: 60秒以上)

削除スクリプト(dns-delete)
チャレンジ検証が完了した後、呼び出されます。以下を処理する必要があります。

  1. 作成スクリプトで作成した TXTレコードを削除します。
  2. 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キーは無視され、既存のアカウントが再利用されるため、必ずアカウントファイルを先に削除します。

  1. スクリプトまたは設定ファイルの EAB値を、新しく発行した値に置き換えます。
  2. win-acmeデータディレクトリ内の Registration_v2Signer_v2*.renewal.jsonファイルを削除します。
  3. ステップ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.jsonCsr.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レコード削除完了。"