ゼロトラストプロキシをどこまでマネージドで作れるか検討した話
この記事は MIXI DEVELOPERS Advent Calendar 2024 の 6 日目の記事です。
え?なんで 13 日に投稿しているのかって?色々あったんですよ。ええ。
というわけで、ここ最近作っているゼロトラストプロキシの話です。
最近、社内で利用している様々なウェブサイトに対して、ページの閲覧を制限させたいという話が出てきました。条件としては色々あり、アクセスするユーザーの所属組織、閲覧するページ、その他の条件などに応じて、ページの閲覧許可を設定したいとのことです。
ページごとに閲覧を制限したいとなると、リクエストを仲介して L7 での制御をしたくなります。さらに追加で与えられた要件を盛り込むと、何か 1 つのサービスを利用して実現することはなかなか難しいことがわかりました。
今回は、AWS や Google Cloud を利用したパターンを中心に、どこまで L7 ゼロトラストプロキシが実現できそうかを調査したので、ここで紹介します。
要件
今回のシステムの必須の要件は以下のとおりです。
- リクエストパスごとのアクセス制御
- ユーザーをグルーピングしてのアクセス制御
- 複数の IdP に対応
- 外部 IdP に対応
- プロキシからの出口 IP アドレスを固定
- アクセスログの記録
- プロトコルは HTTP が使えれば良い
- 認証時に MFA
プロキシからの出口 IP アドレスを固定したい理由は、アクセス制限をしたい Web サイトが IP アドレスによるアクセス制限しか行えない場合などがあるためです。
また、必須ではないですが、以下の要件も満たせると良いです。
- 運用負荷が高くない
- コストが高すぎない
- 既存のサーバーの改修をほとんどなしに導入可能
これらを踏まえて、クラウドを利用したり、既存のゼロトラストプロキシ系のサービスの契約を検討しました。検討したのは以下のとおりです。
- AWS のサービスを利用したパターン 3 種
- Google Cloud のサービスを利用したパターン 3 種
- Akamai EAA
- Tailscale などの Wireguard などの技術を利用した分散型VPN
A. AWS で構築するパターン
A-1. ALB + Cognito + 複数の Cognito User Pool + SES + SNS + VPC + NAT Gateway
アクセスを制限したいパスごとに Cognito User Pool を分けて、パスごとの認証できるユーザを変えるという方法です。
この場合、ユーザーのアクセスしたいパスごとに User Pool が増えてしまうため、User Pool が冗長で管理コストが高くなるという問題があります。
さらに、Cognito 自体の問題として、Cognito がサンドボックスだと、SMS で MFA をしようとすると 10 個までしか電話番号を登録できなかったり、サンドボックスを辞めようとすると AWS SMS / SNS の組み合わせが必要になり管理サービスが増えるなど、そもそも Cognito 自体の運用が大変ということも挙げられます。
A-2. ALB + Cognito + Cognito の Lambda Trigger + SES + SNS + VPC + NAT Gateway
Cognito の Lambda Trigger を使用して、ユーザーがアクセスしたリクエストパスに応じてアクセスの許認可を行う方法も考えてみました。
この場合、Lambda を実行できるのはログイン直後のみで、一度ログインしてしまったら Lambda を実行させることができないという問題があります。そのため、ログイン直後はどのパスでもアクセスできてしまい、認可処理としては今回のケースは不適当であると考えました。
A-3. ALB + Cognito + SES + SNS + VPC + Lambda + NAT Gateway
この場合は、パスベースの認可処理を ALB 背後に配置した Lambda で行うパターンです。Lambda 内部、もしくは DB などと接続してアクセスを許可するユーザーとパスのパターンを取得し、Lambda でリクエストの度にユーザーとリクエストパスの組み合わせを検証するという形になります。
これは前述の問題をある程度解消できますが、最初に出た Cognito 自体の運用が大変というところは変わっていないため、他のサービスと比較するのが良さそうです。
その他の Cognito を使用するパターン
さらに、上記の 2 つのパターンの派生で以下のようなパターンもありますが、AWS で認証の処理をマネージドで行おうとすると、Cognito を利用する前提になってしまうので微妙なところです。
- CloudFront + Lambda@Edge + Cognito
- Lambda@Edge で Cognito とやりとりして、ログイン処理をさせる方法です
-
この場合、CloudFront は VPC 内に作れないので、出口 IP を固定することができません- このシステムを検討している際はなかった話として、CloudFront と Internal ALB と通信させる方法が出てきました。この方法を使えば一応出口 IP は固定できそうです。(CloudFront 以外に ALB も必要になりますが)
- いずれにしても Cognito とあわせてシステムがより複雑になるのでちょっとつらそう
- API Gateway + Cognito + Lambda
- ALB の代わりに API Gateway にしたパターンです。 ALB の時と同様の問題があります。
B. Google Cloud で構築するパターン
B-1. Cloud Load Balancing + Identity Aware Proxy (IAP) + Cloud NAT
Google Cloud で AWS と同様の環境を考えるとすると、まずは IAP を利用した認証が挙がります。この場合は、IAM でパスベースの認証もできるし、Cloud NAT により出口 IP の固定も可能となり、システムもシンプルで、通常はこちらを採用するのが良いでしょう。
ただし、この場合の唯一の欠点として、外部 IdP を使えないという問題がありました。
今回のシステムでは、複数の IdP を利用し、かつ外部 IdP の利用も必須だったため、この方法は採用できませんでした。
B-2. Cloud Load Balancing + Identity Aware Proxy (外部IDP連携) + Identity Platform + Cloud NAT + Cloud Run Functions
B-1 に対して、IAP と Identity Platform を組み合わせると外部 IdP を利用することが可能になります。
ただし、この場合は IAP の IAM 認証で実現していたパスベースの認可処理が使えなくなるため、認可処理は自前で実装する必要があります。そのため、Load Balancing の背後に Cloud Run Functions を噛ませて、そこで認可処理を実現することで、すべての要求を満たすようにしました。
この場合は、認可処理は自前で実装するので、開発のコストが上がります。
ただし、一応利点もあり、認可処理を自前で実装できるということは、パス以外の要素も利用した認可処理も可能になります。
B-3. Cloud Load Balancing + Identity Aware Proxy (IAM) + Workforce Identity + Cloud NAT
IAP を IAM 認証で使用した場合は、Workforce Identity というのを組み合わせる方法もあります。
Workforce Identity を利用すると外部 IdP を使用できるためうまくいきそうですが、この場合は Workforce Identity に設定した外部 IdP のみしか使えず、複数の IdP の利用が難しいという問題があります。
そのため、この場合は要件を満たせませんでした。
C. Akamai EAA Connector + AWS VPC + EC2/ECS/EKS + NAT Gateway
https://techdocs.akamai.com/eaa/docs/welcome-eaa より抜粋
弊社では Akamai EAA (Enterprise Application Access) を一部のプロジェクトで導入しており、これの Connector というものを利用するという手があります。
Connector は EC2 などの環境にインストールして使うもので、これによりユーザーのリクエストを Akamai のクラウド環境を通って Connector に入り、Connector 経由で別のサーバーにアクセスすることが可能になります。
この場合、認証は Akamai の方でやってもらえますし、パスベースでの認可処理も対応しています。
ただ、これの一番の問題はフルマネージドではないという点です。Connector をインストールしたサーバーの運用が必要になるため、今回は採用を見送りました。
D. Tailscale Connector
https://tailscale.com/blog/saas より抜粋
近年 VPN の代わりとして台頭してきた Tailscale ですが、こちらはプロトコルが HTTP に限定されずに使うことができます。
HTTP に限定されずに使えるということは、裏を返せば L7 でのアクセス制御ができないということです。つまりパスベースの制御ができませんので、こちらも要件を満たせませんでした。
また、他のサービスと比較して料金が高額になるというのもネックでした。
まとめ
上記の検討によって、何か 1 つないしは少数のサービスを組み合わせるだけで、いい感じにすべての要件を満たす L7 ゼロトラストプロキシを作ることは難しそうなことがわかりました。
そのため、このプロジェクトでは B-2 のパターンを採用することにしました。こちらであれば一定の認可処理の変更もできつつ、ある程度はマネージドサービスを利用して構築できるため、運用負荷を減らせるのかなと考えています。
Discussion