harumaki.net

インフラ屋の覚書や、ラーメン食べある記とか。

Amazon Linux nginx SSL web

[nginx] SSLクライアント認証をしつつ、指定したIPは認証を許可する設定

投稿日:2018年3月23日

AWS EC2でセットアップし、自社内で利用しているredashを、社内の担当者と外注先とで共用しつつ、セキュリティのためSSLクライアント認証を導入したいとの依頼を受け対応。
ただし従業員のほうが利用者が多いので、ちょっと融通を利かせる必要が。

具体的には

  • グローバルに公開する
    ほんとはVPNなりセキュリティグループでの制限をしたかったが、外注先が固定IPではないので( ^ω^)・・・
  • 会社からアクセスする場合も、VPNを経由せずグローバルからアクセスさせる。外注先からレポートをもらうとき、同じドメインを見るようにしたい、とのこと(DNSの設定で制約があったため、インターナルゾーンが使えなかった…)
  • 指定したIPアドレス(会社のゲートウェイIP)からは、証明書なしでアクセスを許可
  • 指定したIPアドレス以外からは、証明書が必要(外注さんに使ってもらう)
  • 証明書がない場合はエラーを返す

といった感じで実装してみる。

▼構成

  • webアプリはredashを使う
  • サーバはAWS EC2インスタンス(redashが使えるやつ)
  • EIPあり
  • httpサーバはnginx
  • サイトアクセスはSSLのみにする
  • 証明書は自己署名認証局を立てる(いわゆるオレオレ)
    ルート証明書は利用者全員に配布する。

▼情報収集

apacheでは変数を設定することで許可させることが可能。

参考:お世話になっております!

Apacheのクライアント認証をIPアドレスによっては不要にしてみる
ただし、nginxではssl moduleの埋め込み変数でIPアドレスを定義する変数が無い様子。

と同時に、apacheのSSLRequire に該当する条件式をif文で定義することは可能。
ただしnginx公式サイトではif文での条件式を推奨していない)

参考:NGINX:If Is Evil
https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/ (英文)
http://mogile.web.fc2.com/nginx_wiki/nginx_wiki201701/start/topics/depth/ifisevil/ (日本語)
今回はlocationでなくserverでif文を使うようにしたので、変な動きはしないと思う…(という願い)

結局ほかにうまい方法が見当たらなかったので、IPアドレスを指定した条件式をif文で書くことに。

参考:お世話になっております!

▼動作確認が出来た、認証時のIP振り分け設定

結局、(やりたくなかったが)ifで条件式を設定する形で何とか対応した。

やったことまとめ:

  •  apacheの埋め込み変数でのIP指定にあたる部分は、nginxのgeoモジュールを使って許可するIPを変数に指定(if文の条件式に使う)。
    ngx_http_geo_module
  • if文での条件式に対応させるためssl_verify_client の指定を“optional”にする
  • 条件式にてgeo変数で指定したIPアドレスを評価し、OKの場合は認証時の埋め込み変数”ssl_client_verify”にSUCSESSを入れて認証成功状態にして処理を通す。
    http://nginx.org/en/docs/http/ngx_http_ssl_module.html#var_ssl_client_verify
  • 指定したIP以外は通常に認証を行う。ssl_client_verifyにてSUCSESS出なかった場合は403を返す
    ssl_verify_client で”optional”を指定した場合、はクライアント側の証明書の有無を判別するもののリクエストの拒否はしないので、if文で何らかの形で拒否をする設定が必要となる。ちなみにssl_verify_client をyesで設定した場合の認証エラー時はステータスコード400(Bad request)になる。

■設定抜粋:

最終的にはapache httpdと同じような感じで実装が出来た。

	http {

	    <--snip-->
	    # geoディレクティブで許可するIPを指定し、ステータスOKとする。
	    geo  $geo {
        	default  disable;
        	203.0.113.254 allow;
	    }

	    <--snip-->
	 
	    server {

		# if文での条件式に対応させるため、optionalを指定する。
		ssl_verify_client optional;

		# クライアントとの接続元IPが変数"$geo"に合致した場合、"ssl_client_verify"の値に"SUCCESS"をsetして認証成功扱いにする。
	        if ($geo = 'allow') {
	            set ssl_client_verify "SUCCESS";
	        }

		# 変数"$geo"に合致しないIPからのアクセスの条件式はこちら。
		# "ssl_verify_client optional"の補完として、認証に失敗したクライアントには403を返す。
		if ($ssl_client_verify != SUCCESS) {
			return 403;
		}

	    <--snip-->
	    } 

	} 

■上記を反映させた、実際の設定(IPとかホスト名は変えてあります):

		upstream rd_servers {
		  server 127.0.0.1:5000;
		}

		# 後述する"ssl_client_verify"での判定時に、自社のIPを許可するためgeoモジュールを使用して
		# 自分の会社のゲートウェイIPアドレスを許可する。
		    geo  $geo {
	        	default  disable;
	        	203.0.113.254 allow;
		    }

		# 80 -> 443 http -> https リダイレクト
		server {
		  listen 80;
		  server_name host.example.com;
		    location / {
		      return 301 https://$host$request_uri;
		    }
		}

		server {

		  server_tokens off;

		  listen 443 ssl;
		  server_name host.example.com;

		  # サーバ側SSL設定
		  ssl_certificate /etc/ssl/CA/certs/host.example.com.crt;
		  ssl_certificate_key /etc/ssl/CA/private/host.example.com.key;

		  # クライアント証明書を要求する設定
		  # if文での条件式に対応させるため、optionalを指定する。
		  ssl_verify_client optional;
		  ssl_client_certificate /etc/ssl/CA/cacert.pem;

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

		  gzip on;
		  gzip_types *;
		  gzip_proxied any;

		  ## IP access limitation  2018-03-23
		        # 自社GWからアクセスする場合は許可する
		        if ($geo = 'allow') {
		            set $ssl_client_verify "SUCCESS";
		        }

		        # 社外からのアクセスには証明書が必要。認証が通らない場合は403を返す。
		        if ($ssl_client_verify != SUCCESS) {
		                return 403;
		        }

		  location / {
		    proxy_set_header Host $http_host;
		    proxy_set_header X-Real-IP $remote_addr;
		    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		    proxy_set_header X-Forwarded-Proto $scheme;
		    proxy_pass       http://rd_servers;
		  }
		}

ひとまず上記で要件はクリア。ラーメン食べに行ってきます!

-Amazon Linux, nginx, SSL, web
-, ,

執筆者:


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA


関連記事

no image

オープンソースのWebロードバランサー(に使えるミドルウェア)をいろいろ調べてみる

お客さんのWebシステムをハウジング環境からAWSへ移行するにあたり、一部のサイトにおいて仕様上ELBを使えない状況になってしまい、代替の方法を考える必要が出てきてしまった… ELB以外でロードバラン …

no image

haproxyを試す 4.ソケット接続とステータス出力

haproxyでステータスやバックエンドサーバーの状態やプールを確認する場合、コマンドラインからチェックできるツールは無い様子。代わりにソケットリレーツール”socat(SOcketをCA …

OpenRestyのインストールメモ[RPMbuild] Ver1.9.7.4編

先ほどのOpenResty_1.9.7.2インストール作業からのアップデート。 最新の1.9.7.4では、名称がngx_openrestyから只の”openresty”に変わっ …

no image

[ただのメモ] nginx 問答無用の301リダイレクト設定。

※書式を忘れがちなので、自分用のメモ目的です。 ドメインの変更やサービスの移転などでリダイレクトを設定する際、コンテンツごとのURLパスを無視してすべてのリクエストを指定した移転先へ飛ばす設定。 職業 …

[AWS]Certificate Manager 証明書リクエストメールの宛先を変更する場合

参考:お世話になっております! AWS Certificate Manager UserGuide: Validate Domain Ownership Using the ACM API Confi …