してみるテストロゴ
Apache 2.4系でHTTP/2サーバを構築してみるテスト。

nsdを使って、Let's Encryptのdns-01認証チャレンジにより証明書を取得してみる。

2018年1月10日に、Let's Encryptのドメイン認証方法のひとつである、TLS-SNIチャレンジに脆弱性が発見されました。

そのため権威DNSを使ってドメイン認容を行う、dns-01認証チャレンジを使って、Let's Encryptから証明書をとる方法が、推奨される取得方法のひとつになっています。

dns-01認証チャレンジを行うには、hookスクリプトと呼ばれるスクリプトを用意する必要があります。

acme.shにも、たいていのクラウドサービス向けのhookスクリプトが標準で付いてきます。

ところが、DNS権威サーバとしてnsdを使う場合、クラウドサービスと違って、「hookスクリプト」が標準では用意されていません。

いまどき、自前でDNS権威サーバを持とうとするのが、時代遅れという風潮すら感じる今日この頃ですが、hookスクリプトが無くて困っている方も居ると思います。

そこで、今回は、hookスクリプトを用意して、nsdと、acme.shを使って、dns-01認証チャレンジを行う方法をご紹介いたします。

 

nsdのインストールと設定は、以下のページを参考に済ませておいてください。

今回用意したhookスクリプトは、ゾーンファイルのファイル名に制限があります。とはいえ、簡単な改造で、他の命名規則に対応できると思います。

また、nsd以外のbindゾーンファイル互換の権威サーバにも、簡単な改造で対応できるとおもいます。

 

acme.shのインストールは以下を参考に済ませておいてください。

nsdの準備

まず、

を参考に、nsdが起動して、お手持ちのドメイン名の名前解決ができる状態まで済ませておいてください。

ここでは、ドメイン名として、example.comを例にします。

# vi /etc/nsd/nsd.conf
/etc/nsd/nsd.conf
(…省略…)
username: nsd
logfile: "/var/log/nsd.log"
(…省略…)
zone:
        name: "example.com"
        zonefile: "example.com"

上記リンク先の内容通りですが、このように、ゾーンファイルのファイル名がドメイン名と一致するように、設定してください。

そして、以下のようなゾーンファイルを想定しています。

# vi /etc/nsd/example.com
/etc/nsd/example.com
$ORIGIN example.com.
$TTL 1H
@           IN      SOA     ns1.example.com. postmaster.example.com. (
                        2016051001      ;serial
                        1H              ;refresh
                        1H              ;retry
                        1H              ;Expire
                        1H              ;Minimum
                        );

                        IN      NS      ns1.example.com.

ns1                     IN      A      xxx.xxx.xxx.1
                        IN      AAAA    240d:XXXX:XXXX:XXXX::2

www                     IN      A       xxx.xxx.xxx.1
                        IN      AAAA    240d:XXXX:XXXX:XXXX::2
  

このゾーンファイルの末尾に、認証用のテキストレコードを一時的に追加するフックスクリプトになります。

 

冒頭でも書いたように、nsdのゾーンファイル名を、お使いのドメイン名と一致するようにしてください。

そうしないと、今回用意したhookスクリプトは使えません。

たとえば、ドメイン名が、「example.com」であれば、nsdのゾーンファイルを、/etc/nsd/example.comのパスに用意してください。

hookスクリプトのインストール

今回用意したhookスクリプトは、ゾーンファイルのファイル名、つまりドメイン名が、第2階層の場合と、第3階層の場合で別の物を使います。

たとえば、example.comは、ピリオドが一つで、2つの単語を区切っています。これは、第2階層のドメイン名です。

一方、example.co.jpは、ピリオドが2つで、3つの単語を区切っています。これは、第3階層のドメイン名です。

 

第2階層のドメインがゾーンファイル名になっている場合は、以下のdns_nsd_lv2.shのスクリプトを利用します。

配布ライセンスは修正BSDライセンスです。

これを~/.acme.sh/dnsapiディレクトリに配置します。

FreeBSDの場合は、gsedをインストールして下さい。

緑色の部分は、みなさんの環境にあわせて変更してください。(以下の例は、FreeBSD11.0の場合です。)

# pkg install gsed    #for FreeBSD
# cd ~/.acme.sh/dnsapi 
# vi dns_nsd_lv2.sh 
~/.acme.sh/dns_nsd_lv2.sh
#!/usr/bin/env sh

# Copyright (c) 2018 non-standard programmer All rights reserved.

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright notice, 
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice, 
# this list of conditions and the following disclaimer in the documentation 
# and/or other materials provided with the distribution.
# * Neither the name of the "non-standard programmer" nor the names of its contributors 
# may be used to endorse or promote products derived from this software 
# without specific prior written permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL  BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

CP="/bin/cp -p"
MV="/bin/mv -f"
SED="/usr/local/bin/gsed -e"
RELOAD="/usr/local/sbin/nsdc reload"
ZONE_FILE_DIR="/etc/nsd"

dns_nsd_lv2_add() {
  fulldomain=$1
  txtvalue=$2
  ZONE_FILE_PATH=${ZONE_FILE_DIR}/`echo $1 | ${SED} "s/.*\.\([^\.]\+\.[^\.]\+\)/\1/"`
  _info "Using nsd"
  _debug fulldomain "$fulldomain"
  _debug txtvalue "$txtvalue"
  _debug zonefile "${ZONE_FILE_PATH}"

if [ ! -e ${ZONE_FILE_PATH} ]; then
        _debug zone_file_path "${ZONE_FILE_PATH} does not exist."
        return 1
fi

if [ ! -e ${ZONE_FILE_PATH}.org ]; then
${CP} ${ZONE_FILE_PATH} ${ZONE_FILE_PATH}.org
fi

if [ ! -e ${ZONE_FILE_PATH}.org ]; then
        _debug zone_file_copy "${ZONE_FILE_PATH}.org does not exist."
        return 1
fi

echo  ${fulldomain}. IN TXT ${txtvalue} >> ${ZONE_FILE_PATH}

${RELOAD}

  return 0
}

dns_nsd_lv2_rm() {
  fulldomain=$1
  txtvalue=$2
  ZONE_FILE_PATH=${ZONE_FILE_DIR}/`echo $1 | ${SED} "s/.*\.\([^\.]\+\.[^\.]\+\)/\1/"`
  _info "Using nsd"
  _debug fulldomain "$fulldomain"
  _debug txtvalue "$txtvalue"

if [ -e ${ZONE_FILE_PATH}.org ]; then
${MV} ${ZONE_FILE_PATH}.org ${ZONE_FILE_PATH}
${RELOAD}
fi

}

たとえば、FreeBSD以外なら、

上記SED変数を変更せず、gnu sedを/usr/local/bin/gsedにインストールするか、

上記SED変数をgsedがインストールされているパスに変更してください。

Solarisなら、SED=/bin/gsed -e

といった感じです。

nsd以外のデーモンをお使いの方は、RELOADとZONE_FILE_DIRの2つを変更してください。

 

第3階層のドメイン名(たとえばexample.co.jp)がゾーンファイルのファイル名の場合は、以下のファイルをダウンロードしてお使いください。

第4階層以降のレベルのドメイン名をご利用の方は、sedの正規表現を書き換えてみてください。

この他、改造などする方には、こちらが参考になります。

第2階層/第3階層のドメイン名に対応したhookスクリプト(dns_nsd_lv2.sh/dns_nsd_lv3.sh)をダウンロード⇒ [dns_nsd_1.0.1.tar.gz]

dns-01認証チャレンジで証明書の発行

hookスクリプトのインストールが終わったら、早速試します。

acme.shでdns-01認証チャレンジを行うには、--dnsオプションを使い、hookスクリプト(dns_nsd_lv2.sh等)を指定します。

また、クラウドのDNSサービスは反映まで時間がかかりますが、自前のnsdならすぐに反映するので、待機時間を指定する、--dnssleepオプションは、1を指定します。

ちなみに、デフォルトのままだと、120秒も待たされます。

 

まず、acme.shのあるディレクトリに移動します。

# cd ~/.acme.sh 

第2階層のドメイン名をゾーンファイル名にして、RSA暗号の証明書を取得する場合は、

# ./acme.sh --issue --dns dns_nsd_lv2 --dnssleep 1 -d example.com

とすると、/etc/nsd/example.comファイルを使います。

実行してみると、

# ./acme.sh --issue --dns dns_nsd_lv2 --dnssleep 1 -d example.com
[2018年 1月XX日 X曜日 XX時50分14秒 JST] Single domain='example.com'
[2018年 1月XX日 X曜日 XX時50分14秒 JST] Getting domain auth token for each domain
[2018年 1月XX日 X曜日 XX時50分14秒 JST] Getting webroot for domain='example.com'
[2018年 1月XX日 X曜日 XX時50分14秒 JST] Getting new-authz for domain='example.com'
[2018年 1月XX日 X曜日 XX時50分15秒 JST] The new-authz request is ok.
[2018年 1月XX日 X曜日 XX時50分16秒 JST] example.com is already verified, skip dns-01.
[2018年 1月XX日 X曜日 XX時50分16秒 JST] Verify finished, start to sign.
[2018年 1月XX日 X曜日 XX時50分16秒 JST] Cert success.
-----BEGIN CERTIFICATE-----
MII…(省略)…=
-----END CERTIFICATE-----
[2018年 1月XX日 X曜日 XX時50分16秒 JST] Your cert is in  /home/username/.acme.sh/example.com/example.com.cer
[2018年 1月XX日 X曜日 XX時50分16秒 JST] Your cert key is in  /home/username/.acme.sh/example.com/example.com.key
[2018年 1月XX日 X曜日 XX時50分17秒 JST] The intermediate CA cert is in  /home/username/.acme.sh/example.com/ca.cer
[2018年 1月XX日 X曜日 XX時50分17秒 JST] And the full chain certs is there:  /home/username/.acme.sh/example.com/fullchain.cer

といった感じで、証明書が取れます。

当然、

# ./acme.sh --issue --dns dns_nsd_lv2 --dnssleep 1 -d www.example.com

といったサブドメインを付けてもOKです。

 

おなじく、ECDSA暗号の証明書を取得する場合は、

# ./acme.sh --issue --dns dns_nsd_lv2 --dnssleep 1 -d example.com -k ec256

とします。

 

ちなみに、第3階層のドメイン名をゾーンファイル名にして、RSA暗号の証明書を取得する場合は、

# ./acme.sh --issue --dns dns_nsd_lv3 --dnssleep 1 -d example.co.jp

とすると、/etc/nsd/example.co.jpファイルを使います。

おなじく、ECDSA暗号の証明書を取得する場合は、

# ./acme.sh --issue --dns dns_nsd_lv3 --dnssleep 1 -d example.co.jp -k ec256

とします。

Webサーバの設定を追加(RSA暗号の証明書)

Apache Webサーバのインストールなどは当サイトのインストールするものHTTP/2の各種設定 Let's Encrypt(無償の証明書発行機関)から取得の設定があることを前提に書いています。

このApacheの、バーチャルホスト設定に、

RSA暗号の証明書なら、

サーバ証明書ファイル( /home/username/.acme.sh/example.com/example.com.cer)

秘密鍵ファイル( /home/username/.acme.sh/example.comt/example.com.key)

中間証明書ファイル( /home/username/.acme.sh/example.com/ca.cer)

を追加します。

usernameの部分は、みなさんのログイン名が入ります。

# vi /usr/local/apache2/conf/extra/httpd-ssl-vhosts.conf
/usr/local/apache2/conf/extra/httpd-ssl-vhosts.conf
<VirtualHost *:443>

	SSLEngine on

# for RSA
	SSLCertificateFile "/home/username/.acme.sh/example.com/example.com.cer"
	SSLCertificateKeyFile "/home/username/.acme.sh/example.com/example.com.key"
	SSLCertificateChainFile "/home/username/.acme.sh/example.com/ca.cer"

	DocumentRoot "/var/https/example.comhtdocs/"
	ServerName example.com:443
	ServerAdmin webmaster@example.com
    
	<Directory /var/https/example.com/htdocs>
		Options -Indexes
		AllowOverride All
		Require all granted
	</Directory>
	<FilesMatch "\.(cgi|shtml|phtml|php)$">
		SSLOptions +StdEnvVars
	</FilesMatch>
    
	ErrorLog "|/usr/local/apache2/bin/rotatelogs -l /var/log/https/example.com/error_log.%Y%m%d 86400"
	CustomLog "|/usr/local/apache2/bin/rotatelogs -l /var/log/https/example.com/access_log.%Y%m%d 86400" combined
	CustomLog "|/usr/local/apache2/bin/rotatelogs -l /var/log/https/example.com/ssl_request_log.%Y%m%d 86400" "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
    
    </VirtualHost>
  

設定の反映は、apachctl -tで、設定ファイルの間違いが無いこと(Syntax OKが表示されること)を確認の上、下記のように

# /usr/local/apache2/bin/apachctl -t
Syntax OK
# /usr/local/apache2/bin/apachctl stop
# /usr/local/apache2/bin/apachctl start

とします。

Webサーバの設定を追加(ECDSA暗号の証明書)

ECDSA暗号の証明書なら、

サーバ証明書ファイル( /home/username/.acme.sh/example.com_ecc/example.com.cer)

秘密鍵ファイル( /home/username/.acme.sh/example.com_ecc/example.com.key)

中間証明書ファイル( /home/username/.acme.sh/example.com_ecc/ca.cer)

を追加します。

usernameの部分は、みなさんのログイン名が入ります。

# vi /usr/local/apache2/conf/extra/httpd-ssl-vhosts.conf
/usr/local/apache2/conf/extra/httpd-ssl-vhosts.conf
<VirtualHost *:443>

	SSLEngine on

# for ECDSA
	SSLCertificateFile "/home/username/.acme.sh/example.com_ecc/example.com.cer"
	SSLCertificateKeyFile "/home/username/.acme.sh/example.com_ecc/example.com.key"
	SSLCertificateChainFile "/home/username/.acme.sh/example.com_ecc/ca.cer"

	DocumentRoot "/var/https/example.comhtdocs/"
	ServerName example.com:443
	ServerAdmin webmaster@example.com
    
	<Directory /var/https/example.com/htdocs>
		Options -Indexes
		AllowOverride All
		Require all granted
	</Directory>
	<FilesMatch "\.(cgi|shtml|phtml|php)$">
		SSLOptions +StdEnvVars
	</FilesMatch>
    
	ErrorLog "|/usr/local/apache2/bin/rotatelogs -l /var/log/https/example.com/error_log.%Y%m%d 86400"
	CustomLog "|/usr/local/apache2/bin/rotatelogs -l /var/log/https/example.com/access_log.%Y%m%d 86400" combined
	CustomLog "|/usr/local/apache2/bin/rotatelogs -l /var/log/https/example.com/ssl_request_log.%Y%m%d 86400" "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
    
    </VirtualHost>
  

設定の反映は、apachctl -tで、設定ファイルの間違いが無いこと(Syntax OKが表示されること)を確認の上、下記のように

# /usr/local/apache2/bin/apachctl -t
Syntax OK
# /usr/local/apache2/bin/apachctl stop
# /usr/local/apache2/bin/apachctl start

とします。

Webサーバの設定を追加(マルチアルゴリズム証明書)

RSA暗号の証明書と両方とって、マルチアルゴリズム証明書にしても良いでしょう。

バーチャルホスト設定に、

RSA暗号の証明書の

サーバ証明書ファイル( /home/username/.acme.sh/example.com/example.com.cer)

秘密鍵ファイル( /home/username/.acme.sh/example.comt/example.com.key)

中間証明書ファイル( /home/username/.acme.sh/example.com/ca.cer)

と、ECDSA暗号の証明書の、

サーバ証明書ファイル( /home/username/.acme.sh/example.com_ecc/example.com.cer)

秘密鍵ファイル( /home/username/.acme.sh/example.com_ecc/example.com.key)

中間証明書ファイル( /home/username/.acme.sh/example.com_ecc/ca.cer)

の両方を追加します。

usernameの部分は、みなさんのログイン名が入ります。

# vi /usr/local/apache2/conf/extra/httpd-ssl-vhosts.conf
/usr/local/apache2/conf/extra/httpd-ssl-vhosts.conf
<VirtualHost *:443>

	SSLEngine on

# for RSA
	SSLCertificateFile "/home/username/.acme.sh/example.com/example.com.cer"
	SSLCertificateKeyFile "/home/username/.acme.sh/example.com/example.com.key"
	SSLCertificateChainFile "/home/username/.acme.sh/example.com/ca.cer"

# for ECDSA
	SSLCertificateFile "/home/username/.acme.sh/example.com_ecc/example.com.cer"
	SSLCertificateKeyFile "/home/username/.acme.sh/example.com_ecc/example.com.key"
	SSLCertificateChainFile "/home/username/.acme.sh/example.com_ecc/ca.cer"

	DocumentRoot "/var/https/example.comhtdocs/"
	ServerName example.com:443
	ServerAdmin webmaster@example.com
    
	<Directory /var/https/example.com/htdocs>
		Options -Indexes
		AllowOverride All
		Require all granted
	</Directory>
	<FilesMatch "\.(cgi|shtml|phtml|php)$">
		SSLOptions +StdEnvVars
	</FilesMatch>
    
	ErrorLog "|/usr/local/apache2/bin/rotatelogs -l /var/log/https/example.com/error_log.%Y%m%d 86400"
	CustomLog "|/usr/local/apache2/bin/rotatelogs -l /var/log/https/example.com/access_log.%Y%m%d 86400" combined
	CustomLog "|/usr/local/apache2/bin/rotatelogs -l /var/log/https/example.com/ssl_request_log.%Y%m%d 86400" "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
    
    </VirtualHost>
  

設定の反映は、apachctl -tで、設定ファイルの間違いが無いこと(Syntax OKが表示されること)を確認の上、下記のように

# /usr/local/apache2/bin/apachctl -t
Syntax OK
# /usr/local/apache2/bin/apachctl stop
# /usr/local/apache2/bin/apachctl start

とします。

cronによる再取得

acme.shは、証明書の期限が、30日を切らなければ、再取得を行いません。

そのため、Let's Encryptの発行回数の制限には到達しにくいので、毎日cronに登録して実行しておくといいでしょう。

管理者権限(root)で、登録しましょう。

たとえば、毎日の午前3時30分に証明書の再取得し、Apacheのリスタートをするとすれば、

# crontab -e
/var/spool/cron/crontabs/root/
30 3 * * * "/home/username/.acme.sh"/acme.sh --cron --home "/home/username/.acme.sh" --reloadcmd "/usr/local/apache/bin/apachectl restart" > /dev/null

となります。これで、Let's Encryptのサービスが終わったり、障害が起きない限り、有効な証明書が、永久に維持され続けます。

[cronによる取得の制限] >> Let's Encryptのサーバ証明書を自動更新
NEXT >> Let's Encryptの機能のまとめ

©Copyrights 2015-2018, non-standard programmer

このサイトは、あくまでも私の個人的体験を、綴ったものです。 軽く参考程度にご利用ください。