目次

acme-client

Let's Encrypt(以下LE)は、無料でDomain Validation(以下DV)証明書を発行してくれる機関である。 このLEは、申請から証明書発行までを人手を介さずシステムだけで処理してくれる、自動化に非常に向いている仕組みを提供している。

Let's Encrypt

以前、Let's Encryptを少し調べてみたで調査した内容をまとめる。

Let's Encryptとは?

LEは、Internet Security Research Group (ISRG) が運営する、無料で利用でき、自動化されていて、オープンな認証局。

Let's Encryptの基本方針は

(以上、About - Let's Encrypt - Free SSL/TLS Certificatesより抜粋)

実際に利用するレベルから見ると、

というサービスを提供してくれるサイト及びサービスである。

日本語訳のサイト: Let's Encrypt 総合ポータル

注意点

つまり、あくまで、「あるドメイン」が、「申請されたタイミング」で、「管理されているであろう」かつ「存在している」ことのみを証明するものである。 逆に、「仮に乗っ取られていたとしても申請を受けたLE側では判断しない」(管理組織が正当であるかどうかはドメインが存在しているかどうかとは独立の問題なので)ものである。

証明書の取得方法

Let's EncryptはDV証明書を発行するにあたって、ACME(Automatic Certificate Management Environment)を使用する。 このACMEは2017/03/13に、draft-ietf-acme-acme-06として更新されており、将来RFCになることが期待されている。

このACMEを利用した証明書取得のためのツールは様々ある。LEで把握しているACMEの実装に関しては、https://letsencrypt.org/docs/client-options/を参照すること。

acmeで証明書を取得・失効する流れ

acme-clientのManual Pageから抄訳

証明書取得の流れは以下の通り。

  1. CA(Let's EncryptのACME Server)に接続し、リソースリストを要求する
  2. (Option) RSAアカウント鍵を作成・登録する
  3. RSAアカウント鍵の確認。これは、CAへの認証とその後すべてのやりとりに利用される
  4. 各domainごとに以下の処理を行う
    1. CAに対し、認証のChallengeを提出
    2. Challengeへの返答ファイルを作成する
    3. Cアのchallengeによる検証を待つ
  5. 展開されたRSAもしくはECDSAのdomain鍵を読み込む
  6. そのdomain及びaltnamesに記載されたドメイン用の、domain鍵を用いたX509リクエストを作成する
  7. X509リクエストへの署名をCAに要求する
  8. X509証明書(Certificate)をダウンロードする
  9. CA issureから来たX509証明書を展開する
  10. 証明書チェーンをissureからダウンロードする

証明書失効の流れも同様。

  1. CA(Let's EncryptのACME Server)に接続し、リソースリストを要求する
  2. (もし見つかれば)、X509証明書を読み込み展開する
  3. X509 失効要求書を作成する
  4. CAに失効要求書を提出
  5. 証明書、チェン、そして、全チェーンを削除する。

Challenge

ACMEにおけるChallengeとは、要するに「存在確認」を意味する。この存在確認方法として以下の3つがIDに定義されている。

FreeBSDでのLEの証明書取得から更新

FreeBSD 11.0 RELEASE-p10 を利用している場合、LEを利用するにはportsに収録されているacme関連のツールを利用するのが一番簡単である。 おそらく、通常最も単純なのは、security/py-certbot を利用することだと考えられるが、今回は以下の理由により別ツールを利用する。

というわけで、代替ツールを利用するが、今回はacme-clientを利用する。 acme-clientはOpenBSDプロジェクトで利用されているacme clientアプリである。

今回の想定の構成

                     |               |
                     +-- nginx LB1 --+-- Web1a
internet -- Router --+               |
              |      +-- nginx LB2 --+-- Web1b
              |      |               |
              |          |           |
              +----------+-- Work ---+
                         |           |

Challengeの流れ

  1. WorkからNATを経由してLEに要求を送る
  2. LEはChallenge TokenをWorkに返答する
  3. WorkはChallenge Tokenを必要な場所に設置し、Challenge要求をLEに送付する
  4. LEは、要求を受理し、要求のあったドメイン(ここでは、http://example.comとする)のWeb Server(実態はLB1/LB2)にChallenge Requestを送付
  5. LB1/LB2 はChallenge要求を受け付けると、Proxyとして動作し、WorkにChallenge要求を転送する
  6. WorkはTokenをLEに返す
  7. LEは正しくTokenを受領できた場合にのみ、Workに対して証明書を送る

行うべき設定

acme-client

acme-clientは、

である。

ここでは、

とする。

acme-client -bnNv -k ${CERTkey} -f ${PRIVKEY} -C ${CHALLENGE} -c ${CERTDir} 申請するURL

を実行すると、必要な証明書等を作成し、秘密鍵、公開鍵を取得してくれる。

オプションの意味

Load Balancer設定

LB-nginx.conf
....(snip)

upstream HTTP_WWW {
    server 198.51.100.11;
    server 198.51.100.12;
}
upstream HTTP_ACME {
    server 192.0.2.101;
}

server {
    listen  203.0.113.11;
    server_name www.example.com;

    location ^~ /.well-known/acme-challenge/ {
        proxy_pass http://HTTP_ACME;
    }
    location / {
        proxy_pass http://HTTP_WWW;
    }
}
....(snip)

Work設定

Work-nginx.conf
server {
    listen          192.0.2.101:80;
    server_name     www.example.com;

    rewrite /.well-known/acme-challenge/(.*) /$1 last;

    location / {
        root    ${CHALLENGE}; # 実際のChallengeディレクトリを記載してください。
        index   index.html;
    }
}

これで、手元のブラウザから、http://www.example.com/.well-known/acme-challenge/index.html にアクセスして、index.htmlが見えればOK

おまけ

マルチドメインに対応するために、acmeを一気に処理してくれるscriptを書いたので、参考にどうぞ。 詳しいことは、自力でこのshell scriptを読んでください。

crt-update.sh
# cat crt-update.sh
#! /bin/sh

# Let's Encrypt Certificate renewal script for FreeBSD and acme-client
#	Copyright (C) by seirios@seirios.org
#
# Usage: crt-upd.sh [target domains...]

###############################################################################

: ${DEBUG:=0}
: ${FORCE:=0}

ACME_BASE=/home/seirios/htdocs/acme
OPTS="-bnN"
DOPTS=""; [ ${DEBUG} -ne 0 ] && DOPTS="-v"
FOPTS=""; [ ${FORCE} -ne 0 ] && FOPTS="-F"

ACCKEY=${ACME_BASE}/SSL/privkey.pem
SSL=${ACME_BASE}/SSL
CHALLENGE=${ACME_BASE}/WWW
DOMAINSFILE=${ACME_BASE}/domains.txt

UID=`id -u`
[ ${UID} -ne 0 ] && echo "Must run on root/UID=0" && exit

if [ ${DEBUG} -ne 0 ]; then
  ECHO="/bin/echo"
else
  ECHO=""
fi

if [ ${#} -eq 0 ]; then
  DOMAINS=`cat "${DOMAINSFILE}" | sed 's/[#|].*$//' | while read DOMAIN line ; do
    echo -n "${DOMAIN} "
  done`
else
  DOMAINS=${@}
fi

[ ${DEBUG} -ne 0 ] && /bin/echo "Target domain: ${DOMAINS}"

for i in ${DOMAINS}; do
  echo "Getting ${i} Certificates"
  DOMKEY=/home/seirios/htdocs/acme/SSL/$i/privkey.pem
  [ ! -d ${SSL}/${i} ]       && ${ECHO} mkdir ${SSL}/${i}
  [ ! -d ${CHALLENGE}/${i} ] && ${ECHO} mkdir ${CHALLENGE}/${i}

  ${ECHO} acme-client ${OPTS} ${DOPTS} ${FOPTS} -k ${DOMKEY} -f ${ACCKEY} -C ${CHALLENGE}/${i} -c ${SSL}/${i} ${i}

  case $? in
    0) echo "${i} is updated" ;;
    1) echo "${i} is troubled" ;;
    2) echo "${i} is not need to update" ;;
  esac
done

###############################################################################
#
# Settings.
#	Requirement: FreeBSD and acme-client
#
# ToDo
#	- Change DEBUG/FORCE controll from environment variable to command
#	  line options.
#
# Version History
#
# ver: 0.1	Initial revision.
#      0.2	Add DEBUG initializer and FORCE initializer.
#		Display acme-client status.
#		ex. DEBUG=1 FORCE=1 crt-upd.sh
# 

注意

domains.txt
# ACME domain configuration file.
# format: domain|server:dir server:dir ...
www.example.com|198.51.100.11:/some/where/Certs 198.51.100.12:/some/where/Certs
www.example.net|198.51.100.11:/some/where/Certs 198.51.100.12:/some/where/Certs

これで、/some/where/SSL/www.example.(com|net)cert.pem,chain.pem,fullchain.pem,privkey.pemが作成されるので、必要に応じてファイルをrenameして転送すれば良い。