OpenBSD acme-client で Let's Encrypt 証明書を取得する

@nabbisen

はじめに

OpenBSD の acme-client

acme-clientOpenBSD で標準の自動証明書管理環境 (Automatic Certificate Management Environment, ACME) のためのクライアントです。このソフトウェアは OS インストール時にインストールされます。

acme-client.conf で設定しますが、これは難しくありません。そしてそれによって Let’s Encrypt や他の認証局から証明書を取得できるようになります。

基本的な使い方はこのような感じです:

# # 設定
# nvim /etc/acme-client.conf

# # 証明書の取得または (リニューアルのための) 更新
# acme-client <domain>

# # 証明書の無効化 (失効させる)
# acme-client -r <domain>

CertBot について

余談ですが、有名な他の選択肢がありますね。そうです、CertBot です。電子フロンティア財団 (Electronic Frontier Foundation, EFF) から提供されている、非常によく使われているクライアントです。pkg_add certbot の実行でインストールすることは可能です。しかしながら、このソフトウェアを OpenBSD 環境下で使うと、証明書リニューアルの時に パーミッションの問題が発生することがあります (英語)

環境

  • OS: OpenBSD 7.0
  • ACME クライアント: OpenBSD acme-client
  • 証明書の認証局: Let’s Encrypt
  • Web サーバー: OpenBSD httpd

使い方

acme-client.conf の設定

OpenBSD から公式に提供されている設定ファイルがあります。それを見てみましょう。

$ cat /etc/examples/acme-client.conf

わかりやすい例が書かれています:

#
# $OpenBSD: acme-client.conf,v 1.4 2020/09/17 09:13:06 florian Exp $
#
authority letsencrypt {
	api url "https://acme-v02.api.letsencrypt.org/directory"
	account key "/etc/acme/letsencrypt-privkey.pem"
}

authority letsencrypt-staging {
	api url "https://acme-staging-v02.api.letsencrypt.org/directory"
	account key "/etc/acme/letsencrypt-staging-privkey.pem"
}

authority buypass {
	api url "https://api.buypass.com/acme/directory"
	account key "/etc/acme/buypass-privkey.pem"
	contact "mailto:[email protected]"
}

authority buypass-test {
	api url "https://api.test4.buypass.no/acme/directory"
	account key "/etc/acme/buypass-test-privkey.pem"
	contact "mailto:[email protected]"
}

domain example.com {
	alternative names { secure.example.com }
	domain key "/etc/ssl/private/example.com.key"
	domain full chain certificate "/etc/ssl/example.com.fullchain.pem"
	sign with letsencrypt
}

コピーして使いましょう:

$ # 初期状態では以下を実行しても何も出ない (余談)
$ ls /etc/acme-client*

$ doas cp -p /etc/examples/acme-client.conf /etc/

設定ファイルを編集しましょう:

$ doas nvim /etc/acme-client.conf

例えば以下のような内容です:

- domain example.com {
- 	alternative names { secure.example.com }
- 	domain key "/etc/ssl/private/example.com.key"
- 	domain full chain certificate "/etc/ssl/example.com.fullchain.pem"
- 	sign with letsencrypt
- }
+ domain your.domain {    
+ 	#alternative names { secure.example.com }    
+ 	domain key "/etc/ssl/private/your.domain.key"    
+ 	domain full chain certificate "/etc/ssl/your.domain.fullchain.pem"    
+ 	sign with letsencrypt    
+ }

さあこれで your.domain について “sign with letsencrypt (Let’s Encrypt でサインする)” 準備が整いました。

OpenBSD httpd (Web サーバー) を起動する

httpd が egress をリッスンしていることが必要です:

server "your.domain" {
	listen on egress port 80
	# ...
}

ちなみに httpd.conf のサンプルファイルも /etc/examples/ で見付けられます。

もちろんこのデーモンを起動させておく必要があります:

$ # httpd が有効化されていない場合:
$ doas rcctl start -f httpd
$ # 有効化されている場合:
$ doas rcctl restart httpd

証明書をリクエストする

以下の一行のコマンドを実行するだけです。そうすればうれしいことに証明書を取得できます:

$ doas acme-client -v your.domain

ここで -v は verbose 出力のためのもので、省略可能です。

他の使い方もあります。更新は次のように行います:

$ # 上と同じコマンド
$ doas acme-client your.domain

さらに証明書を失効させることもできます:

$ doas acme-client -r your.domain

補足

定期的な更新が必要

Let’s Encrypt 証明書の有効期間は 90 日間しか無いことを憶えておきましょう。定期的に更新する必要があります。

対応策の一つに cron の使用が考えられます。

Let’s Encrypt には制限 (Rate Limits) が存在する

Let’s Encrypt にはいくつかのレート制限があることに注意してください。

一例ですが、

  • “Certificates per Registered Domain” (登録ドメインごとの証明書)。1 週間あたり 50 件までです。

また他の例ですが、

  • “Duplicate Certificate” (証明書の重複)。同じ証明書を更新できるのは 1 週間に最大 4 回 (合計で 5 回) までです。

これらに関わる公式ドキュメントは こちら (英語) です。

サイド・ストーリー: 歴史と経緯

acme-client は初めて登場したのは 6.1 のリリースでした。

6.5 あるいはそれ以前 (ただし 6.1 以降) では、証明書取得のために指定しなければならないコマンドオプションの数が、現在よりも多かったです:

$ doas acme-client -ADv your.domain

しかし 6.6 で acme-client が v02 Let’s Encrypt API とやり取りできるようになりました。それによって -A および -D オプションは不要になったのです。ありがたい改善です。

トラブルシューティング: チャレンジが失敗した場合

以下のようなエラーが出て、失敗に終わり悲しい思いをすることがあるかもしれません:

acme-client: /etc/ssl/private/your.domain: generated RSA domain key
acme-client: https://acme-v02.api.letsencrypt.org/directory: directories
acme-client: acme-v02.api.letsencrypt.org: DNS: xxx.xxx.xxx.xxx
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84303399960
acme-client: challenge, token: (...), uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84303399960/MQGXJQ, status: 0
acme-client: /var/www/acme/(...): created
acme-client: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84303399960/MQGXJQ: challenge
acme-client: order.status 0
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84303399960
acme-client: challenge, token: (...), uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84303399960/MQGXJQ, status: -1
acme-client: order.status -1
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84303399960
acme-client: Invalid response from http://your.domain/.well-known/acme-challenge/(...) [2606:4700:3031::6815:3ae6]: \\"\\u003c!DOCTYPE html\\u003e\\\\n\\u003chtml\\u003e\\\\n\\u003chead\\u003e\\\\n\\u003cmeta charset=\\\\\\"utf-8\\\\\\"\\u003e\\\\n\\u003ctitle\\u003e404 Not Found\\u003c/title\\u003e\\\\n\\u003cstyle type=\\\\\\"text/css\\\\\\"\\u003e\\u003c!--\\\\nbody { background-\\"
acme-client: bad exit: netproc(87417): 1

これは 404 つまり ページが見付かりません エラーです。別のケースもあります。403 つまり アクセスが拒否されました エラーです。

これらはあなたの Web サーバーが “不正なレスポンス (bad response)” を返したことによるものです。チャレンジに対するレスポンスを正しくマッピングしてあげましょう:

  server "your.domain" {
  	listen on egress port 80
+ 	# acme-client
+ 	location "/.well-known/acme-challenge/*" {
+ 		root "/acme"
+ 		request strip 2
+ 	}
  	# ...
  }

その後に httpd を再起動します:

$ # デーモンを有効化していない場合:
$ doas rcctl -f restart httpd
$ # 有効化している場合:
$ doas rcctl restart httpd

これで直るかもしれません。

# acme-client -v your.domain

以下のようなメッセージに変われば成功です:

acme-client: https://acme-v02.api.letsencrypt.org/directory: directories
acme-client: acme-v02.api.letsencrypt.org: DNS: xxx.xxx.xxx.xxx
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84304202350
acme-client: challenge, token: (...), uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84304202350/NVmlRA, status: 0
acme-client: /var/www/acme/(...): created
acme-client: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84304202350/NVmlRA: challenge
acme-client: order.status 0
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84304202350
acme-client: challenge, token: (...), uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84304202350/NVmlRA, status: 2
acme-client: order.status 1
acme-client: https://acme-v02.api.letsencrypt.org/acme/finalize/58118741/68735636600: certificate
acme-client: order.status 3
acme-client: https://acme-v02.api.letsencrypt.org/acme/cert/(...): certificate
acme-client: /etc/ssl/your.domain: created

おわりに

Let’s Encrypt の証明書を取得できました ! あとはそれを httpd.conf で設定するだけです。

  server "your.domain" {
  	listen on * port 80
  	# acme-client
  	location "/.well-known/acme-challenge/*" {
  		root "/acme"
  		request strip 2
  	}
+ 	block return 301 "https://$SERVER_NAME$REQUEST_URI"
  }
+ server "your.domain" {
+ 	listen on * tls port 443
+ 	tls {
+ 		certificate	"/etc/ssl/your.domain.fullchain.pem"
+ 		key		"/etc/ssl/private/your.domain.key"
+ 	}
+ 	# acme-client
+ 	location "/.well-known/acme-challenge/*" {
+ 		root "/acme"
+ 		request strip 2
+ 	}
+ 	# ...
+ }

ここで、私は egress* に変更しました。これは lo にも適用したかったからです。必須の変更ではありません。

暗号化による TLS 接続すなわち HTTPS 通信の基盤が完成しました。

Happy connecting securely🕊

Series

Secure Connection
  1. LibreSSL: openssl エラー - v3_ca エクステンションが存在しない
  2. PostgreSQL 12: TLS 接続
  3. PostgreSQL 14: TLS 接続
  4. OpenBSD acme-client で Let's Encrypt 証明書を取得する

Comments or feedbacks are welcomed and appreciated.