Let's Encryptにイントラサイトの証明書を発行してもらう
2018/03/28
Let's Encrypt、便利ですよね。無料でvalidな証明書が手に入るという事がこんなに便利だとは。
で、せっかく無料で発行できるんだから外部からのアクセスを遮断しているイントラなサーバーもこれを使いたい。という訳でイントラサイトでLet's Encryptの証明書を発行する方法を考えてみた。
スポンサーリンク
Let's Encryptをイントラで
Let's Encryptは入力したドメインに対してHTTPで特定のファイル(.well-known/)にアクセスする事で所有権の確認を行っています。
すなわち、イントラサイトなどの外部からのHTTPアクセスを遮断しているサーバでは証明書を取得する事が出来ません。
それとは別にDNS認証というものがありますが、これはこれで証明書発行時に提示されるトークンをDNSに登録する必要があり、Route53のような自動でレコードの更新が出来るDNSサーバーを利用していない場合は自動化が出来ません。(めんどくさいし)
なので、出来ればHTTPで認証させたい。だけど、イントラだから外部のアクセスは遮断している。どうしよう?……よく考えてみて下さい。
HTTPで特定のファイル(.well-known)にアクセスする。それはすなわち、その特定のファイルに対して応答できれば良いのです。リバースプロキシの出番ですね。
前提条件?
要は.well-knownへのアクセスをイントラ側に投げるだけなので、前提条件としてはリバースプロキシの使えるWebサーバーが外部からアクセスできる場所にあればOKです。
フロント側
という訳で、まずはフロント(リバースプロキシでイントラ側に投げる係)の設定から。
「.well-known」へのアクセスをイントラ側にぶん投げるだけですね。フロント側のサーバの種類別に書いておきましょう。
nginx
.well-known以下のアクセスのみイントラ側に転送して、他は応答しないようにしています。
server {
listen 80;
listen [::]:80;
# イントラ側で利用されているドメインを記載
server_name intra1.example.com intra2.example.com;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# 444=応答しない
error_page 403 404 = @deny;
location @deny {
return 444;
}
location / {
return 444;
}
# well-knownのみイントラに渡す
location ^~ /.well-known/ {
# $host(server_nameに指定したドメイン)のIPが引けるDNSを指定
# もしDNSがない場合はhostsに記載するなどして名前解決が出来るようにする
resolver 192.168.1.1;
proxy_pass http://$host;
# 「dummy.」と付ける事でLet's Encrypt専用として区別する
proxy_set_header HOST "dummy.${host}";
proxy_http_version 1.1;
proxy_intercept_errors on;
# どうしても名前解決できない場合
#if ( $host = "intra1.example.com" ) {
# set $intra_ip 192.168.1.1
#}
#proxy_pass http://$intra_ip;
}
}
Apache
ProxyPassに変数を使う方法が良く分からなかった……バーチャルホストを複数用意する形で対応します。(もっと良い方法があるかも?)
やってる事自体はnginxと同じですね、やり方が違うだけで
<VirtualHost *:80>
ServerName intra1.example.com
<Location /.well-known/>
ProxyPass http://intra1.example.com/.well-known/
ProxyPassReverse http://intra1.example.com/.well-known/
RequestHeader set Host "dummy.intra1.example.com" early
</Location>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
<VirtualHost *:80>
ServerName intra2.example.com
<Location /.well-known/>
ProxyPass http://intra2.example.com/.well-known/
ProxyPassReverse http://intra2.example.com/.well-known/
RequestHeader set Host "dummy.intra2.example.com" early
</Location>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
イントラ側
イントラ側は流れて来た.well-knownなアクセスを捌いてやれば良いのですが、こちらも念のため.well-known以外は閲覧できない様に細工してやりましょう。
また、Let's Encrypt用のrootには/var/www/lets_encryptを使用すると仮定します。
nginx
普通に.well-knownを返すだけです
server {
listen 80;
listen [::]:80;
root /var/www/lets_encrypt;
server_name dummy.intra1.example.com dummy.intra2.example.com;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location / {
return 404;
}
location ^~ /.well-known/ {
try_files $uri $uri/ =404;
}
}
Apache
特に考える必要はないですね。
<VirtualHost *:80>
ServerName dummy.intra1.example.com
ServerAlias dummy.intra2.example.com
DocumentRoot /var/www/lets_encrypt
<Directory /var/www/lets_encrypt>
# とりあえず、全て拒否
Require all denied
</Directory>
<Directory /var/www/lets_encrypt/.well-known/>
# .well-known以下のみ許可
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
証明書の発行
証明書の発行はイントラ側でいつも通り、letsencrypt-auto(今はcertbot-auto)で/var/www/lets_encryptを使用して証明書をお願いするだけです。
そうすれば、Let's Encrypt側がアクセスしてきた際にイントラ側のファイルを確認して満足して証明書を発行してくれます。
図解
図解するとこんな感じ。
外側からのアクセスの時にリバースプロキシ側でHTTPのHOSTヘッダを書き換えて外側向けのVirtualHostで応答する。
もっと噛み砕いで表現すると、内側からアクセスされた時と外側からアクセスされた時に違うVirtualHostを利用する。というわけ。
異なるVirtualHostは不可侵でしょうから、そうすればイントラ向けコンテンツが外から見えてしまう事はないはず。という理屈。
そんな事しなくてもリバースプロキシ側で/.well-known/だけ通すようにすればそれ以外のアクセスは飛んでこないはずだが、もしもの事があってはいけないので念には念をという事で二重に防御。