純規の暇人趣味ブログ

手を突っ込んで足を洗う

Let's Encryptにイントラサイトの証明書を発行してもらう

      2016/07/24    HimaJyun

最近、「Let's EncryptにECDSAの鍵を発行」してもらったり「GitoliteをSmart HTTPで利用出来る様にしたり」しました。

ここまでやったのならば、Smart HTTPでのGitアクセスもLet's製証明書で暗号化してしまいたいです。(オレオレ証明書は警告が出るのが面倒)

ただ、僕のプライベートGitサーバは外からアクセス出来ない様にしているので、そのままではLet's証明書が取得出来ません。

そこで、イントラサイトでもLet's証明書を取得する方法を考えてみました。

Let's Encryptをイントラで

Let's EncryptはDNSに登録されているAレコードを利用して、そこにHTTPで特定のファイル(.well-known/)にアクセスする事でドメインの所有者の確認を行っています。

すなわち、イントラなどの外部からHTTPでアクセスできないサーバでは証明書を取得する事が出来ません。

でもでも、よくよく考えてみて下さい。

HTTPで特定のファイル(.well-known)にアクセスする、それはすなわち、その特定のファイルに対して応答できれば良いのです、リバースプロキシの出番ですね。

前提条件?

要は「.well-known」へのアクセスをリバースプロキシでイントラに投げましょう、と、言う事です、後はそれを如何に安全に出来るか。

もちろん、別のポートを開けて、そこにLet's Encryptの組み込みWebサーバでアクセスさせる方法もありますが、むやみやたらにポートを開けたくないのと、イントラサーバに外からアクセス出来る様にしたくなかったので

とりあえず、以下の状態だと仮定しましょう。

  • フロント側にリバースプロキシの使えるWebサーバが存在

フロント側

「.well-known」へのアクセスをイントラ側にぶん投げるだけですね。

フロント側のサーバの種類別に書いておきましょう。(ちなみに僕はフロントnginx、イントラApacheの構成です。)

nginx

nginxの場合は444と言うエラーコードが使えます。(厳密には、使えると言うより割り当ててある)

この444を応答すると、実際にはコンテンツを応答せずに接続をぶった切ります、応答する回線帯域が惜しいので(苦笑

それを利用して、.well-known以外の接続はぶった切りつつ、.well-knownは応答してやりましょう。

server {
    listen 80;
    listen [::]:80;
    # イントラ側で利用されているドメインを記載
    server_name hoge.jyn.jp fuga.jyn.jp;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    # error_page 404 =444;的なのは出来ないみたいなので、名前付きlocationで対応
    error_page 403 404 = @deny;
    # 444を応答
    location @deny {
        return 444;
    }
    location / {
        return 444;
    }
    # .well-knownのアクセスのみイントラへGo
    location ^~ /.well-known/ {
        # 証明書を取得したいイントラサーバが複数ある場合は変数とifを利用して分岐すると良いです。
        set $intra_letsencrypt_ip $host;
        set $intra_letsencrypt_host $host;
        # hoge.jyn.jpでのアクセスだった場合
        if ( $host = "hoge.jyn.jp" ) {
            # ダミーのドメインをHostヘッダとして通知する。
            set $intra_letsencrypt_host hoge.dummy.jyn.jp;
            # IPでアクセスすればダミーのドメインが/etc/hostsに記載されていなくてもOK!!
            set $intra_letsencrypt_ip 192.168.1.1;
        }
        # 上と同じ様に設定
        if ( $host = "fuga.jyn.jp" ) {
            set $intra_letsencrypt_host fuga.dummy.jyn.jp;
            set $intra_letsencrypt_ip 192.168.1.1;
        }
        # 後はリバースプロキシにポイ
        proxy_pass http://$intra_letsencrypt_ip;
        proxy_set_header HOST $intra_letsencrypt_host;
        proxy_http_version 1.1;
        proxy_intercept_errors on;
    }
}

Apache

Apacheの場合は残念ながら444が使えませんので妥協しましょう。

また、Proxypassに変数を使えないので、Apacheはバーチャルホストを複数用意する形で対応します。

やってる事はnginxと同じですね、やり方が違うだけで

<VirtualHost *:80>
    ServerName hoge.jyn.jp

    <Location /.well-known/>
        ProxyPreserveHost On
        ProxyPass http://192.168.1.1/.well-known/
        ProxyPassReverse http://192.168.1.1/.well-known/
        RequestHeader set Host "hoge.dummy.jyn.jp" early
    </Location>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
<VirtualHost *:80>
    ServerName fuga.jyn.jp

    <Location /.well-known/>
        ProxyPreserveHost On
        ProxyPass http://192.168.1.1/.well-known/
        ProxyPassReverse http://192.168.1.1/.well-known/
        RequestHeader set Host "fuga.dummy.jyn.jp" 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

ここで444を返すとフロント側が変な事になりそうなので、イントラ側では404でお返しします(404ならフロント側が444にしてくれるはず)

server {
    listen 80;
    listen [::]:80;
    root /var/www/lets_encrypt;
    server_name hoge.dummy.jyn.jp fuga.dummy.jyn.jp;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    error_page 403 =404;
    location / {
        return 444;
    }

    location ^~ /.well-known/ {
        try_files $uri $uri/ =404;
    }
}

Apache

特に考える必要はないですね。

<VirtualHost *:80>
    ServerName hoge.dummy.jyn.jp
    ServerAlias fuga.dummy.jyn.jp
    # 2つ以上ある場合は以下の通り
    #ServerAlias fuga.dummy.jyn.jp piyo.dummy.jyn.jp

    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側がアクセスしてきた際にイントラ側のファイルを確認して満足して鍵を発行してくれます。

ちなみにですが、「ECDSAの鍵」でもGit(少なくともGit for Windowsでは)使えましたよ、他は知りませんけど……

 - サーバ運営