💭

PomeriumをECS上に構築して社内ツールProxyを実現する

2021/09/07に公開

概要

テレワークが良い感じに流行ってきて、社内のユーザしか繋げないような社内ツールのアクセス管理ってみなさんどうされてる感じでしょうか。
多少大きな会社とかだと、業務ネットワークから出て行くIPアドレスが固定されていて、そのアドレスでしか繋げないようにして、アクセス制限をよろしくやったりすると思います。ただ、テレワークで家とか公衆無線LANみたいないろんな経路からアクセスするとなるとそうはいかなくて、VPNを構築したり、場合によっては諦めてBasic認証で開けてしまうとかあると思います。

問題もあってIPアドレス制限だと経路が複数あると管理が大変、VPNだと接続をするのが面倒、Basic認証は認証情報使い回しがち...みたいなちょっとずつ困ったなぁみたいなことあると思います。

このとき、GCPだったりするとサービスを構築しているとCloudIAPというサービスで良い感じに認証してProxyもしてくれたりするんですが、AWSだとそういうのがないので自分で頑張るしかありません。そういうときのためにPomeriumをご紹介します。

Pomeriumとは

https://www.pomerium.com/

PomeriumはIdPと連携してユーザ認証をし、ポリシーに従って認可も良い感じにしながら通信をProxyしてくれる便利ツールです。OSSとして開発されていて自分で運用することも可能ですし、公式で有料SaaSとして提供されています。

日本語記事があんまり無い...って書こうと思っていましたが、少し前に記事が出てたのでこちらも併せて読むとどんどんわかってくると思います。
https://blog.icttoracon.net/2021/03/04/pomeriumについて/

連携可能なIdPとしては、Auth0やG Suite、GitHubなどにも対応しており自分の組織にあったIdPを利用すると良さそうです。今回はG Suiteを用いた場合の説明をします。

G Suiteと連携をする

公式ドキュメントはこちら: https://www.pomerium.com/docs/identity-providers/google.html

トークンはGCPから取得します。利用したい組織のGCP Consoleを開いて、プロジェクトを追加します。

まずはOAuth同意画面を構成する必要があるので、プロジェクトの「APIとサービス」から「認証情報」を選び「同意画面を設定」を選びます。

社内ユーザのみのアクセスで十分なためここではUser Typeに内部を設定します。
その後、アプリ名の設定やサポートメールの設定が要求されます。アプリ名は認証する際のアプリ名として表示されるのであんまり変な名前にはしない方が良いでしょう。

作成したプロジェクトのAPIとサービスから認証情報を選び、認証情報を作成から、OAuthクライアントIDを選択します。

アプリケーションの種類はWebアプリケーションを選択します。
この設定画面で承認済みのリダイレクトURIを設定する必要があります。
リダイレクト先については https://<今回使いたいドメイン>/oauth2/callback とする必要があります。

作成後、クライアントIDとクライアントシークレットが発行されるので、メモしておきます。
OAuth2.0クライアントIDのリストからも確認できます。

次にユーザ情報を閲覧できる権限を持ったサービスアカウントを追加します。
IAMと管理からサービスアカウントを選択し、サービスアカウントを作成を選択します。
適当にアカウント名を決めて、省略可となっている部分は省略してOKです。

作成が完了すると以下のようになります。

このサービスアカウントを選択し、キーを発行します。

キーのタイプにはJSONを選択し、作成するとJSONが一つダウンロードされます。
このJSONをエディタで開き、直接要素追加が必要です。

{
  "type": "service_account",
  "project_id": "pomerium-redacted",
  "private_key_id": "e07f7c93870c7e03f883560ecd8fd0f4d27b0081",
  "private_key": "-----BEGIN PRIVATE KEY-----\\n-----END PRIVATE KEY-----\n",
  "client_email": "redacted@pomerium-redacted.iam.gserviceaccount.com",
  "client_id": "101215990458000334387",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/redacted%40pomerium-redacted.iam.gserviceaccount.com",
+  "impersonate_user": "user@pomerium.com"
}

公式のドキュメントの物を引用します。ここで指定されている impersonate_user はなんでもよいらしく、たとえば user@<あなたのG Suiteドメイン> とかでも大丈夫です。保存しておきましょう。
このJSONは後ほど利用するので、大切に保管してください。秘密鍵なども入っているのでうっかりどこぞにcommitしないように気をつけましょう。

サービスアカウント作成後はサービスアカウント一覧から作成したサービスアカウントを選択し、ドメイン全体の委任を有効にするようにします。

チェックボックスにチェックを入れると、クライアントIDが発行されるので、これを控えておきます(OAuthのクライアントIDと混同しないように注意、以下サービスアカウントクライアントIDとします)。

次にG SuiteのAdmin Consoleにログインします。
左メニューのセキュリティ、APIの制御を選択し、画面内のドメイン全体の委任を管理を選択します。
その後、APIクライアントを新しく追加します。
この際に、先ほど控えたはずのサービスアカウントクライアントIDが要求されるので入力します。
OAuthスコープ(カンマ区切り)[1]と書かれたところに、 https://www.googleapis.com/auth/admin.directory.group.readonly と入力すると、さらにテキストボックスが出るので、二つ目に https://www.googleapis.com/auth/admin.directory.user.readonly と入力します。

承認をすれば準備は完了です。アプリケーションのデプロイに入りましょう。

AWSへのデプロイ

まずはデプロイするにあたり構成を説明します。

実行環境としてはAWS ECS上に構築します。理由としては業務でECS Fargateを基本的に利用しているので、同じような感じでできたらなという理由なだけです。
最初に紹介した構築記事や公式のドキュメントではk8sでの方法などが紹介されています。k8sでやる場合にはそちらを参考にしてください。
Pomeriumは公式でDocker Imageが提供されており、環境変数に設定項目を渡すことでそのまま動作させることができます。

本来であれば、Pomeriumにアクセスする際のSSL証明書はPomeriumの仕組みでLet's Encryptを利用して良い感じに発行したりなんとかしているのですが、今回はACMを使って証明書を発行し、ALBでSSL終端を行うようにします。この部分については構築当時の試行錯誤の結果、こうなってしまったので、今後の課題としてできればNLBを使ってFargate側でSSL終端をするような設定にしたいと思ってます。


例として今回は https://*.dev.example.com 以下に様々なエンドポイントを置いていこうと思います。
G Suiteの認証用コールバックURIは、 https://auth.dev.example.com/oauth2/callback とします。

Route53などで、 *.dev.example.com のエイリアスにALBを指定し、ALBではHTTPSのアクセス許可し、ACMで *.dev.example.com の証明書発行を行っておきます。この辺は省略します。

pomeriumのタスク定義

  • コンテナイメージ: pomerium/pomerium:v0.15.2 (2021/9/7現在最新)
  • ポートマッピング: 443:tcp

環境変数

Key Value 備考
AUTHENTICATE_SERVICE_URL https://auth.dev.example.com
IDP_PROVIDER google
INSECURE_SERVER true SSL終端はALBで行うのでPomerium自体は平文で待ち受けられるようにする
POLICY 後述
COOKEY_SECRET `head -c32 /dev/urandom base64` などで事前に生成する
IDP_CLIENT_ID OAuthクライアントID
IDP_CLIENT_SECRET OAuthクライアントシークレット
IDP_SERVICE_ACCOUNT 事前に保存したサービスアカウントのJSONをbase64でエンコードする
SHARED_SECRET COOKEY_SECRETと同じく `head -c32 /dev/urandom base64` で生成

POLICY にはPomeriumにProxyして欲しいエンドポイントの設定や、どのアカウントならアクセスして良いかなどの情報を書いたyamlをbase64エンコードして差し込みます。

- from: https://httpbin.dev.example.com
  to: https://httpbin.org
  allowed_domains:
    - example.com

この場合だと、 https://httpbin.dev.example.com へのアクセスは https://httpbin.org/ にProxyするというような設定になります。
to がhttpでの待ち受けをしていてもアクセス可能で、様々な事情でHTTPS化できていないツールもこれによってHTTPS化できると思うのでうまいことやりましょう。

大抵の場合はこれだけで十分要件に合うと思いますが、アクセス可能なユーザの制限、細かなその他の設定方法は公式を確認してください。 https://www.pomerium.com/reference/#policy


あとはこのタスクを立ち上げて、 https://httpbin.dev.example.com に接続すると、よくみるGoogle Accountでのログインを要求されるので、今回連携した組織のアカウントでログインすれば無事Proxyされます。

お手持ちの社内ツールにアクセスしたければ、今回立ち上げたECS Taskからアクセスできるように適切にSecurityGroupを設定したり、NAT InstanceでIP固定したり[2]して、Policyを書けばアクセスできるようになると思います。
この際、Policyの to: に書くべき接続先はPomeriumから見たアクセス先なので、アドレスの指定などには注意してください。

おつかれさまでした。

落穂拾い、今後の展望、課題

ポータルサイト的な存在

pomeriumにはいわゆるポータルサイト的なのがありません。自分で構築する必要があります。たくさんアクセス先が出てきたときに迷子になってしまいます。私の場合、pomerium containerと一緒にnginx containerを立てて、そこでHTMLを返すようにしてポータルサイトとしてうごかすようにしています。

このnginx containerもPomerium経由でアクセスすればokです。

Proxyしているユーザ情報の取得

事前に設定をすれば、Proxyする際のHTTP Headerにどのユーザが認証してやってきてるかの情報を乗せることが可能です。

具体的な設定方法としては https://www.pomerium.io/docs/topics/getting-users-identity.html を参考にしつつ説明すると

HTTP Headerに乗る際にJWTになってやってくるので、JWTの署名と検証をするための秘密鍵と公開鍵を生成します。

$ openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem
$ openssl req -x509 -new -key ec_private.pem -days 1000000 -out ec_public.pem -subj "/CN=unused"

この ec_private.pem をbase64エンコードしてコンテナの環境変数の SIGNING_KEY に渡すことで設定は完了です。
加えてPolicyの設定の際に pass_identity_headers: true を追加します。

- from: https://httpbin.dev.example.com
  to: https://httpbin.org
  allowed_domains:
    - example.com
  pass_identity_headers: true  # これ

するとアクセスの際のHTTP Headerに X-Pomerium-Jwt-Assertion というのが乗っており、これを先ほど生成した公開鍵の ec_public.pem で検証した上で問題なければ、emailアドレスの取得などができますので、これを利用することでアプリケーション側でなんらかの処理がしたいときにも便利です。
たとえば先に説明したポータルサイトの表示をユーザによって変えるみたいなこともできますね。

実はTCP通信もProxyできる

今回は様々な事情でALBで通信しているので、HTTPSしか通りません。ですがPomerium自体はTCP通信のProxyもサポートしています。

https://www.pomerium.com/docs/topics/tcp-support.html

TCP Proxyならば公式の例示にもあるように、開発用RedisのProxyとして使うとか、開発用MySQLにつなぐとか、可能ですしなんならsshのProxyCommandでのProxyとしても利用できるらしく、いわゆる踏み台サーバも不要になるかもしれません。

SSL証明書もLet's Encryptで良い感じになるのでNLBを使うことでここはうまく動くようになると思います。今回記事を書くに当たって色々調べているうちにできそうな気がしてきたので、そのうちなんとかしたいと思います。

ServiceModeについて

https://www.pomerium.com/docs/topics/production-deployment.html

PomeriumはServiceModeというものがあり、All in Oneな起動モードとコンポーネントごとの分散起動モードがあります。

実際に大企業で大人数がアクセスしてくるみたいな環境下だと間違いなく通信が詰まってしまいます(実際テレワークが加速してVPNサーバが大変になるみたいな話はありました)。

このときには認証・認可を行う部分やProxy部分などを個別に立ち上げてそれぞれで良い感じにロードバランシングできる仕組みがあるらしいです。今回の要件では限られたチームでの導入だったので、全社的な導入のチャンスがある人は是非構築して知見を共有していただけるとうれしいです。

終わりに

ざっくり一年ぐらい前に社内ツール向けに整備してみて、特に何のメンテもなくうまいことずっと動いているのでかなり安定していてめでたいなと思います。
ECSで構築したこともあって、バージョンアップもDocker Imageのバージョンを変えるだけで済み、かなり楽に運用ができています。

今回この記事を書くのにドキュメントをちゃんと読み直したのですが、構築当時から色々進化しているので、色々試していこうかなと思いました。

脚注
  1. カンマ区切りと書いてあるが、説明の通り一つ入れたらさらにテキストボックスが出てくるので別にカンマで区切らなくても良い。なんだろうなこれは。 ↩︎

  2. 結局IPアドレス制限か!というのは勘弁してください。まあどうしてもそういう状況ってやっぱりあるじゃないですか。少なくとも管理するべきIPアドレスは減るのでマシかな...... ↩︎

Discussion