Pound 2.7の小ネタあれこれ


Poundは、軽量なリバースプロキシです。SSL(TLS)ラッパーとしても利用できます。

特に目新しい話題もありませんが、iPhoneアプリから呼び出すサーバAPIを背後に置くなど、ATSの制限に引っかかることもあるかもしれないので、とりあえずあげておきます。

なお、ここではソースからインストールする方法をとりあげます。特に記述がなければ、対象バージョンは2.7です。

HTTPリクエスト/レスポンスヘッダサイズの制限を変更する

Poundは、デフォルトではHTTPリクエスト/レスポンスヘッダサイズの上限が4096バイトです。
通常はこれで問題ないはずですが、長いcookieを発行するなどでサイズが超過し、背後のWebサーバで稼働しているアプリケーションなどが正しく動作しないことがあります。

Apacheなどでは設定で上限を変更できますが、Poundの場合はconfigureでパラメータを指定してmakeし直すことになります。

(以下、カレントがソースディレクトリ内になっている前提で。ソース展開時のownerに注意して。)

$ make clean
$ ./configure --with-maxbuf=【設定するバイト数】 --with-ssl --with-dh=2048
$ make
$ sudo make install

※本来はリクエスト等を処理するためのバッファサイズの指定ですが、HTTPリクエスト/レスポンスヘッダとして処理できるサイズがこのバッファサイズの制限を受けるため、ここを調整します。

 なお、1行目の「make clean」は一度もmakeしていない場合は不要です(エラーになる)。

 また、./configure、makeするために、

  • make
  • gcc
  • openssl
  • openssl-devel

などのパッケージが必要です(CentOS系の場合)。

鍵交換にECDHEを使う

鍵交換にECDHEを使う設定です。PFS(Perfect Forward Secrecy)対応となるので、TLS通信がよりセキュアになります。

また、iPhoneアプリ(iOS9以上)から呼び出すサーバAPIを背後に置くときに、ATS(App Transport Security)の制限に引っかかってTLS通信できない・アプリの審査でリジェクトされる…という事態を避けるために必要になります(Pound 2.7からの機能です)。

設定ファイルへの記述で対応します。

pound.cfg(一部抜粋)
ECDHcurve   "prime256v1"

ListenHTTPS
        Address                     【待ち受けIPアドレス】
        Port                        443
        Cert                        "【証明書ファイルのパス】"
        Disable                     SSLv3
        SSLAllowClientRenegotiation 0
        SSLHonorCipherOrder         1
        Ciphers                     "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:EDH+aRSA:-RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS"

(以下略)

暗号スイートの指定は適宜調整してください。

プロトコルと暗号スイートの指定について

ApacheやNginxとOpenSSLを組み合わせて使っている人にとっては当たり前の話だと思いますが、Poundは2.6までプロトコルの指定をする設定項目がなかったことと、SSL 2.0の無効化は暗号スイートの指定で行っていたので、もしかすると勘違いがあるかもしれません。

脆弱性対処のためにSSL 3.0までを無効化する場合、プロトコルの無効化指定(Disable)でSSLv3を無効にするのが正しいのですが、Pound 2.6までこの項目がなかったため、場合によっては暗号スイートの指定(Ciphers)でSSLv3を無効化(!SSLv3や-SSLv3)してしまっているケースがあるかもしれません。

暗号スイートの指定でSSLv3を無効化すると、SSL 3.0だけではなく、TLS 1.0やTLS 1.1も利用できなくなってしまいます。

なお、Pound 2.7では、デフォルトで「Disable SSLv3」が指定されていることになっているので、特に設定を書き加えることなく、SSL 3.0は無効化されています。

ソースから上書きインストールするときの注意

Poundをバージョンアップしたり、configureのパラメータ指定を変えたいときの注意点です。
サービス停止時間を短くしたいと考えるあまり、サービスを止めずに「make install」し、それからサービスの再起動をしてしまうと、当然ですが異常が発生します。

「DHパラメータの生成に時間がかかるからmake後にサービスを停止したい」場合でも、必ず、サービスを停止(「service pound stop」など)してから「make install」し、その後サービスを起動(「service pound start」など)します。
「make install」はファイルをコピーしているだけなので、所要時間はわずかです。安心してください。

ただ、ちょっと厄介なのは、手順を間違えた場合に「起動せずに停止してしまう」のではなく、「一見正常に起動しているように見えるが、一部の暗号スイートが使えない」状態になることです。

こちらのメーリングリストで質問されている方は、どうやらこのミスを犯してしまったようです(たぶん)。

うっかりやってしまった場合は、対象プロセスをkillして(-9は不要)、あらためてサービスを起動するか、OSを再起動すると、正常な状態になります。

HSTSに対応する

中間者攻撃による不正サイトへのリダイレクトや、セッションハイジャックなどを防ぐ・軽減する仕組みとして、HSTS(HTTP Strict Transport Security)があります。

HSTS関連の設定機能は、Pound 2.7のstable版には採用されなかったようですが、2.7c用のパッチがあるので、stable版に適用できるよう、修正してみました。

pound_27_sts.patch
diff -uprN Pound-2.7.original/config.c Pound-2.7/config.c
--- Pound-2.7.original/config.c 2015-01-27 01:47:53.000000000 +0900
+++ Pound-2.7/config.c  2016-07-06 18:31:24.148061328 +0900
@@ -76,7 +76,7 @@ static CODE facilitynames[] = {
 static regex_t  Empty, Comment, User, Group, RootJail, Daemon, LogFacility, LogLevel, Alive, SSLEngine, Control;
 static regex_t  ListenHTTP, ListenHTTPS, End, Address, Port, Cert, xHTTP, Client, CheckURL;
 static regex_t  Err414, Err500, Err501, Err503, MaxRequest, HeadRemove, RewriteLocation, RewriteDestination;
-static regex_t  Service, ServiceName, URL, HeadRequire, HeadDeny, BackEnd, Emergency, Priority, HAport, HAportAddr;
+static regex_t  Service, ServiceName, URL, HeadRequire, HeadDeny, BackEnd, Emergency, Priority, HAport, HAportAddr, StrictTransportSecurity;
 static regex_t  Redirect, RedirectN, TimeOut, Session, Type, TTL, ID, DynScale;
 static regex_t  ClientCert, AddHeader, DisableProto, SSLAllowClientRenegotiation, SSLHonorCipherOrder, Ciphers;
 static regex_t  CAlist, VerifyList, CRLlist, NoHTTPS11, Grace, Include, ConnTO, IgnoreCase, HTTPS;
@@ -564,6 +564,7 @@ parse_service(const char *svc_name)
     memset(res, 0, sizeof(SERVICE));
     res->sess_type = SESS_NONE;
     res->dynscale = dynscale;
+    res->sts = -1;
     pthread_mutex_init(&res->mut, NULL);
     if(svc_name)
         strncpy(res->name, svc_name, KEY_SIZE);
@@ -625,6 +626,8 @@ parse_service(const char *svc_name)
             lin[matches[1].rm_eo] = '\0';
             if(regcomp(&m->pat, lin + matches[1].rm_so, REG_ICASE | REG_NEWLINE | REG_EXTENDED))
                 conf_err("HeadDeny bad pattern - aborted");
+        } else if(!regexec(&StrictTransportSecurity, lin, 4, matches, 0)) {
+            res->sts = atoi(lin + matches[1].rm_so);
         } else if(!regexec(&Redirect, lin, 4, matches, 0)) {
             if(res->backends) {
                 for(be = res->backends; be->next; be = be->next)
@@ -851,12 +854,16 @@ parse_HTTP(void)
         } else if(!regexec(&LogLevel, lin, 4, matches, 0)) {
             res->log_level = atoi(lin + matches[1].rm_so);
         } else if(!regexec(&Service, lin, 4, matches, 0)) {
-            if(res->services == NULL)
+            if(res->services == NULL) {
                 res->services = parse_service(NULL);
-            else {
+                if(res->services->sts >= 0)
+                    conf_err("StrictTransportSecurity not allowed in HTTP listener - aborted");
+            } else {
                 for(svc = res->services; svc->next; svc = svc->next)
                     ;
                 svc->next = parse_service(NULL);
+                if(svc->next->sts >= 0)
+                    conf_err("StrictTransportSecurity not allowed in HTTP listener - aborted");
             }
         } else if(!regexec(&ServiceName, lin, 4, matches, 0)) {
             lin[matches[1].rm_eo] = '\0';
@@ -1469,6 +1476,7 @@ config_parse(const int argc, char **cons
     || regcomp(&URL, "^[ \t]*URL[ \t]+\"(.+)\"[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&HeadRequire, "^[ \t]*HeadRequire[ \t]+\"(.+)\"[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&HeadDeny, "^[ \t]*HeadDeny[ \t]+\"(.+)\"[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
+    || regcomp(&StrictTransportSecurity, "^[ \t]*StrictTransportSecurity[ \t]+([0-9]+)[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&BackEnd, "^[ \t]*BackEnd[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&Emergency, "^[ \t]*Emergency[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&Priority, "^[ \t]*Priority[ \t]+([1-9])[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
@@ -1639,6 +1647,7 @@ config_parse(const int argc, char **cons
     regfree(&URL);
     regfree(&HeadRequire);
     regfree(&HeadDeny);
+    regfree(&StrictTransportSecurity);
     regfree(&BackEnd);
     regfree(&Emergency);
     regfree(&Priority);
diff -uprN Pound-2.7.original/http.c Pound-2.7/http.c
--- Pound-2.7.original/http.c   2015-01-27 01:47:53.000000000 +0900
+++ Pound-2.7/http.c    2016-07-06 18:37:45.584260067 +0900
@@ -1382,6 +1382,8 @@ do_http(thr_arg *arg)
             if(!no_cont && !regexec(&RESP_IGN, response, 0, NULL, 0))
                 no_cont = 1;

+            for(n = 0; n < MAXHEADERS; n++)
+                headers_ok[n] = 1;
             for(chunked = 0, cont = -1L, n = 1; n < MAXHEADERS && headers[n]; n++) {
                 switch(check_header(headers[n], buf)) {
                 case HEADER_CONNECTION:
@@ -1432,6 +1434,11 @@ do_http(thr_arg *arg)
                         }
                     }
                     break;
+                case HEADER_STRICT_TRANSPORT_SECURITY:
+                    /* enforce pound's STS header */
+                    if(svc->sts >= 0)
+                        headers_ok[n] = 0;
+                    break;
                 }
             }

@@ -1441,6 +1448,8 @@ do_http(thr_arg *arg)
             /* send the response */
             if(!skip)
                 for(n = 0; n < MAXHEADERS && headers[n]; n++) {
+                    if(!headers_ok[n])
+                        continue;
                     if(BIO_printf(cl, "%s\r\n", headers[n]) <= 0) {
                         if(errno) {
                             addr2str(caddr, MAXBUF - 1, &from_host, 1);
@@ -1452,6 +1461,8 @@ do_http(thr_arg *arg)
                     }
                 }
             free_headers(headers);
+            if(!skip && ssl && svc->sts >= 0)
+                BIO_printf(cl, "Strict-Transport-Security: max-age=%d\r\n", svc->sts);

             /* final CRLF */
             if(!skip)
diff -uprN Pound-2.7.original/pound.h Pound-2.7/pound.h
--- Pound-2.7.original/pound.h  2015-01-27 01:47:53.000000000 +0900
+++ Pound-2.7/pound.h   2016-07-06 18:41:18.769273399 +0900
@@ -370,6 +370,7 @@ typedef struct _service {
 #endif
     int                 dynscale;   /* true if the back-ends should be dynamically rescaled */
     int                 disabled;   /* true if the service is disabled */
+    int                 sts;        /* strict transport security */
     struct _service     *next;
 }   SERVICE;

@@ -441,6 +442,7 @@ typedef enum { RENEG_INIT=0, RENEG_REJEC
 #define HEADER_URI                  9
 #define HEADER_DESTINATION          10
 #define HEADER_EXPECT               11
+#define HEADER_STRICT_TRANSPORT_SECURITY 12

 /* control request stuff */
 typedef enum    {
diff -uprN Pound-2.7.original/svc.c Pound-2.7/svc.c
--- Pound-2.7.original/svc.c    2015-01-27 01:47:53.000000000 +0900
+++ Pound-2.7/svc.c     2016-07-06 18:42:10.738253739 +0900
@@ -395,6 +395,7 @@ check_header(const char *header, char *c
         { "User-agent",         10, HEADER_USER_AGENT },
         { "Destination",        11, HEADER_DESTINATION },
         { "Expect",             6,  HEADER_EXPECT },
+        { "Strict-Transport-Security", 25, HEADER_STRICT_TRANSPORT_SECURITY },
         { "",                   0,  HEADER_OTHER },
     };
     int i;

(すみません、GitHub連携してないので、コピペして使ってください。)

Poundのソースを展開したディレクトリの1階層上で、以下のとおりに適用してください。

$ patch -p1 -d Pound-2.7 < pound_27_sts.patch

なお、リンク元の記述にあるとおり、このパッチには、「includeSubDomainsオプションが使えない」(=攻撃対策としては一部不十分なところがある)、「HTTPListenerで処理するリクエストについてHSTSヘッダを除去しない」などの制約があります。

また、リンク元の記述では「HTTPSListeners(ListenHTTPSディレクティブ)の内側に記述してね」というようなことが書かれていますが、正確には、さらにその中の、Serviceディレクティブの内側に設定(「StrictTransportSecurity 有効期間=秒数」)を記述します。

pound.cfg(一部抜粋)
ECDHcurve   "prime256v1"

ListenHTTPS
        Address                     【待ち受けIPアドレス】
        Port                        443
        Cert                        "【証明書ファイルのパス】"
        Disable                     SSLv3
        SSLAllowClientRenegotiation 0
        SSLHonorCipherOrder         1
        Ciphers                     "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:EDH+aRSA:-RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS"

        Service
                StrictTransportSecurity 31536000

(以下略)

以上、使っている方は少ないと思いますが、Poundの小ネタ集でした。