SSL-Offloader

前置き

この良い小さなソフトウェアについて Igor Sysoev に感謝します。これは、私にとってこの偉大なプロジェクトに何か貢献できる唯一の方法です。私達が単純に"SSL-Proxy"と呼ぶものを確立する全体像を説明しようとしました(別名、SSL/TLS/HTTPS-Offloader/-Accelerator/-Terminator/-Dispatcher/Reverse-Proxy/Loadbalancer など)。

私達の会社では、NGINXをリバースプロキシとして使い、複数のバックエンドからHTTPを使ってコンテントを受け取る時にクライアントにHTTPSを提供しています。VRRPを使って1つのクラスタに"接続"している2つのバーチャルマシーンがあり、それぞれが1つ以上のアプリケーションを持つ約80のTomcatサーバ(およびいくつかのIIS, Apache/PHP...)のためのフロントエンドとして振舞います。There were many reasons for this structure: One CI (ITIL: change item) for SSL certificates, customer demands (one URL xyz with multiple services), simple but effective failover mechanism for changes and so on.

Update 2014, success story:
"super-url"を使ったリバースプロキシとしてのこのNGINXセットアップは7年以上の間完璧に動作していました(この間に、ubuntuのバージョンを何回か変更しました - hardyからprecise)。今では、2つのアプリケーションだけが残され、このスキーマには含められることができません。コメントを除いた設定は約7000行です。このセットアップで週辺り約10回の変更をし、それら全てはその場で(リロードを使って)行うことができます。The security audits of these applications never showed topics like “unpatched ssl versions” again, the web-services team can concentrate on deploying applications with no need to touch an apache configuration ever again and we (the networking team) can solve every access issue by ourself.

始めに、ちょっとした警告です。

  1. ここで説明しようとしていることは全て最終的に"リバースプロキシ"設定のようなものです。バックエンドのアプリケーションが相対パスを使用するだけであれば、全てがうまくいくでしょう。絶対パスを使う場合、それは複雑に成りえます。正しいパスのプリフィックスを使ってデプロイすることができれば、この場合もやはり全てがうまくいきます。アプリケーションがルートパスにある場合、それが不可能になります - たとえ、戻ってくるコード(html, css, jsなど)をパースできたとしても。
  2. "追加設定無しで"動作する電気器具のような、"簡単な"解決法を欲しがるかも知れません。My experience with this matter is: The operation of a central entry point for many applications with all their unique characteristics is unfortunatly this complex. Don’t expect from an appliance more than save buttons and a fancy web-gui for similar settings as described here. At this point you are entering the twilight zone between configuration and programming, because your proxy is mostly an integral part of a bigger “web-program”.

Technical Data

サーバはとても効率的に動作します:

  1. Hypervisor: VMware ESXi
  2. VM: 4x CPU (load: ~0.15), 768 MByte RAM (used: ~12%), 4 GByte HD
  3. Base system: Ubuntu 12.04.5/precise
  4. Working horse: NGINX 1.6.2-5+precise0 (ppa:nginx/stable)
  5. SSL-Library: LibSSL 1.0.1-4ubuntu5.21
  6. Entropy-Daemon: haveged 1.1-2
  7. VRRP daemon: Keepalived 1:1.2.2-3ubuntu1.1
  8. Software watchdog: Monit 1:5.3.2-1
  9. NTP alternative: Chrony 1.24-3.1ubuntu1 (one socket)

考慮すべきこと

設定

ポリシーのようなもの...

  1. 更新する時に問題を避けるために、設定ファイルとディレクトリの配置はインストレーションの既存のものへの追加にしなければなりません。
  2. 各設定はユニークでなければなりません。これは一方で小さなincludeファイルの幾つかの追加の参照と複雑な配置につながります。
  3. if-ステートメントは入れ子にすべきではありません。したがってincludeの中にinclude-ステートメントを使うことは避けます。
  4. 全てのディレクトリをincludeする場合、switchのようなファイル拡張子を使います。

/etc/nginxの中に新しく生成されたファイル:

  • conf.d
  • mapping/<segment>/<application>
  • sslcerts/<domain of the channel>/[<app-group>|wildcard].[crt|key]
  • sites-enabled/<ip-slot>_<app-group>.<customer>.any.<segment>
  • scripts

名前の慣習

1つの内部(ADS)ドメイン、幾つかの公のそれ、そして複雑な状況がありました:

  • エントリー ポイント/チャンネル: LAN ユーザ (lan), インターネット (ext), 幾つかの種類の VPN/WAN (vn2) およびDNSを持たない古いVPN (vn1)
  • カスタマー: 会社、幾つかのワークグループ、ラベル、支社、そして外部のカスタマー
  • ライフ サイクル/セグメント: 開発 (dev), 統合1 と 2 (ig1/ig2), 試供品 (apv), 実演 (dem) そして製品 (prd)
  • environments: real (local) and labor

私達の解決方法は、2つの大きなDNSの木から始め、アプリケーション名をDNSからパスへ移動するものでした:

http(s)://<app-group>.<customer>.<channel>.<segment>.[local|labor]/<application>/

製品システムの場合、公的なドメインにマップされていました。

Tip

  1. プロキシ上のDNS dispatcherとして、pdnsd を使います。
  2. avahi... のようなサービスを使いたい場合は、私達がしたように *.local ドメインを使う必要がないかも知れません。

アプリケーションサーバ

最も重要なこと...

  1. 異なるカスタマーに異なるテーマを使う1つの"知的な"アプリケーションがあれば、仕事を減らすことができます。
  2. Javaアプリケーションについて、(apache+ajpがするように)TomcatのURLについてTomcatをだますために、Remote IP Valve を使います。

準備

ネットワーク設定

ネットワーク設定を/etc/sysctl.d/10-network-security.confファイルに置きました。設定の幾つかは既に下の設定の中にありました。

### http://www.cyberciti.biz/tips/linux-unix-bsd-nginx-webserver-security.html

# Avoid a smurf attack
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Turn on protection for bad icmp error messages
net.ipv4.icmp_ignore_bogus_error_responses = 1

# Turn on syncookies for SYN flood attack protection
net.ipv4.tcp_syncookies = 1

# Turn on and log spoofed, source routed, and redirect packets
#net.ipv4.conf.all.log_martians = 1
#net.ipv4.conf.default.log_martians = 1

# No source routed packets here
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0

# Turn on reverse path filtering
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Make sure no one can alter the routing tables
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0

# Don't act as a router
net.ipv4.ip_forward = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# Turn on execshild
kernel.exec-shield = 1
kernel.randomize_va_space = 1

# Tuen IPv6
net.ipv6.conf.default.router_solicitations = 0
net.ipv6.conf.default.accept_ra_rtr_pref = 0
net.ipv6.conf.default.accept_ra_pinfo = 0
net.ipv6.conf.default.accept_ra_defrtr = 0
net.ipv6.conf.default.autoconf = 0
net.ipv6.conf.default.dad_transmits = 0
net.ipv6.conf.default.max_addresses = 1

# Optimization for port usefor LBs
# Increase system file descriptor limit
fs.file-max = 65535

# Allow for more PIDs (to reduce rollover problems)
# !!! may break some programs 32768
#kernel.pid_max = 65536

# Increase system IP port limits
net.ipv4.ip_local_port_range = 2000 65000

# Increase TCP max buffer size setable using setsockopt()
net.ipv4.tcp_rmem = 4096 87380 8388608
net.ipv4.tcp_wmem = 4096 87380 8388608

# Increase Linux auto tuning TCP buffer limits
# min, default, and max number of bytes to use
# set max to at least 4MB, or higher if you use very high BDP paths
# Tcp Windows etc
net.core.rmem_max = 8388608
net.core.wmem_max = 8388608
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_window_scaling = 1

仮想アドレス

これは1つのシステムのVRRP設定 /etc/keepalived/keepalived.conf です。2つおインスタンスの設定があります。障害時には、両方のVRRPのアドレスが、残っているシステムに所属します。2つ目のシステムのために、"state"と"priority"の値を変更します。

注意

インタフェースをプロミスカスモードに設定した後で、keepalived を再起動する必要があります例えば、デバッグのためにtcpdumpを使う場合)。

vrrp_instance ONE {
        state MASTER
        priority 120
        interface eth0
        virtual_router_id <id-1>
        advert_int 1
        authentication {
                auth_type pass
                auth_pass <pass-1>
        }
        virtual_ipaddress_excluded {
                <vrrp-ipv4-1>
                <vrrp-ipv6-1>
        }
}

vrrp_instance TWO {
        state BACKUP
        priority 80
        interface eth0
        virtual_router_id <id-2>
        advert_int 1
        authentication {
                auth_type pass
                auth_pass <pass-2>
        }
        virtual_ipaddress_excluded {
                <vrrp-ipv4-2>
                <vrrp-ipv6-2>
        }
}

HTTPS アドレス

1つのありえる解決方法は、NAT(network address translation)ではなく、直接ルーティングを使うことです。この場合は、NGINX設定のサーバに一致するローカルIPアドレスが必要です。/etc/network/interfacesファイルの中に、このようにダミー(あるいはループバック)のための post-up コマンドを追加することができます。モジュールdummy/etc/modulesに追加するのを忘れないでください。

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
    ...
    post-up /etc/nginx/conf.d/ip-mtu.sh
iface eth0 inet6 static
    ...

auto dummy0
iface dummy0 inet manual
        up      /sbin/ip link set dummy0 up
        post-up /etc/nginx/conf.d/ip-addr.sh
        down    /sbin/ip link set dummy0 down

参照したスクリプトは幾つかのarp発行を訂正し、ルートされたネットワークのping-pongパケットを避けるためにブラックホールルーティングを行い、もちろんネットワークアドレスを追加します。

#!/bin/bash

echo 0 > /proc/sys/net/ipv4/ip_no_pmtu_disc
echo 1 > /proc/sys/net/ipv4/tcp_mtu_probing
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/dummy0/arp_announce
echo 1 > /proc/sys/net/ipv4/conf/dummy0/arp_ignore

ip route add blackhole <network-1>
ip route add blackhole <network-2>
...
ip route add blackhole <network-n>

ip addr add <address-1>/32  dev dummy0 label <label-1>
ip addr add <address-2>/32  dev dummy0 label <label-2>
...
ip addr add <address-x>/32  dev dummy0 label <label-x>

Maybe you need a second file for all settings with requires a working network interface (e.g. if you have to fix some MTU/MSS values, you have to route to real ips on a real interfaces).

#!/bin/bash

# VPN Networks with broken PMTU
# (ADVMSS = MTU - 40)
ip route add <host-/network-1> via <default gateway> mtu <mtu> advmss <mtu-40>
ip route add <host-/network-2> via <default gateway> mtu <mtu> advmss <mtu-40>
...
ip route add <host-/network-m> via <default gateway> mtu <mtu> advmss <mtu-40>

核となる設定

nginx.conf

デフォルトの設定ファイル /etc/nginx/nginx.confにあまり変更をしないことに決めました。VMは4つのコアを持ち、各コアは1つの固定のworkerを取り、NGINXは他のプロセスよりも優先度を高くしたいです。他の全ての設定はincludeされていました(includeされるファイルmime.typesはプロジェクトHTML5-Boilerplateから選ばれています)。

worker_processes 4;
worker_priority -1;
worker_rlimit_nofile 8192;
worker_cpu_affinity 0001 0010 0100 1000;

user      www-data;
pid       /var/run/nginx.pid;
error_log /var/log/nginx/error.log;

events {
    multi_accept on;
    worker_connections 4096;
}

http {
    map_hash_bucket_size 128;
    include /etc/nginx/mime.types;
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

sslproxy.conf

ファイル/etc/nginx/conf.d/sslproxy.conf は全ての重要なグローバル設定を持ちます。特に:

error_page 404 =410 /40x.html;
404エラーページはieの内部ページを避けるために410として隠されるでしょう。
proxy_intercept_errors on;
アプリケーションサーバからの全てのエラーは対応するローカルエラーページの後ろに隠されるでしょう。
proxy_redirect http:// $scheme://;
アプリケーションサーバからの各HTTPリダイレクトはHTTPSにrewriteされるでしょう。
proxy_set_header Accept-Encoding “”;
バックエンドへのプロキシインタフェースはデータを圧縮してはいけません(lan接続)。
### global ###
server_tokens           off;
server_name_in_redirect off;
ignore_invalid_headers  on;
if_modified_since       before;
root                    /etc/nginx/content/;
ssi                     on;
ssi_silent_errors       on; # testing=off
add_header X-Frame-Options SAMEORIGIN;
add_header Strict-Transport-Security max-age=16000000;

### tcp ###
tcp_nodelay             off;
tcp_nopush              on;
sendfile                on;
keepalive_requests      100;

### timeouts ###
resolver_timeout        6;
client_header_timeout   30;
client_body_timeout     60;
send_timeout            60;
keepalive_timeout       65 20;

### buffers ###
client_header_buffer_size   1k;
client_body_buffer_size     128k;
large_client_header_buffers 4 4k;
client_max_body_size        10m;
client_body_temp_path       /var/spool/nginx/client/;
output_buffers              1 32k;
postpone_output             1460;

### errors ###
recursive_error_pages   off;
error_page              400 402 403 405 406 410 411 413 416 /40x.html;
error_page              500 501 502 503 504 /50x.html;
error_page              404 =410 /40x.html;
error_page              443 =200 /test.png;
open_log_file_cache     max=1024 inactive=30s min_uses=3 valid=5m;

### acl ###
allow                   10.0.0.0/8;
allow                   172.16.0.0/12;
allow                   192.168.0.0/16;
deny                    all;

### ssl ###
ssl                     on;
#ssl_stapling           on; # selfsigned=off
#ssl_stapling_verify    on; # selfsigned=off
ssl_prefer_server_ciphers on;
ssl_protocols           TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers             HIGH:!RC4:!3DES:!aDSS:!aNULL:!kPSK:!kSRP:!MD5:@STRENGTH:+SHA1:+kRSA;
ssl_session_cache       shared:TLSSL:16m;
ssl_session_timeout     10m;
ssl_certificate         sslcert/de/<company>/wildcard.crt;
ssl_certificate_key     sslcert/de/<company>/wildcard.key;

### compression ###
gzip                    on;
gzip_disable            "msie6";
gzip_vary               on;
gzip_min_length         128;
gzip_buffers            128 32k;
gzip_comp_level         6;
gzip_proxied            any;
gzip_types              text/plain text/css text/x-component
                        text/xml application/xml application/xhtml+xml application/json
                        image/x-icon image/bmp image/svg+xml application/atom+xml
                        text/javascript application/javascript application/x-javascript
                        application/pdf application/postscript
                        application/rtf application/msword
                        application/vnd.ms-powerpoint application/vnd.ms-excel
                        application/vnd.ms-fontobject application/vnd.wap.wml
                        application/x-font-ttf application/x-font-opentype;

### proxy-global ###
#resolver               <dns-proxy>; # we use "pdnsd" here
proxy_intercept_errors  on; # testing=off
proxy_ignore_client_abort off;
proxy_redirect          http:// $scheme://;

### proxy-header ###
proxy_hide_header       Server;
proxy_hide_header       X-Powered-By;
proxy_hide_header       X-AspNet-Version;
proxy_set_header        Accept-Encoding   ""; # no backend compression
proxy_set_header        Host              $http_host;
proxy_set_header        X-Forwarded-By    $server_addr:$server_port;
proxy_set_header        X-Forwarded-For   $remote_addr;
proxy_set_header        X-Forwarded-Class $classification; # our internal custom header
proxy_set_header        X-Forwarded-Proto $scheme;
map $scheme $msiis      { http off; https on; } # compatibility
proxy_set_header        Front-End-Https   $msiis;

### proxy-timeouts ###
proxy_connect_timeout   6;
proxy_send_timeout      60;
proxy_read_timeout      60;

### proxy-buffers ###
proxy_buffering         on;
proxy_buffer_size       8k;
proxy_buffers           256 8k;
proxy_busy_buffers_size    64k;
proxy_temp_file_write_size 64k;
proxy_temp_path         /var/spool/nginx/temp/;

logging.conf

幾つかの追加の情報が必要な場合、この設定ファイル/etc/nginx/conf.d/logging.confは記録を引き起こさなければなりません。SSLプロキシをネットワークデバイスとして定義したため、したがってアプリケーションはユーザアクセスを記録する責任があります。

log_format apache
    '$remote_addr - $remote_user [$time_local] '
    '"$request" $status $body_bytes_sent '
    '"$http_referer" "$http_user_agent" '
    '"$http_cookie"';
log_format full
    '$remote_addr $remote_user [$time_local] '
    '"$host"->$proxy_host->$upstream_addr '
    '"$request" $status($upstream_status) '
    '$bytes_sent/$gzip_ratio($sent_http_content_type) '
    '$request_time($upstream_response_time)';
log_format perf
    '$request_time($upstream_response_time) '
    '$bytes_sent/$gzip_ratio($sent_http_content_type) '
    '$status "$upstream_addr$uri"';
log_format gzip
    '$bytes_sent/$gzip_ratio($sent_http_content_type) '
    '[$http_accept_encoding]"$http_user_agent"';

log_format redirect
    '$time_local $redir_match $redir_action $redir_url';

#access_log off;
 access_log /var/log/nginx/access.log      apache;
#access_log /var/log/nginx/access-full.log full;
#access_log /var/log/nginx/access-perf.log perf;
#access_log /var/log/nginx/access-gzip.log gzip;

backend.conf

このファイルをIDを持つ2つのバックエンドサーバの関係を定義するために使います。NGINXの構文はこれらの定義をグローバルにするだけです。このことは、新しい1つのバックエンド/アップストリームを定義した場合に2つのファイルをtouchする必要があることを意味します。

upstream <backend-id-1>  {
  server <server-ip-1.1>:<internal-port>;
  server <server-ip-1.2>:<internal-port> backup;
}
upstream <backend-id-2>  {
  server <server-ip-2.1>:<internal-port>;
  server <server-ip-2.2>:<internal-port> backup;
}
...
upstream <backend-id-n>  {
  server <server-ip-n.1>:<internal-port>;
  server <server-ip-n.2>:<internal-port> backup;
}

ssl-proxyの最近のバージョンでは、亜フィルは/etc/nginx/scripts/gen-upstream.confを使ってアプリケーション定義から自動的に生成されます。つまり、これで1つのpath_<context> ファイルによって新しいサービスを追加することができます。

#!/bin/sh
START=`pwd`
cd /etc/nginx

cat mapping/dem/path_* | grep "#UP#" | cut -c "6-" > conf.d/upstream.dem-auto.conf
cat mapping/prd/path_* | grep "#UP#" | cut -c "6-" > conf.d/upstream.prd-auto.conf

nginx -t

cd $START

サーバとアプリケーション

簡単なアプリケーション

これは/etc/nginx/mapping/<segment>/<application>にあるデフォルトのアプリケーションの例で、これはほとんどの場合に当てはまるはずです。最初の部分はバックエンド設定の自動生成のために使われるコメントです。

#UP# upstream <backend-id-n>  {
#UP#   server <server-ip-n.1>:<internal-port>;
#UP#   server <server-ip-n.2>:<internal-port> backup;
#UP# }

location /<app-path>/ { proxy_pass http://<backend-id>; }

rootのアプリケーション

幾つかのwebアプリケーションはrootパスをサブディレクトリに変更することを許可しません。それらのうちの一つをサーバ設定の中のサブディレクトリを使ってアプリケーションと組み合わせて使うことができます。proxy_intercept_errors機能を使いたい場合は、if-ステートメントを追加する必要があります。

location / {
    if (-f $request_filename) { break; }
    proxy_pass http://<backend-id>;
}

アップローダー アプリケーション

CMSシステムの編集ページのような幾つかのアプリケーションはしばしば追加の必要があります。例えば映画あるいは大きなPDFをアップロードしたいとします。その場合アップロードの最大サイズを調整する必要があります。

location /<app-path>/ {
    client_max_body_size 100m;
    proxy_pass http://<backend-id>; }

長時間実行しているアプリケーション

アプリケーションの応答が長く掛かり(例えば、レポートの生成)、keepaliveの仕組みが利用できない(私達の場合は"BIRT"フレームワークでした)場合、タイムアウトのデフォルトの設定を、クライアントおよびサーバ側で、予定以上に使いました。

location /<app-path>/ {
    send_timeout 3600;
    proxy_read_timeout 3600;
    proxy_pass http://<backend-id>;
}

Soap Web-Service

クライアントと情報を交換するデフォルトの方法のため、SOAPは タイプ500のエラーを変更しないことを必要とします。

location /<app-path>/ {
    proxy_intercept_errors off;
    proxy_pass http://<backend-id>;
}

簡単なサーバ

この例は、1つの簡単なアプリケーションを使ってインターネット(全てを許可)サーバを説明します。rewriteルールはアプリケーションディレクトリに初期リダイレクトを行います。以下のindexページはアプリケーションの責任下にあります。

server {
    ssl_certificate     sslcert/<dns-domain>/<subdomain>.crt;
    ssl_certificate_key sslcert/<dns-dmoain>/<subdomain>.key;
    listen              <ip>:443; allow all;
    server_name         <ip> <dns>;
    set $classification "<customer>.<channel>.<segment>";
    rewrite ^/+$        /<app-path>/ redirect;
    include             mapping/<segment>/<app-path>
}

リダイレクタ

これは私達のセットアップの中でより複雑なうちの一つです。古い/レガシー URLをスムーズに変更する必要がある場合にのみ、これを実装してください。

Motivation / Goal:

  1. 新しいリンクを使って、リダイレクト、リフレッシュ、あるいはエラーページを送信します。
  2. DNS名あるいはDNSプラス コンテキストのために動作します(パスの最初の部分)
  3. www. プリフィックスの暗黙的な一致
  4. URLとリクエストの引数のために動作します
  5. 引数内で %-コード をパースすることができます

redir-map.conf

map $redir_match $redir_target { hostnames;
#[<context>.]<hostname> #(static|refresh|redirect)@<scheme>://<target>/<context>/;
my-app-1.old-url.com    redirect@https://new-url.com/my-app-1/;
.old-url.com            redirect@https://new-url.com/default-app/;

redir.action

if ($redir_target ~* ^(.*)@(.*)) { set $redir_action $1; set $redir_url $2; }
if ($redir_action = "static")    { rewrite ^ /301-static.html      last; }
if ($redir_action = "refresh")   { rewrite ^ /301-refresh.html     last; }
if ($redir_action = "redirect")  { rewrite ^ $redir_url permanent; break;
    access_log /var/log/nginx/redirector.log redirect;}

リダイレクタ サーバ

server {
    allow       all;
    listen      80 default; ssl off;
    listen      443 default ssl;
    server_name <dns-name>;

    include     mapping/security.ext;

    location /  {
        # deliver local files
        if (-f $request_filename) { break; }
        # redirector
        set $redir_host $http_host;
        if ($http_host ~* ^www\.(.*)) { set $redir_host    $1; }
        if ($uri ~* ^/([^/]+))        { set $redir_context $1.; }
        set $redir_match $redir_context$redir_host;
        include mapping/redir.action;
        # global https enforcement
        if ($scheme = "http") {
            rewrite ^ https://$http_host$request_uri permanent; }
    }

    location /status {
        stub_status     on;
        allow           <monitoring system>;
        deny            all;
    }
}

リダイレクタ アプリケーション

この部分はとても特別です: カスタマーのほとんどがsignonページ(SSOシステム)をブックマックしているため、これをrewriteすることも注意しなければなりません。

location /<login-app>/ {
    if ( $arg_<return-url> ~* ^https?(://|%3A%2F%2F)([^/%]+)(/|%2F)([^/%]*) ) {
         set $redir_match $4.$2; }
    include mapping/redirector.action;
    proxy_pass http://<backend-id>;
}

Active-Sync ゲートウェイ

This is only a simple gateway (no certificates!) for several different Exchange servers. dnsエントリに対して、デバイスの幾つかの"fingerprint"を検証します。The code can be “plugged” into the context files above as a service.

location /Microsoft-Server-ActiveSync {
    access_log /var/log/nginx/activesync.log;
    resolver your.dns.server.ip;
    # deny anonymous; deny other http methods
    if ( $remote_user     =   "" )              { return 444; break; }
    if ( $request_method !~* ^(POST|OPTIONS)$ ) { return 444; break; }
    # extract domain and user-id
    if ( $remote_user     ~* ^(.+)\x5C(.+)$ )   { set $domain $1; set $userid $2; }
    if ( $remote_user    !~* ^(.+)\x5C(.+)$ )   { return 444; break; }
    # replace underscores in username
    if ( $userid          ~* ^(.+)_(.+)$ )      { set $userdn $1x$2; }
    if ( $userid         !~* ^(.+)_(.+)$ )      { set $userdn $userid; }
    # extract device-type and version
    if ( $http_user_agent ~* ^MSFT-(.+)/(.+)\.(.+)\.(.+)$ )  { set $device MSFT$1;  set $versio $2x$3x$4; }
    if ( $http_user_agent ~* ^Apple-iPhone(.*)/(.+)\.(.+)$ ) { set $device iPhone;  set $versio $1x$2x$3; }
    if ( $http_user_agent ~* ^Apple-iPad(.+)/(.+)\.(.+)$ )   { set $device iPad;    set $versio $1x$2x$3; }
    if ( $http_user_agent ~* ^Apple-iPod(.+)/(.+)\.(.+)$ )   { set $device iPod;    set $versio $1x$2x$3; }
    if ( $http_user_agent ~* ^Android-(.+)/(.+)\.(.+)$ )     { set $device Android; set $versio $1x$2x$3; }
    # always allow initial requests without arguments
    set $initia $request_method:$args;
    if ( $initia ~* ^OPTIONS:$ ) { set $target $domain-exchange; set $versio ok; }
    if ( $versio =  "" )         { return 444; break; }
    # set target, if usernames match
    if ( $userid =  $arg_User )  { set $target $domain-$userdn-$arg_DeviceId-$device-$versio; }
    # forward request
    proxy_pass http://$target.your.internal.sync.domain;
}

失敗したリクエストはリゾルバ エラーとしてerror.logの中に現れます。error.log はrsyslogによって監視され、syslogサーバに転送されました。syslogサーバは内部ドメインをチェックし、サポートにメールを送信します。

...
# Mail-Trap: ActiveSync
$ActionExecOnlyOnceEveryInterval 300
$ActionMailTo recicpient-1@your.company
$ActionMailTo recicpient-2@your.company
:msg,contains,"your.internal.sync.domain" :ommail:;mailBody
...

リモート ログ

問題

手短に: NGINXはSyslogをサポートしません。 したがって、Syslogサポートを必要とする場合は幾つかの取りうる方法があります。

  1. syslog patchと一緒にNGINXをコンパイル:

    私はオリジナルのパッケージを使うことが好きです...

  2. ファイルサポートがあるsyslog実装 (例えば "imfile"を使ったrsyslog) を使用:

    error.logについては良いです。access.logをローカルに2回保存したくはないので、access.logが消費する領域については良い考えではありません。

簡単な解決法

  1. 全ての(既存の)syslogメッセージについて/etc/rsyslog.d/remote.confファイルを生成する:
# export via udp
*.notice;local0,local1,local2,local3,local4,local5,local6,local7.*;mark,cron.none @<syslog-server>
  1. ファイルの監視に/etc/rsyslog.d/nginx.conf ファイルを生成する。Repeat the part in the middle for every file you want to see in the syslog. 最後の行が重要です。そうでなければ、これらのメッセージを3度記録するでしょう (NGINX log, udp syslog そしてローカルsyslog):
# import-module: file
$ModLoad imfile

# nginx/error.log
$InputFileName          /var/log/nginx/error.log
$InputFileTag           nginx:
$InputFileStateFile     nginx_error.log
$InputFileSeverity      warning
$InputFileFacility      local7
$InputRunFileMonitor

# send and drop
:syslogtag,isequal,"nginx:"     @<syslog-server>
& ~
  1. Create a script /etc/cron.daily/logfile-actions, which will be executed every day and place there the cleanup commands (eg. アクセスログについては1日、その他全てについえてゃ6ヶ月)。chmod +xするのを忘れないでください。This this at least process all files, which you don’t want to store local a second time. しかし、以前言ったように、1日のアクセスログの量があまり大きく無い場合にのみ動作します... そしてそれはあまりスマートではありません。

    #!/bin/sh
    find /var/log/       -name       *.gz -mtime +180 -delete
    find /var/log/nginx/ -name access*.gz -mtime +2   -delete
    

索引: スクリプト

sync-config.sh

毎日の使用のための最も重要なスクリプトです。しかし、年を重ねるに連れて、とても醜いものになっています。

#!/bin/bash

case `hostname` in
    "sslproxy-01" )
        PEER="sslproxy-02";;
    "sslproxy-02" )
        PEER="sslproxy-01";;
esac

START=`pwd`
    /etc/init.d/nginx reload
    sleep 2
    chown www-data:adm /var/log/nginx/*
    /etc/init.d/keepalived reload
    cd /etc/nginx/sslcert/
    tar -cvjpf /etc/nginx/sslcert.tbz2 ./*
cd $START

echo "
    put -P /etc/cron.daily/logfile-actions        /etc/cron.daily/
    put -P /etc/sysctl.d/10-network-security.conf /etc/sysctl.d/
    put -P /etc/monit/monitrc                     /etc/monit/
    put -P /etc/monit/conf.d/*                    /etc/monit/conf.d/
    put -P /etc/keepalived/keepalived.conf        /etc/keepalived/remote.conf
    put -P /etc/keepalived/remote.conf            /etc/keepalived/keepalived.conf
    put -P /etc/nginx/sslcert.tbz2                /etc/nginx/
    put -P /etc/nginx/nginx.conf                  /etc/nginx/
    rm     /etc/nginx/conf.d/*
    put -P /etc/nginx/conf.d/*                    /etc/nginx/conf.d/
    rm     /etc/nginx/content/*
    put -P /etc/nginx/content/*                   /etc/nginx/content/
    rm     /etc/nginx/scripts/*
    put -P /etc/nginx/scripts/*                   /etc/nginx/scripts/
    rm     /etc/nginx/sites-enabled/*
    put -P /etc/nginx/sites-enabled/*             /etc/nginx/sites-enabled/
    rm     /etc/nginx/mapping/*
    rm     /etc/nginx/mapping/dem/*
    rm     /etc/nginx/mapping/prd/*
    put -P /etc/nginx/mapping/*                   /etc/nginx/mapping/
    put -P /etc/nginx/mapping/dem/*               /etc/nginx/mapping/dem/
    put -P /etc/nginx/mapping/prd/*               /etc/nginx/mapping/prd/
    rm     /etc/nginx/access/*
    rm     /etc/nginx/access/lan/*
    rm     /etc/nginx/access/ext/*
    rm     /etc/nginx/access/vn1/*
    rm     /etc/nginx/access/vn2/*
    put -P /etc/nginx/access/*                    /etc/nginx/access/
    put -P /etc/nginx/access/lan/*                /etc/nginx/access/lan/
    put -P /etc/nginx/access/ext/*                /etc/nginx/access/ext/
    put -P /etc/nginx/access/vn1/*                /etc/nginx/access/vn1/
    put -P /etc/nginx/access/vn2/*                /etc/nginx/access/vn2/
    bye
" | sftp -C root@$PEER
rm /etc/nginx/sslcert.tbz2

ssh $PEER '
    cd /etc/nginx/sslcert/
    rm -rf ./*
    tar -xvjpf /etc/nginx/sslcert.tbz2
    rm /etc/nginx/sslcert.tbz2
    /etc/init.d/nginx reload
    chown www-data:adm /var/log/nginx/*
    /etc/init.d/keepalived reload
'

exit 0

dump-config.sh

"正規化"設定ファイルを生成します。基本的に、include-ステートメントを評価し、空白とコメントを削除する再帰的なスクリプトです。これは、バックアップ/リストア、ssl検証などを行う私のほとんどのスクリプトの基本です。おそらく綺麗でも無く、完全でもありませんが、動作します。

#!/bin/sh
START=`pwd`
cd /etc/nginx

if [ -x $0 ]
    then CMD=$0
    else CMD=$START/$0
fi

if [ "$1" ]
    then FILE=$1
    else FILE="nginx.conf"
fi

echo "# start: $FILE"
cat $FILE | awk '{
    gsub("#.*","",$0);
    gsub(";",";\n",$0);
    gsub("{","\n{\n",$0);
    gsub("}","\n}\n",$0);
    print;
}' | awk -v HK="'" -v CMD=$CMD '{
    gsub("[ \t]+"," ",$0);
    gsub("^[ \t]","",$0);
    gsub("[ \t]$","",$0);
    gsub(HK,"%%",$0);
    if ($1=="include") {
        sub(";$","",$2);
        print CMD" "HK$2HK; }
    else {
        print "echo "HK$0HK; }
}' | sh | awk -v HK="'" '{
    gsub("%%",HK,$0);
    if ($0=="") {
        pass; }
    else {
        print; }
}' | cat
echo "# stop: $FILE"

cd $START
#exit 0

clean-restart.sh

このスクリプトは幾つかのサービスを再起動し、ログファイルを削除し、大きな変更の場合にループバックアドレスを再活性化します。開発システムで特に使います。製品マシーン上では、意図しないリブート時に正しく全てのことが開始されるように、そうしないで変更を行ってから再起動します。 unexpected reboot.

#!/bin/bash

/etc/init.d/monit      stop
/etc/init.d/keepalived stop
/etc/init.d/nginx      stop
ifconfig -a | grep "lo:" | awk '{print "ifconfig "$1" down"}' | sh

chmod    +x  /etc/nginx/conf.d/ip-addr.sh
chmod -R +x  /etc/nginx/scripts/*
chmod -R 600 /etc/nginx/sslcert/*
rm /var/log/monit
rm /var/log/nginx/*
# other commands, like "apt-get -y upgrade"

/etc/nginx/conf.d/ip-addr.sh
/etc/init.d/nginx      start
/etc/init.d/keepalived start
/etc/init.d/monit      start

exit 0

良く知られたバグ / 欲しいもののリスト

インラインの include
With an statement like include @<identifier> and a block like include { include_name <identifier>; ... } a seperate file for every include could be avoided.
グローバルなrewriteルールrewriteのためのログオプション
If you have a inverse proxy it would be the perfect place to enforce a bunch o rewrite rules globaly. Because this is an security feature, each firing of one rule should be logged in a (separate?) log.