AWS Verified Access を使った時とパブリックALBを経由した場合のリクエスト情報を比較してみた

2023.05.05

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

いわさです。

AWS Verified Access を使うと、プライベートネットワーク内で稼働する Web アプリケーションに外部からアクセス出来るようになるわけなのですが、よくあるパブリックサブネットの Application Load Balancer を経由する場合とリクエスト情報がどのように変わるのか気になりました。

Verified Access 経由した場合、アプリ側で気をつける点はあるのでしょうか。

結論としては CloudFront などを挟んだ場合と同じで、ALB から見たクライアント IP アドレスが Verified Access の ENI になったり、X-Forwarded-For が一段追加になるくらいで、あとは Verified Access 固有ヘッダーの x-amzn-ava-user-context が付与されるくらいでした。

検証結果をお知らせします。

検証環境作成

今回は次のように、サンプルアプリケーションをターゲットグループで指定するパブリックな Applcation Load Balancer とプライベートな Application Load Balancer を用意し、Verified Access からはプライベートな Application Load Balancer へアクセスさせたいと思います。信頼プロバイダーは Azure AD を使います。

それぞれの ALB のアクセスログを有効化して S3 へ出力します。
後ほどこのログを観察したいと思います。

また、EC2 からも Apache のアクセスログを CloudWatch Logs へ出力させて後ほど観察したいと思います。

上記一式(Verified Access と DNS と ACM 以外)を構築してくれる CloudFormation テンプレートを以下に作成しました。

こちらをデプロイすると Verified Access で必要なリソース情報を取得出来ます。

:
Stack hoge0503app: UPDATE_COMPLETE
  Outputs:
    PrivateSubnetId1: subnet-00358c89cc7b80d73
    VerifiedAccessSecurityGroup: sg-0b9fc57f7f7f9a026
    PrivateLoadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/hoge0503app-internal-alb/5fabf2c7a1b5fbef
    PrivateSubnetId2: subnet-0750904da4dab1ca2

前回と同様に AWS CLI で作成しました。手順は記事のとおりなので割愛します。

また、デプロイ後にパブリックな ALB 用にも DNS レコードを作成します。何でも良いですけど私は Route 53 を使いました。

検証環境にアクセスする

今回は hoge0503public.tak1wa.com がパブリック経路、hoge0503private.tak1wa.com がプライベート(Verified Access)経路となります。

パブリック経路は次のように確認出来ました。

プライベート経路も Azure AD 認証が要求されます。

認証成功後、次のようにアプリケーションへアクセスすることが出来ました。

ログなどを確認してみる

ではアクセスが出来たので出力されたログを確認してみましょう。
その前に IP アドレスを抑えておきたいので ENI を確認します。

プライベート IP アドレスはこんな感じでした。

リソース IPアドレス
EC2 10.0.2.137
Private ALB 10.0.2.159, 10.0.3.175
Public ALB 10.0.0.94, 10.0.1.216
Vertified Access ENI 10.0.2.201, 10.0.3.238
私のパブリック IP 203.0.113.1

※ パブリック IP アドレスはダミーに置き換えています

ALB アクセスログ

まず、次のように CloudFormation で作成された S3 バケットに出力されていることは確認出来ました。

S3 に出力したログなので Athena を使いましょう。
次の公式ドキュメントの手順に従うと必要なテーブルを用意してすぐに使用することが出来ます。

パブリック

まずはパブリック ALB のアクセスログです。
クライアント IP は直接の接続元 IP アドレスですね。

全てのヘッダーとか確認したかったのですが、ALB のアクセスログだとそこまでは確認出来ないようです。

余談ですが、アクセスログを確認してみるとデプロイして数分の間にboaform/admin/formLoginなど目掛けて外部からリクエストが送信されており、ひええーとなりました。

プライベート

プライベート ALB の場合は次のようにクライアント IP が Verified Access の ENI のプライベート IP になっていました。まぁそうだよなという感じです。
先程と比較すると全く攻撃を受けていないこともわかります。

他の情報はパブリックとあまり大差ないですね。
というか比較のための情報量が ALB のアクセスログだと少なすぎたかもしれないです。しまった。

CloudWatch Logs

EC2 の CloudWatch エージェントから CloudWatch Logs へ Apache のアクセスログを転送させているのでそちらも確認してみましょう。
次のようにインスタンス ID のロググループが作成されているはずです。

ただ、こちらもデフォルトのログフォーマットだったために情報量が少なかったです。しまった。

リクエストヘッダーを観察することに

アクセスログを見ても、接続元 IP アドレスが違うという点くらいしかわからなかったので、リクエスト情報を直接確認する作戦に切り替えました。

PHP あたりをインストールしてリクエストヘッダーを出力させます。
SSM セッションマネージャーで接続してインストールします。デフォルトのindex.htmlは動的なものに置き換えます。

[ec2-user@ip-10-0-2-137 bin]$ sudo yum install php
[ec2-user@ip-10-0-2-137 bin]$ sudo rm /var/www/html/index.html
[ec2-user@ip-10-0-2-137 bin]$ sudo vi /var/www/html/index.php
[ec2-user@ip-10-0-2-137 bin]$ sudo systemctl restart httpd.service

index.phpの内容は次を参考にさせて頂き、全ヘッダーを描画するようなものにしました。

パブリック

パブリック ALB の場合は次のようなリクエストヘッダーでした。
まぁ普通です。

X-Forwarded-For: 203.0.113.1
X-Forwarded-Proto: https
X-Forwarded-Port: 443
Host: hoge0503public.tak1wa.com
X-Amzn-Trace-Id: Root=1-645187e1-06c62a9a1ca0929d25a6d104
sec-ch-ua: "Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
sec-fetch-site: none
sec-fetch-mode: navigate
sec-fetch-user: ?1
sec-fetch-dest: document
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9,ja;q=0.8
if-none-match: "5-5fabc1c1e6e1d"
if-modified-since: Tue, 02 May 2023 20:51:56 GMT

プライベート

プライベート ALB の場合は次のようなリクエストヘッダーでした。

以下のハイライト部分が気になった点です。
それ以外はリソースの設定やブラウザの違いによるものなので Verified Access 云々は関係なさそうかなと思いました。

X-Forwarded-For: 203.0.113.1, 10.0.3.238
X-Forwarded-Proto: http
X-Forwarded-Port: 80
Host: hoge0503private.tak1wa.com
X-Amzn-Trace-Id: Self=1-64518862-15e322817b287acf7492b0ec;Root=1-64518862-0ba2121a29ff391b7306cd37
cache-control: max-age=0
sec-ch-ua: "Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
sec-fetch-site: none
sec-fetch-mode: navigate
sec-fetch-user: ?1
sec-fetch-dest: document
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9
if-none-match: "5-5fabc1c1e6e1d"
if-modified-since: Tue, 02 May 2023 20:51:56 GMT
x-amzn-ava-user-context: eyJ0eXAiOiJKV1QiLCJraWQiOiJhODllZDQyZS0xYzg0LTRhOGYtODM1MS1kYTZkNTRjYmMyNzQiLCJhbGciOiJFUzM4NCIsImlzcyISAMPLEHBzOi8vbG9naW4ubWljcm9zb2Z0b25saW5lLmNvbS83MzBmMDI2Yi1mZTZjLTQ1YjQtYjAyMy1iMzBlYWMyNTcxODgvdjIuMCIsImNsaWVudCI6IjQxOTdmYTBkLTBiY2QtNDM5OC1iYWZkLTZhNjhlNjYxYmY5YyIsInNpZ25lciI6ImFybjphd3M6ZWMyOnVzLWVhc3QtMTo1NTA2Njk0SAMPLEg6dmVyaWZpZWQtYWNjZXNzLWluc3RhbmNlL3ZhaS0wODUzZDQzZmNhZjY4NzMzZCIsImV4cCI6MTY4MzA2NTA1MH0.eyJzdWIiOiJUYVZwV09fTkNyTHNsek5kQmhIMmpxeUdtSC1PWFVPb2hpQnRaak9RUFQ0IiwibmFtZSI6InVzZXIxaXdhc2SAMPLEYW1pbHlfbmFtZSI6IuOBu-OBkiIsImdpdmVuX25hbWUiOiLjgYTjgo_jgZUiLCJwaWN0dXJlIjoiaSAMPLE6Ly9ncmFwaC5taWNyb3NvZnQuY29tL3YxLjAvbWUvcGhvdG8vJHZhbHVlIiwiZW1haWwiOiJ1c2VyMUBhYWQudGFrMXdhLmNvbSIsImV4cCI6MTY4MzA2NTA1MCwiaXNzIjoiaHR0cHM6Ly9sb2dpbi5taWNyb3NvZnRvbmxpbmUuY29tLzczMGYwMjZiLWZlNmMtNDSAMPLEMDIzLWIzMGVhYzI1NzE4OC92Mi4wIn0.6bU1N0U5qLaJwHzRJG_4bngSAMPLEnixyKqPjbhxosfwNPL6GyxUw_6gAC9XyR71VLkelp26cDEwpwtq7M8F9g4KmFboDO-mJT9mCLbic725rNI9j8eQu6kl3qAY5d_l
cookie:

X-Forwarded-Forヘッダーは、直接 ALB へアクセスするクライアント である Verified Access の ENI と、エンドユーザーのパブリック IP アドレスで構成されています。
このあたりは CloudFront とか、ALB を多段にするとか、リバースプロキシを増やした場合と同じですね。

Hostヘッダーは Verified Access のアプリケーションドメインが転送されていますね。
Verified Access から内部アプリケーションに HTTPS 通信させる際の SSL 証明書はどうする?と少し考えていたのですが、アプリケーションドメイン(今回だと hoge0503private.tak1wa.com)を使って問題無さそうだということが確認出来ました。

最も特徴的なのは、独自のx-amzn-ava-user-contextヘッダーですね。
次の公式ドキュメントにも記述がありますが、ユーザークレームを含む JWT です。

jwt.io でデコードしてヘッダーとペイロードを確認してみます。

Azure AD から取得されたユーザー情報などが設定されています。
ただし、Azure AD の ID トークンと少し似ていますが、各クレームはよく確認するとちょっと違っていて、署名も Verified Access が独自で行ったものです。
IdP のトークンなどがそのまま転送されているわけではないということを覚えておきたいです。

さいごに

本日は AWS Verified Access を使った時とパブリックALBを経由した場合のリクエスト情報を比較してみました。

クライアント IP アドレスが Verified Access の ENI となるという点以外はリクエスト内容については概ね気にする点は無さそうという感想です。
CloudFront を前段に追加した場合と同じような考慮で Web アプリケーションは動作するのではないでしょうか。

加えてx-amzn-ava-user-contextで IdP 側の情報にも少しアクセス出来るよ、という感じですね。
個人的にはHostヘッダーが期待どおり転送されているのが確認出来て良かったです。Verifed Access の接続先の構成を考えるときの参考になりそうです。