ALBリスナールールを使って特定パス以下のアクセスを許可IPからのみに制限する

一般公開しているサイトの`/admin/`以下のパスで管理システムを稼働させているような構成のアプリケーションはよくあるかと思います。通常、管理システムは特定の人(社内の人)以外からのアクセスを禁止したいかと思います。今回はALBの機能でこういった特定のパス以下のアクセスを許可IPからのみに絞る方法をご紹介します。
2020.07.07

使う機能

ALBのリスナールール機能です。ALBは配下のターゲットリソースにリクエストをルーティングする際に、いろいろな分岐条件を書くことができます。

2020/07/07時点で利用できる条件は以下です。

  • ホストヘッダ
  • パス
  • HTTPヘッダ
  • HTTPリクエストメソッド
  • 文字列のクエリ
  • 送信元IP

ルーティング先としては以下が選択できます。

この条件とルーティング先を組み合わせることで、○○という条件が真のときはXXにルーティングする、みたいな処理を書くことができるわけです。

注意点としては、条件についてはあまり複雑な処理が書けないということです。否定形の条件は書けませんし、正規表現も書けません。

今回実装する内容

リスナールールを以下のように設定します。

  1. パスが/admin/*かつ送信元が許可CIDRの場合、ターゲットグループAに転送
  2. パスが/admin/*の場合、固定レスポンス403を返す
  3. デフォルトアクションでターゲットグループAに転送
  • リスナールールは上から順番に評価されます。ですのでパスが/admin/*かつ送信元が許可CIDRの場合は1で判定終了、ターゲットグループAに転送されます。
  • パスが/admin/*だけれども許可CIDR出ない場合は、1の結果が偽になり、2の判定で真になります。固定レスポンス403が返却されます。
  • それ以外は1,2ともに偽になり、3でターゲットグループAに転送されます。

※ 配信元となるEC2インスタンスは一般ユーザー向けコンテンツも、/admin/*以下の管理者向けコンテンツもどちらも配信すると仮定していますので、1も3も同じターゲットグループにしています。(ターゲットグループA配下にこのEC2がいると仮定します。)

やってみた

リスナールールをいじっていない状態のALB-EC2構成が作成済であるとします。

※今回は検証なので80番のリスナールールを使っていますが、本番で利用する場合はHTTPSを使うと思うので、443番のリスナールールで設定しましょう。

  • ロードバランサーのページに移動します。
  • ALBを選択して、リスナータブ→ルールの表示/編集に遷移します。
  • この状態になっているはずです。
  • 上部プラスボタンから新規ルールを追加します。
  • デフォルトアクションの上に出てきたルールの挿入を押します。
  • 「IF(すべてに一致)」欄にパスと送信元IPの条件を入力します。THEN欄で転送先からターゲットグループを選択します。右上「保存」ボタンクリックで保存します。
  • 2つ目のルールを作成していきます。もう一度プラスボタンを押し、先程作ったルールとデフォルトルールの間の「ルールを挿入」をクリックします。
  • 今度は条件(「IF(すべてに一致)」欄)はパスが/admin/* だけです。THENの方は「固定レスポンスを返す」を選択し以下のように入力します。 右上保存ボタンから保存します。
  • 最終的にこのようになりました。

テスト

ターゲットグループA配下のEC2インスタンスのWebサーバードキュメントルート以下は以下の構成にしています。admin直下のファイルと、パスの判定条件の念の為の確認にもう少し奥まったパスのファイル(admin/hoge/fuga.html)も用意しました。

$ tree /var/www/html
/var/www/html
|-- admin
|   |-- hoge
|   |   `-- fuga.html
|   `-- index.html
`-- index.html

許可IPから

まずはトップページ。当然ながらアクセスできます。

$ curl -v http://ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com
* Rebuilt URL to: http://ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com/
*   Trying xx.xx.xx.xx...
* TCP_NODELAY set
* Connected to ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com (xx.xx.xx.xx) port 80 (#0)
> GET / HTTP/1.1
> Host: ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 07 Jul 2020 08:30:29 GMT
< Content-Type: text/html; charset=UTF-8
< Content-Length: 9
< Connection: keep-alive
< Server: Apache/2.4.43 ()
< Upgrade: h2,h2c
< Last-Modified: Mon, 06 Jul 2020 12:08:02 GMT
< ETag: "9-5a9c4b9f7d574"
< Accept-Ranges: bytes
<
site top
* Connection #0 to host ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com left intact

admin以下にもアクセスしてみます。

$ curl -v http://ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com/admin/
*   Trying xx.xx.xx.xx...
* TCP_NODELAY set
* Connected to ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com (xx.xx.xx.xx) port 80 (#0)
> GET /admin/ HTTP/1.1
> Host: ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 07 Jul 2020 08:32:24 GMT
< Content-Type: text/html; charset=UTF-8
< Content-Length: 17
< Connection: keep-alive
< Server: Apache/2.4.43 ()
< Upgrade: h2,h2c
< Last-Modified: Mon, 06 Jul 2020 11:48:01 GMT
< ETag: "11-5a9c47260cb4e"
< Accept-Ranges: bytes
<
admin/index.html
* Connection #0 to host ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com left intact

もうひとつadmin以下のパスにアクセスしてみます。大丈夫そうです。

$ curl -v http://ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com/admin/hoge/fuga.html
*   Trying xx.xx.xx.xx...
* TCP_NODELAY set
* Connected to ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com (xx.xx.xx.xx) port 80 (#0)
> GET /admin/hoge/fuga.html HTTP/1.1
> Host: ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 07 Jul 2020 08:33:11 GMT
< Content-Type: text/html; charset=UTF-8
< Content-Length: 5
< Connection: keep-alive
< Server: Apache/2.4.43 ()
< Upgrade: h2,h2c
< Last-Modified: Mon, 06 Jul 2020 11:48:34 GMT
< ETag: "5-5a9c4745be19a"
< Accept-Ranges: bytes
<
fuga
* Connection #0 to host ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com left intact

許可IP外から

IPを変えて、許可IP外にして再度アクセスしてみます。

まずトップページ。大丈夫ですね。

$ curl -v http://ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com
* Rebuilt URL to: http://ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com/
*   Trying xx.xx.xx.xx...
* TCP_NODELAY set
* Connected to ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com (xx.xx.xx.xx) port 80 (#0)
> GET / HTTP/1.1
> Host: ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 07 Jul 2020 08:35:13 GMT
< Content-Type: text/html; charset=UTF-8
< Content-Length: 9
< Connection: keep-alive
< Server: Apache/2.4.43 ()
< Upgrade: h2,h2c
< Last-Modified: Mon, 06 Jul 2020 12:08:02 GMT
< ETag: "9-5a9c4b9f7d574"
< Accept-Ranges: bytes
<
site top
* Connection #0 to host ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com left intact

続いてadmin以下。ステータスコードが狙い通り403になっています。一番最後の行が先程入力した「固定レスポンスを返す」のレスポンス本文ですね。

$ curl -v http://ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com/admin/
*   Trying xx.xx.xx.xx...
* TCP_NODELAY set
* Connected to ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com (xx.xx.xx.xx) port 80 (#0)
> GET /admin/ HTTP/1.1
> Host: ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< Server: awselb/2.0
< Date: Tue, 07 Jul 2020 08:36:00 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 13
< Connection: keep-alive
<
* Connection #0 to host ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com left intact
403 forbidden

もう一つのadmin以下のリクエストも正しく403になりました!

$ curl -v http://ip-restrict-test-1234567890.1.elb.amazonaws.com/admin/hoge/fuga.html
*   Trying xx.xx.xx.xx...
* TCP_NODELAY set
* Connected to ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com (xx.xx.xx.xx) port 80 (#0)
> GET /admin/hoge/fuga.html HTTP/1.1
> Host: ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< Server: awselb/2.0
< Date: Tue, 07 Jul 2020 08:38:49 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 13
< Connection: keep-alive
<
* Connection #0 to host ip-restrict-test-1234567890.ap-northeast-1.elb.amazonaws.com left intact
403 forbidden

まとめ

ALBで実現する特定パス以下のIP制限の方法をご紹介しました。やれることはALBでやってしまって、配下のインスタンスの処理量を減らすことで、インスタンスのスケールダウンやスケールインも狙えるかもしれません。もしかしたら Webサーバ撤廃も…?