nginxのメールプロキシーで海外からのアクセスを制限する

#概要
海外から管理するMXサーバへの不正と考えられるSMTPアクセスが多いので、
一律に制限をするSMTPプロキシーサーバをnginxで構築した。

#制限の条件
- 日本からのアクセスは制限しない
- Google,Microsoft,Amazon と考えられる送信元からのアクセスは制限しない
- HELO/EHLO が localhost の場合は、制限する
- 特定ドメイン宛への１分間に１回を超えるSMTPアクセスは、制限する

注意:１接続で複数通メールを送信するアクセスはこの方法では制限できません

#制限時のクライアントへの応答
421 4.7.0 Please Try Again Lator.

#設定
##1. smtp proxy設定
192.168.1.1:25 でSMTPをLISTENする場合

```lang:nginx.conf
mail {
  auth_http  127.0.0.1/smtpproxy/ ;
  proxy_pass_error_message on;
  proxy on;
  smtp_auth none;
  smtp_capabilities 8BITMIME ;
  server {
    listen 192.168.1.1:25;
    protocol smtp;
    xclient  off;
    starttls off;
  }
}

```
##2. proxy先の smtp サーバを応答する設定
smtp proxy先を 127.0.0.1:25 とする場合

```lang:nginx.conf
http {
  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;

  log_format  main  '$remote_addr [$time_local] "$request" $status $body_bytes_sent '
                    '$http_x_forwarded_for $geoip_country_code '
                    '$http_x_helo_domain $http_x_from_addr $http_x_to_addr $limit_key $limit_result';

  access_log  /var/log/nginx/access.log  main;

  sendfile        on;
  keepalive_timeout  65;

  ### geoip と 制限に使用する limit_req_zone の設定
  geoip_country   /usr/share/GeoIP/GeoIP.dat;
  geoip_proxy     127.0.0.1;
  limit_req_zone  $limit_key zone=gccdomreq:5m rate=1r/m;

  server {
    listen       127.0.0.1:80;
    server_name  localhost;
    root /var/www/html;
    charset utf-8;
    index index.html;
    server_tokens off;

    ### smtp proxy が proxy 先を確認する時に接続するURL
    location /smtpproxy {
      proxy_intercept_errors on;
      proxy_pass http://127.0.0.1/geolimit;
      proxy_set_header X-Forwarded-For $http_client_ip;

      set $limit_key '-' ;
      set $limit_result '-' ;

      ### HELO/EHLO ドメインを抽出
      set $helo_domain '-' ;
      if ( $http_auth_smtp_helo ~* ([\w\._-]+)$ ) {
        set $helo_domain $1;
      }
      proxy_set_header X-Helo-Domain $helo_domain;

      ### Fromアドレスを抽出
      set $from_addr '-' ;
      if ( $http_auth_smtp_from ~* ([\+\w\._-]+\@[\w\._-]+) ) {
        set $from_addr $1;
      }
      proxy_set_header X-From-Addr $from_addr;

      ### Fromドメインを抽出
      set $from_domain '-';
      if ( $http_auth_smtp_from ~* \@([\w\._-]+) ) {
        set $from_domain $1;
      }
      proxy_set_header X-From-Domain $from_domain;

      ### Toアドレスを抽出
      set $to_addr '-' ;
      if ( $http_auth_smtp_to ~* ([\+\w\._-]+\@[\w\._-]+) ) {
        set $to_addr $1;
      }
      proxy_set_header X-To-Addr $to_addr;

      ### Toドメインを抽出
      set $to_domain '-';
      if ( $http_auth_smtp_to ~* \@([\w\._-]+) ) {
        set $to_domain $1;
      }
      proxy_set_header X-To-Domain $to_domain;

      ### 許可リスト適用時の応答内容
      error_page 501 =200 /approve ;

      ### 制限時の応答内容
      error_page 503 =200 /reject ;
    }

    location /geolimit {
      ###制限に使用するzone(宛先ドメイン名)の名前をセット
      set $limit_key $http_x_to_domain;
      set $limit_result '-' ;

      ### 日本からのアクセスは許可
      if ( $geoip_country_code ~* ^(JP)$ ) {
        set $limit_result OK_COUNTRY_CODE ;
        return 501;
      }

      ### HELO/EHLO に localhost が入力された場合の制限
      if ( $http_x_helo_domain ~* ^(localhost)$ ) {
        set $limit_result REJECT_HELO_DOMAIN ;
        return 503;
      }

      ### HELO/EHLO で *.google.com,*.amazonses.com,*.outlook.com が入力された場合は許可
      if ( $http_x_helo_domain ~* \.(google|amazonses|outlook)\.com$ ) {
        set $limit_result OK_HELO_DOMAIN ;
        return 501;
      }

### 各種許可リストの設定例
#      ### white list for the client ip
#      if ( $http_x_forwarded_for ~ ^192\.168\.0\. ) {
#        set $limit_result OK_CLIENT_IP ;
#        return 501;
#      }
#
#      ### white list for the "Mail From" domain
#      if ( $http_x_from_domain = 'example.com' ) {
#        set $limit_result OK_FROM_DOMAIN ;
#        return 501;
#      }
#
#      ### white list for the "Rcpt To" domain
#      if ( $http_x_to_domain = 'example.com' ) {
#        set $limit_result OK_TO_DOMAIN ;
#        return 501;
#      }

      ### 制限設定
      limit_req zone=gccdomreq;

      ### httpのproxy元へのsmtpのproxy先を応答内容
      add_header Auth-Status OK;
      add_header Auth-Server 127.0.0.1;
      add_header Auth-Port 25;
      empty_gif;
    }

    ### 許可リスト適用時のhttpのproxy元へのsmtpのproxy先の応答内容
    location /approve {
      add_header Auth-Status OK;
      add_header Auth-Server 127.0.0.1;
      add_header Auth-Port 25;
      empty_gif;
    }

    ### 制限適用時のhttpのproxy元へのsmtpのproxy先の応答内容
    location /reject {
      add_header Auth-Status "Please Try Again Later.";
      add_header Auth-Error-Code "421 4.7.0";
      empty_gif;
    }
  }
}
```

#動作テスト
##1. アメリカからのSMTPアクセス

```
・1回目のSMTPアクセス
220 localhost.localdomain ESMTP ready
EHLO example.com
250-localhost.localdomain
250 8BITMIME
MAIL FROM:<foo@example.com>
250 2.0.0 OK
RCPT TO:<bar@example.com>
250 2.1.5 Ok
=>正常

・アクセスログ
127.0.0.1 [19/Dec/2015:22:00:57 +0900] "GET /geolimit/ HTTP/1.0" 200 43 20.1.1.1 US example.com foo@example.com bar@example.com example.com -
127.0.0.1 [19/Dec/2015:22:00:57 +0900] "GET /smtpproxy/ HTTP/1.0" 200 43 - - - - - - -

・２回目（制限時）のSMTPアクセス
220 localhost.localdomain ESMTP ready
EHLO example.com
250-localhost.localdomain
250 8BITMIME
MAIL FROM:<foo@example.com>
250 2.0.0 OK
RCPT TO:<bar@example.com>
421 4.7.0 Please Try Again Later.
=>制限された

・アクセスログ
127.0.0.1 [19/Dec/2015:22:01:13 +0900] "GET /geolimit/ HTTP/1.0" 503 206 20.1.1.1 US example.com foo@example.com bar@example.com example.com -
127.0.0.1 [19/Dec/2015:22:01:13 +0900] "GET /smtpproxy/ HTTP/1.0" 200 43 - - - - - - -

・エラーログ
2015/12/19 22:01:13 [error] 7500#0: *11 limiting requests, excess: 0.741 by zone "gccdomreq", client: 127.0.0.1, server: localhost, request: "GET /geolimit/ HTTP/1.0", host: "127.0.0.1"

※20.1.1.1 を x-forwarded-for に記述してテスト
```

##2. HELOでlocalhostを入力したテスト
```
・SMTP
220 localhost.localdomain ESMTP ready
EHLO localhost
250-localhost.localdomain
250 8BITMIME
MAIL FROM:<foo@example.com>
250 2.0.0 OK
RCPT TO:<bar@example.com>
421 4.7.0 Please Try Again Later.
=>制限された

・アクセスログ
127.0.0.1 [19/Dec/2015:22:05:29 +0900] "GET /geolimit/ HTTP/1.0" 503 206 20.1.1.1 US localhost foo@example.com bar@example.com example.com REJECT_HELO_DOMAIN
127.0.0.1 [19/Dec/2015:22:05:29 +0900] "GET /smtpproxy/ HTTP/1.0" 200 43 - - - - - - -
```

##3. HELOでhoge.google.comを入力したテスト
```
・SMTP
220 localhost.localdomain ESMTP ready
EHLO hoge.google.com
250-localhost.localdomain
250 8BITMIME
MAIL FROM:<foo@example.com>
250 2.0.0 OK
RCPT TO:<bar@example.com>
250 2.1.5 Ok
=>正常(許可リスト適用)

・アクセスログ
127.0.0.1 [19/Dec/2015:22:07:35 +0900] "GET /geolimit/ HTTP/1.0" 501 174 20.1.1.1 US hoge.google.com foo@example.com bar@example.com example.com OK_HELO_DOMAIN
127.0.0.1 [19/Dec/2015:22:07:35 +0900] "GET /smtpproxy/ HTTP/1.0" 200 43 - - - - - - -
```

##4. 日本からのアクセス
```
・SMTP
220 localhost.localdomain ESMTP ready
EHLO localhost
250-localhost.localdomain
250 8BITMIME
MAIL FROM:<foo@example.com>
250 2.0.0 OK
RCPT TO:<bar@example.com>
250 2.1.5 Ok
=>正常(許可リスト適用)

・アクセスログ
127.0.0.1 [19/Dec/2015:22:10:17 +0900] "GET /geolimit/ HTTP/1.0" 501 174 133.242.186.90 JP localhost foo@example.com bar@example.com example.com OK_COUNTRY_CODE
127.0.0.1 [19/Dec/2015:22:10:17 +0900] "GET /smtpproxy/ HTTP/1.0" 200 43 - - - - - - -
```

#テスト環境
CentOS 6.7
nginx 1.9.9 (--with-http_geoip_module を追加)

#最後に
POP/IMAPへ応用もしてみたい。
時間毎の制限値変更もしてみたい。
宛先と接続元IPの組み合わせで Greylisting をしてみたかったがnginxだけでやる方法を思いつかなかった。

記事内容に間違いがあり、なんらかの損失が発生したとしても責任は負いませんのであしからず。