Why not login to Qiita and try out its useful features?

We'll deliver articles that match you.

You can read useful information later.

15
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Let's EncryptとMyDNSでワイルドカードでマルチドメイン(含サブドメイン)な証明書を発行して自動更新する。apacheも。

Last updated at Posted at 2020-02-07

はじめに

Let's Encryptを初めて使ってみました。
CNをホスト名にしてSANだけだと、とある場所で「CNが一致しません!」となってしまったので、その対策が必要でした。
1枚で使いまわし証明書が発行できたので、記録します。

このエントリを読むとできること

  • CentOS7のapacheで使うための
  • Let's Encryptの証明書を
  • MyDNSを使って
  • ワイルドカード かつ マルチドメインで発行して
  • 自動的に更新する

ことが、できる。はず。

Screenshot from Gyazo

環境など

対象機器および環境

  • CentOS7(7.7.1908)
  • certbot(1.0.0)
  • php(5.4.16)

こんな証明書が欲しいことがあったとしたら

私は example.com というドメインで検証環境を構築しています。
その配下に sub.example.com というサブドメインがあります。
その環境のサーバたちで、1枚の証明書を使いまわしたいと思いました。
なので、こんな証明書が欲しい、ということになります。

項目
CN *.example.com
SAN *.example.com
SAN *.sub.example.com
SAN example.com

MyDNSのアカウント情報

ドメイン
ドメイン名 example.com
マスターID mydns123456
パスワード mydnspassword
サブドメイン
サブドメイン名 sub.example.com
マスターID mydns654321
パスワード subdompassword

実装

フック用のスクリプト

MyDNSの https://github.com/disco-v8/DirectEdit があるのですが、サブドメインも含めては管理できないので、
これを参考にして、以下のものを作成しました。これを使って説明します。
Let's Encrypt MyDNS Hook Script

準備

yum -y install epel-release
yum -y update
yum -y install php php-mbstring certbot python2-certbot-apache mod_ssl
cd /root/
git clone https://github.com/bashaway/le_mydns_hook

./le_mydns_hook/accounts.conf にMyDNSのアカウント情報を記載します。

vi ./le_mydns_hook/accounts.conf
----------8<-----(snip)-----8<----------
$MYDNS_ID['ドメイン名']  = 'マスターID';
$MYDNS_PWD['ドメイン名'] = 'パスワード';
----------8<-----(snip)-----8<----------

例えば、上記の例の場合 accounts.confは以下のように修正します。

----------8<-----(snip)-----8<----------
$MYDNS_ID['example.com']  = 'mydns123456';
$MYDNS_PWD['example.com'] = 'mydnspassword';
$MYDNS_ID['sub.example.com']  = 'mydns654321';
$MYDNS_PWD['sub.example.com'] = 'subdompassword';
----------8<-----(snip)-----8<----------

証明書発行

一度作業すると、certbot renwe で引数が引き継がれるぽいので、自前フックはフルパスで指定しておく。

certbot certonly --manual \
 --server https://acme-v02.api.letsencrypt.org/directory \
 --preferred-challenges dns-01 \
 --agree-tos --no-eff-email \
 --manual-public-ip-logging-ok \
 --manual-auth-hook /root/le_mydns_hook/regist.php \
 --manual-cleanup-hook /root/le_mydns_hook/delete.php \
 -m youraddress@example.com \
 -d *.example.com \
 -d *.sub.example.com \
 -d example.com 

確認してみます(下はステージングで発行したので、IssuerがFakeになっています)

# openssl x509 -in /etc/letsencrypt/archive/example.com/cert1.pem -text | egrep "CN|DNS"
        Issuer: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3
        Subject: CN=*.example.com
                DNS:*.sub.example.com, DNS:*.example.com, DNS:example.com

apacheの設定

firewallの設定

firewall-cmd --add-service https --zone=public --permanent
firewall-cmd --reload

既存で設定されている箇所は、Let's Encryptで発行された証明書におきかえます。

/etc/httpd/conf.d/ssl.conf
#SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem

#SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

#SSLCertificateChainFile /etc/pki/tls/certs/server-chain.crt
SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem

httpdに設定を読み込ませます。

systemctrl reload httpd

自動更新のチェック

チェックのために、--force-renewalをつけてみます。

certbot certonly --manual \
 --server https://acme-v02.api.letsencrypt.org/directory \
 --preferred-challenges dns-01 \
 --agree-tos --no-eff-email \
 --manual-public-ip-logging-ok \
 --manual-auth-hook /root//le_mydns_hook/regist.php \
 --manual-cleanup-hook /root/le_mydns_hook/delete.php \
 -m youraddress@example.com \
 -d *.example.com \
 -d *.sub.example.com \
 -d example.com \
 --webroot-path /var/www/html/ \
 --post-hook "systemctl reload httpd" \
 --force-renewal

おそらく、以下のように更新後のものが発行されていると思います。

$ ls -1 /etc/letsencrypt/archive/example.com/cert*
/etc/letsencrypt/archive/example.com/cert1.pem <--- 最初に発行したもの
/etc/letsencrypt/archive/example.com/cert2.pem <--- force-renewalしたもの

cronによる自動更新

確認のため、短めで --force-renewal する。

/etc/cron.d/letsencrypt
0/10 * * * * root /bin/certbot renew --webroot-path /var/www/html/ --post-hook "systemctl reload httpd" --force-renewal
$ ls -1 /etc/letsencrypt/archive/example.com/cert*
/etc/letsencrypt/archive/example.com/cert1.pem <--- 最初に発行したもの
/etc/letsencrypt/archive/example.com/cert2.pem <--- 手動でforce-renewalしたもの
/etc/letsencrypt/archive/example.com/cert3.pem <--- cronでforce-renewalしたもの

問題なく更新されていたら、週に一度確認するように。
修正しないとレートリミットに引っかかっちゃうので。

/etc/cron.d/letsencrypt
0 1 * * 1 root /bin/certbot renew --webroot-path /var/www/html/ --post-hook "systemctl reload httpd"

さいごに

スクリプト

githubに置いているスクリプトです。
registでTXTレコードの追加、delete.phpでTXTレコードの削除をしています。
ふたつのスクリプトの違いは、 $CERTBOT_ENV['EDIT_CMD'] = 'REGIST';$CERTBOT_ENV['EDIT_CMD'] = 'DELETE';だけです。

regist.php
#!/usr/bin/php
<?php

// set environment
include(__DIR__.'/accounts.conf');
date_default_timezone_set(@date_default_timezone_get());
mb_internal_encoding('UTF-8');
mb_http_output('UTF-8');

// set certbot env
$CERTBOT_ENV['CERTBOT_DOMAIN']     = getenv('CERTBOT_DOMAIN');
$CERTBOT_ENV['CERTBOT_VALIDATION'] = getenv('CERTBOT_VALIDATION');

// txt record
$CERTBOT_ENV['EDIT_CMD'] = 'REGIST';

// mydns account
$MYDNS_ACCOUNT=$MYDNS_ID[$CERTBOT_ENV['CERTBOT_DOMAIN']].':'.$MYDNS_PWD[$CERTBOT_ENV['CERTBOT_DOMAIN']];

$MYDNS_HEADERS = array('Content-Type: application/x-www-form-urlencoded',
                       'Authorization: Basic '. base64_encode($MYDNS_ACCOUNT),);


// コンテクストリソースを設定
$POST_OPTIONS = array( 'http' => array('method' => 'POST',
                                       'header' => implode("\r\n", $MYDNS_HEADERS),
                                       'content' => http_build_query($CERTBOT_ENV)));

// get contents
$MYDNS_CONTENTS = file_get_contents($MYDNS_URL, false, stream_context_create($POST_OPTIONS));

sleep(2);

?>
delete.php
#!/usr/bin/php
<?php

// set environment
include(__DIR__.'/accounts.conf');
date_default_timezone_set(@date_default_timezone_get());
mb_internal_encoding('UTF-8');
mb_http_output('UTF-8');

// set certbot env
$CERTBOT_ENV['CERTBOT_DOMAIN']     = getenv('CERTBOT_DOMAIN');
$CERTBOT_ENV['CERTBOT_VALIDATION'] = getenv('CERTBOT_VALIDATION');

// txt record
$CERTBOT_ENV['EDIT_CMD'] = 'DELETE';

// mydns account
$MYDNS_ACCOUNT=$MYDNS_ID[$CERTBOT_ENV['CERTBOT_DOMAIN']].':'.$MYDNS_PWD[$CERTBOT_ENV['CERTBOT_DOMAIN']];

$MYDNS_HEADERS = array('Content-Type: application/x-www-form-urlencoded',
                       'Authorization: Basic '. base64_encode($MYDNS_ACCOUNT),);


// コンテクストリソースを設定
$POST_OPTIONS = array( 'http' => array('method' => 'POST',
                                       'header' => implode("\r\n", $MYDNS_HEADERS),
                                       'content' => http_build_query($CERTBOT_ENV)));

// get contents
$MYDNS_CONTENTS = file_get_contents($MYDNS_URL, false, stream_context_create($POST_OPTIONS));

?>

出典

https://letsencrypt.org/ja/
https://github.com/disco-v8/DirectEdit

15
15
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?