2015年3月6日金曜日

Nginx で 「SSL Accelerator(SSLアクセラレータ)」を作りました! (SSLリバースプロキシ)


なのまるです~
Nginxを使って、SSLの代理処理をする「SSLアクセラレータ(SSLオフロード)」を作成したので、その構築メモです!


もくじ



背景

最初になぜこんなことをするのか?を書いておこうと思います。

いくつか理由はあるんですが、
  • SSL処理の高速化を図りたい
  • パッチのメンテナンスを単純化したい
  • 高価なアプライアンスを入れる時間的な余裕がない
  • 堅牢なサーバーをフロントに置いて、セキュリティを高めたい
と、こんなところです。

マスタリングNginx」を読んだうろ覚えですが・・・

リバースプロキシサーバーの構築はセキュリティ向上につながります。と一説がありました。
それは、
  • アプリケーションを処理するサーバー
  • トラフィックを処理するサーバー
を分けることにより、Nginxの実行ユーザーを非特権ユーザーで実行することで実現を考えているような内容でした。

他にも、リバースプロキシを立てることにメリットはあると考えています。(もちろんデメリットも)

ただ、今回はそういう話ではないので早速手順に移ります。

構成

構成はまさにこんな感じ。フロントのリバースプロキシ(Nginx)でSSLを処理する!
SSL Offloading, Encryption and Certificates with NGINX - NGINXより
ホスト構成IPアドレス
Appサーバー192.168.1.91
Nginx(SSL処理)サーバー192.168.1.90
Cacti(モニタリング)サーバー192.168.1.31

手順

  • CentOS6でNginx入れる(EPEL)!
  • Nginxでリバースプロキシ設定をする
  • (必要であれば)Apacheのモジュールコンパイル
  • Apacheのモジュール設定をする
  • テストして、本番投入
  • Cactiに追加、Nginx Statusの設定

前提条件

  • CentOS Linux
  • Nginx
  • Apache
  • Cacti(モニタリング)

CentOS6でNginx入れる(EPEL)!

CentOS 7にしようとしたんですが、終了までの時間があまり残されていなかったので、構築と別のところで詰まるのは嫌!
ということで、今回は「CentOS 6」を選びました。

OSはインストール済みとします。

EPELリポジトリの追加
rpm -ivh http://ftp.iij.ad.jp/pub/linux/fedora/epel/6/i386/epel-release-6-8.noarch.rpm

Nginxのインストール
yum update 
yum install nginx


Nginxでリバースプロキシ設定をする

設定は以下のような例で行いました。
(例)/etc/nginx/conf.d/default.conf:
ssl_certificate     /etc/pki/tls/certs/2015.crt;
ssl_certificate_key         /etc/pki/tls/private/2015.key;
ssl_protocols         TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers RC4:HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache    shared:SSL:10m;
ssl_session_timeout  10m;
 
 # header情報は適宜修正してください!

proxy_next_upstream error timeout;
proxy_set_header Host              $host;
proxy_set_header X-Real-IP         $remote_addr;
proxy_set_header X-Forwarded-For   $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port  $remote_port;
port_in_redirect                   off;
add_header      Front-End-Https    on;
 
server {
    listen    80;
    server_name  [公開しているサーバー名];
                location / {
                                proxy_pass http://192.168.1.91; #Apacheが入っているサーバー
                }
                location /server-status {
                                stub_status on;
                                access_log off;
                                allow 127.0.0.1;
                                allow 192.168.1.31; #監視サーバー
                                deny all;
                }
}
 
server {
    listen    443 ssl;
    server_name  [公開しているサーバー名];
                location / {
                                proxy_set_header host              $host;
                                proxy_set_header X-Forwarded-For   $remote_addr;
                                proxy_set_header X-Forwarded-Proto https;
                                proxy_set_header X-Forwarded-Port  443;
                                # proxy_set_header Connection "keep-alive"; # header情報は適宜修正してください! 
                                proxy_pass http://192.168.1.91; #Apacheが入っているサーバー
                }
                location /server-status {
                                stub_status on;
                                access_log off;
                                allow 127.0.0.1;
                                allow 192.168.1.31; #監視サーバー
                                deny all;
                }
}
↑ ※ 2017年2月1日修正

SSL証明書

Apacheからやっている人はハマる可能性があるので、注意をこちらに。
Nginxで使う証明書は1つです。

中間証明書はサーバー証明書と一つにまとめる必要があるということです!
  • 例:
$ cat www.example.com.crt bundle.crt > www.example.com.chained.crt
参考:



(必要であれば)Apacheのモジュールコンパイル

このままだと、アプリケーションサーバのログやアクセス元がリバースプロキシのサーバーからのリクエストになってしまいます。

192.168.1.90 - - [02/Mar/2015:21:20:41 +0900] "GET /server-status HTTP/1.0" 200 22563 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.74 Safari/537.36"
192.168.1.90 - - [02/Mar/2015:21:20:41 +0900] "GET /favicon.ico HTTP/1.0" 200 279 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.74 Safari/537.36"
192.168.1.90 - - [02/Mar/2015:21:22:32 +0900] "GET /server-status HTTP/1.0" 200 24477 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.74 Safari/537.36"

これでは困るので、Apacheのモジュールを使って解決することにしました。以下の2通りが有名でした。
  • mod_rpaf
  • mod_extract_forwarded

mod_rpaf は開発も止まっており、脆弱性が発見されていたりとあまり未来は明るくなさそうです。。。

ただ、わけあってmod_rpafを採用しました。
公式サイトにはもうアクセスできないんですが、ソースはこちらを利用しました。
Apacheモジュールの組み込みは以下のようにします。
apxs -ic mod_rpaf.c

他のサイトでは、mod_extract_forwardedがいいと書かれていますので、
特に理由がなければ「mod_extract_forwarded」の採用をお勧めします!

Apacheのモジュール設定をする

既存のconfigまたは、httpd-mod_rpaf.confなどを作って以下のように設定します。

 # Apache 1.3 ならば下記を有効にする
 # LoadModule rpaf_module libexec/mod_rpaf.so
 # Apache 2.0 ならば下記を有効にする
LoadModule rpaf_module modules/mod_rpaf-2.0.so
 # <IfModule mod_rpaf.c>
    RPAFenable On
    RPAFsethostname On
    RPAFproxy_ips 192.168.1.90
    RPAFheader X-Forwarded-For
 # </IfModule>

参考:


テストして、本番投入

ちょうどこの上に「ロードバランサ」がいたので、HTTPS(443)だけを別IPで通してテストしました。

一通りの動作確認ができたので、実際に切り替えます。


Cactiに追加、Nginx Statusの設定

こちらは別記事にしますね!


まとめ


  • さーっと流しましたが、実際は1時間くらいで作り上げてます!
  • SSLの証明書の作成にはまるかもしれないので気を付けてください!
  • Ansible などで、自動で作れるようにしたいです!w


参考サイト

Zenback