2016年8月22日月曜日

QNAP の仮想ホストで SNI

昨日はどうにか仮想ホストでちゃんと中間証明書を有効にする方法を確立した。さらに一日経って、頭が整理できて、全体的により洗練されたやり方に行き着くことができた。昨日の成功をベースにして SNI(仮想ホストのそれぞれで別々の証明書を使い分けること)も余裕でできることがわかったので、それも含めて、全体を整理し、洗練したやり方になったので、それをまとめておこうと思う。

Web サーバーのポートの設定

Web UI 用のポートは「システム設定 > 一般設定 > システム管理」で 8080(HTTP)と 8081(HTTPS)に設定した。さらに、「HTTPS のみを使用する」設定にした。

Web サーバーのポートは「Webサーバ > Webサーバ」で 80(HTTP)と 443(HTTPS)に設定した。さらに仮想ホストの設定で、 443(HTTPS)の設定の仮想ホストだけをエントリーした。

StartSSL での証明書の取得

StartSSL で、仮想ホストそれぞれの証明書と、XXX.myqnapcloud.com 用の証明書もついでに取得する。これらの中間証明書はすべて共通(StartSSL)の中間サーバーのものとなるので、中間証明書は別々に用意しなくてもいいのが洗練されたやり方となるポイント。

それぞれの Web サイト認証は、「Website Control Validation」のリンクをクリックして、認証用の HTML をダウンロードし、ウェブサイトのルートに設置してから、認証ボタンを押して、認証を成功させることができる(認証後は、認証用 HTML を削除して構わない)。ちなみに、XXX.myqnapcloud.com のルートは、/share/Web そのものである。

XXX.myqnapcloud.com 用の証明書の設定

XXX.myqnapcloud.com(Web UI)用の証明書は、Web UI から「システム設定 > セキュリティ > 証明書とプライベートキー」で行える。StartSSL からダウンロードした zip の中の さらに ApacheSever.zip の中の 2_XXX.crt を証明書としてアップロード、秘密キーには、StartSSL のツールボックスの「Decrypt Private key」で暗号化を解除した秘密キーをペーストしてアップロード、1_root_bundle.crt を中間証明書として(「発行者の中間証明書をアップロード」をチェックして)アップロードする。

この作業だけで、まず、XXX.myqnapcloud.com は https が正常に有効となる。

QNAP の Web UI は、この設定作業を通じて、中間証明書を /etc/stunnel/uca.pem に保存する。前回リバースエンジニアリングによってこのことを突き止められたので、仮想ホストの設定の際には、この中間証明書を利用する形にすればエレガントになる。

各仮想ホスト用の証明書の設定

前回のリバースエンジニアリングによって、HTTPS を有効にした仮想ホストの設定は、/etc/config/apache/extra/httpd-ssl-vhosts-user.conf にあることが判明した。この中の次の箇所を次のように修正する:

- SSLCertificateFile "/etc/stunnel/stunnel.pem"
+ SSLCertificateFile "/share/Web/mydomain.com/cert/server.pem"
+ SSLCertificateChainFile "/etc/stunnel/uca.pem"

仮想ホストの証明書のパスは好みで ok だが、SSLCertificateFile で仮想ホストの証明書を、SSLCertificateChainFile で中間証明書(のチェーン)を指すようにするのがポイント。で、中間証明書は XXX.myqnapcloud.com の設定で作成されるものと共通なので、画一的に /etc/stunnel/uca.pem を指定しておけばよい。

ちなみに、仮想ホストの証明書は、暗号化を解除した秘密キーを先頭に、続いて仮想ホストの証明書を記載する形式になっている。

仮想ホストが複数ある場合は、上記箇所が、仮想ホスト分エントリーが存在する。それぞれについて、それぞれの証明書を指すようにパスを指定することになる。これで SNI が実現する。

※ httpd-ssl-vhosts-user.conf は、Web UI で仮想ホストの設定を変更したりしたタイミングでリセットされてしまうので(ちなみに NAS の再起動ではリセットされないようだ)、その点には留意しておく必要がある。

HTTPd の再起動

/etc/init.d/Qthttpd.sh restart

リダイレクト用の index.php のカスタマイズ

XXX.myqnapcloud.com のルート(/share/Web)には、リダイレクト用の index.php が存在する。これは設定状態に応じて、HTTP を HTTPS にリダイレクトしたりする機能を提供するのだが、myqnapcloud 用には上手く機能するものの、仮想ホスト用には上手く機能しない。例えば、ここでは、仮想ホストは一律 HTTPS(443)のみを使うようにしてあるわけだが、HTTP(80)でアクセスした場合、443 ではなく、myqnapcloud 用の 8081 の方へリダイレクトされてしまい、Web UI のログイン画面が表示されてしまう。独自ドメインで運用する仮想ホストで、これはまずい。そのため、index.php をカスタマイズして、myqnapcloud 以外のドメインの場合は、443 の方にリダイレクトするように修正した index.php へと差し替えた。


<?php
function isIPv6($ip) {
    if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
        return true;
    }
    return false;
}
function isMyCloudNAS() {
    if (isset($_SERVER['HTTP_HOST']) && strlen($_SERVER['HTTP_HOST']) > 0)
        $_http_host = $_SERVER['HTTP_HOST'];
    else
        return false;
    $mycloudnas_domains = Array(
        'mycloudnas.com',
        'myqnapnas.com',
        'qcloudnas.com',
        'myqnapcloud.com'
    );
    foreach ($mycloudnas_domains as $d) {
        if (strncasecmp(stristr($_http_host,$d),$d,strlen($d))==0) {
            return true;
        }
    }
    return false;
}
$webAccessIP = $_SERVER['SERVER_NAME'];
if (isMyCloudNAS() == true) {
    $extPort = exec('/sbin/getcfg System ExtPort -d 0');
    if (intval($extPort)>0)
        $webAccessPort = $extPort;
    else
        $webAccessPort = exec('/sbin/getcfg System "Web Access Port" -d 8080');
    if ($_SERVER['HTTPS'] && exec('/sbin/getcfg Stunnel Enable -d 1') == '1') {
        $protocol='https';
        $webAccessPort = exec('/sbin/getcfg Stunnel Port -d 443');
    }
    else
        $protocol='http';
} else {
    # mycloudnas 以外の仮想ホストは一律、https (443) のみとする。
    $protocol='https';
    $webAccessPort = 443;
}
if (isIPv6($webAccessIP))
    $webAccessUrl = $protocol.'://['.$webAccessIP .']:'.$webAccessPort.'/';
else
    $webAccessUrl = $protocol.'://'.$webAccessIP .':'.$webAccessPort.'/';
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
<meta http-equiv="expires" content="0">
<script type='text/javascript'>
    location.href = '<?=$webAccessUrl?>';
</script>
    </head>
</html>


SNI 達成!

以上で、QNAP だけで、SNI が実現できてしまう。もともと Web サーバーは、さくらインターネットのスタンダードプランを年間契約しているのだけど、来年はメールだけのプラン(さくらのメールボックス)に変更するというのも、一つの手かもしれない。

0 件のコメント:

コメントを投稿