ALBでOIDCとかSAMLとかやってみる
- ALB + OIDCを使い、Google (GCP)のアカウントでの認証ができた
- Amazon Cognitoで遊べた
- それらの認証を通した後ろのアプリケーションにて、認証されたユーザのemailを取れた
- SAMLは個人でノリで遊ぶ感じではなさそうとわかった
これまでアクセス制御系は(楽なので)雑にIP制限したりbasic認証でなんとかしていたが、2022年にもなったのでもうちょっと近代的なやつを試しておく。
置き換え元があくまでIP制限やbasic認証といった簡易なものなので、想定用途もあまり複雑な(対外向けのSSOとかのような)ものは考えない。
かつ、アプリケーションに認証系をゴリっと組み込むのはやりたくないので、ALBなどアプリケーションの外のレイヤでなんとかしたい。
とりあえず適当にALBを作る。
必要な関連リソースとして
- Security Group (適当にマイIPから80, 443を通すとかでok)
- Target Group (適当にlambdaターゲットにでもしておき、関数は後から入れるにする)
- SSL証明書 (80のリスナーでは認証できないのでなんらか必要)
がある。証明書どうするかな...
ALBのリスナールールを確認すると、認証アクションにはOIDCとCognitoが選択できる。
OIDCはそのままとして、確かCognito経由だとSAMLができた気がする。たしか。
まずはOIDCを試しに行く。
適当に使えるアカウント系のはGCPがあるので、それで試す。
とりあえず例によってクラメソさんとこの記事を参考に。ちと古いけど雰囲気はわかるやろ。
googleのOAuthやつのダイアログを進めると、同意画面のところでUser Typeというのを選べた。いや個人アカウントなので実質選べないけど。
説明を見る限り、Workspace使ってるなら認証できるユーザを組織に絞れるように見える。今回やりたい用途(雑にIP制限するような用途)の代替としてはこれだけでよさそう。
ん?なんかやること間違えてた気がしなくもないが、改めてOAuthクライアントIDを作り直し、jsonを取得まで済み
jsonまで(クライアントID, クライアントシークレットまで)取得できたらALBに戻る。
リスナールールの例のOIDCの設定項目を埋めていくが、前述のクラメソさんとこの記事にあるようにcurl https://accounts.google.com/.well-known/openid-configuration
すると情報が返るので、それに従ってコピペしていく。
レスポンスのkeyと設定画面の項目はまぁ見たらわかる。
これらが設定できる&正常系のレスポンス(lambda)の設定ができてると、ALBに貼ったドメインにアクセスすると見慣れたgoogleの認証画面が出て、それを通過するととてもテストな感じのlambdaのレスポンスが返ってくる。
...正常系はかなりあっさり完了。
異常系をやってみたいので、IP制限を外す(443 from anyする)かつ適当な他のgoogleアカウントの元から試す。
異常系も何もworkspaceじゃないからどのアカウントでもよいんだった。
というか、OAuth同意画面で設定したテストユーザの制限が効いてないように見えるんだけどどうなってるのか。
そういえばALBのターゲットグループの先のlambdaを作っていなかったので作る。
一旦はレスポンスが返りさえすれば何でもいいので、何も設定は変えずに適当に作る。
ALBにくっつける場合はレスポンス(lambdaの戻り値)の形式をいい感じにしないとだめらしい。
該当ページからそのまま引用。こんな感じ(というかこれそのもの)を返しておけばテストとしてはok
{
"statusCode": 200,
"statusDescription": "200 OK",
"isBase64Encoded": False,
"headers": {
"Content-Type": "text/html"
},
"body": "<h1>Hello from Lambda!</h1>"
}
OIDCについて、とりあえず認証はできたっちゃできたので、アプリケーション側を見ていく。
ALBから転送されてくるリクエストのヘッダに X-Amzn-Oidc-Data
というのがあり、そこにJWTのトークンが入るらしい。とりあえず取得してみる。
非常に雑であるが、こんな感じでresponseとして吐いてみる
const { Buffer } = require("buffer");
exports.handler = async (event) => {
const d = event.headers["x-amzn-oidc-data"];
const body = Buffer.from(d, "base64").toString();
const response = {
statusCode: 200,
statusDescription: "200 OK",
isBase64Encoded: false,
headers: {
"Content-Type": "text/html",
},
body: body,
};
return response;
};
ちなみに該当ヘッダの名前は小文字だった。大文字だったりCamelCaseで試したけどだめだったのでeventをdumpしてみたらまさかの小文字。
試すと、まぁなんかjsonが展開される。
認証したgoogleアカウントの画像とか見えるので、良い感じに情報が入ってそう。
とりあえず、signerとしてALBのARNが入っている(つまりAWSアカウント番号が入っている)ので、レスポンスをコピーして退避したらlambdaはサクッと元に戻す
データはcloudwatch logsで見るようにする
const { Buffer } = require("buffer");
exports.handler = async (event) => {
const d = event.headers["x-amzn-oidc-data"];
if (d) console.log(Buffer.from(d, "base64").toString())
const body = 'test';
const response = {
statusCode: 200,
statusDescription: "200 OK",
isBase64Encoded: false,
headers: {
"Content-Type": "text/html",
},
body: body,
};
return response;
};
jwtの中身のドキュメントと思しきものがこれ。
確かに sub
は含まれていてとても長い数字文字列が入っているしpictureもあるが、一番欲しいemailがない。
GCP側のOAuth同意画面で auth/userinfo.email
を入れてみても来ない。
よくわからないけど、ALBがそれをリクエストしていないとかなのかもしれない。わからんけど。
ALB側のドキュメントあった(先に読め)
scopeを設定するところがあるようで、リスナールールのOIDCの設定項目の詳細設定を開いたらたしかにあった。デフォルトでは openid
になっているのだが、ここを画像のように email
にすると、JWTのbody部分に email
と email_verified
フィールドが現れた。
ということで、ALBとOIDC(google)を使って認証ができた。emailが取れてれば認可はアプリケーション側で適当に書けば良いと思う。
いまは個人アカウントなので試せていないが、エンタープライズなIdPだと組織内しか通さないとかできるだろうし、それなら認可は何もしないということもできそう。たぶん。
なお、jwtの検証は一応やっておけと各種ドキュメントには書いてある。今回はlambda上で色々実装するのが面倒なのでスキップしているが。
逆に言えば、認証&email読み取りまではリクエストヘッダの読み取りとbase64デコードのみでできている。大変楽である。
ではSAMLもいってみますかー。
まずは下調べから まずはcognitoを普通に試すところから(になった)
GCPでのSAMLはなんかありそう(調べたらなんとかできそう)に見えるので、先にcognitoをなんらか使ってみる。
AWSコンソールでcognitoの画面に行くと、新UIがあるとのことでそっちへ。
そこからひとまずユーザプールを作るが、cognitoナニモワカラナイの状態だと全然太刀打ちできなかった。
軽く調査すると、cognitoでなにかやってみてる記事があり、どうも旧UIだと適当なデフォルト設定で雑にユーザプールを作ることができるようだ。
というわけで旧UIに移り、適当にプールを作る。
何も編集していないがひとまずプールを作り、ALBのリスナールールの認証の設定を試みる。
先程までOIDCで設定していたところをcognitoに変更し、作成したプールを選ぶと
このユーザープールにはドメインがありません。Cognito コンソールを開き、ドメインを追加します。
とメッセージが出るので、なにやらドメイン設定が要るらしい。ついでに、その下にアプリクライアントの選択があるので、そっちも要るらしい。
cognitoの設定画面に戻り、作成したプールを開き、(旧UIの)サイドバーを見ると アプリの統合 > ドメイン名
とそれっぽいのがあるので適当に設定する。
何が起こるかイマイチわからないままだが、ひとまずAmazon Cognitoドメインとして適当な名前をつけて保存。
「アプリクライアント」については、ユーザプールを作成した直後に出てる全般設定のビューの下の方にアプリクライアントを作る的なボタンがあるので押し、適当な名前をつけて作成。
その後そのままALBに設定して保存...しようとするとエラーになる(ユーザプールにOAuthを設定しろ的な)。
そこで参考ページ(下記、再掲)を見ると、「アプリクライアントを設定する」のセクションで色々設定されているので、その通りに設定する。
そして保存すると、ALB側も設定が保存できる。
そして満を持してALBに設定したドメインにアクセスするとこうなる
どうもcognitoデフォルトのログイン画面っぽい。そしてドメインを見ると、 アプリの統合 > ドメイン名
というメニューからよくわからんまま設定したドメインになっている。これだったのか。
現時点ではSAMLとかは設定してない生cognitoなので、ユーザIDやパスワードは無い。しかしなにやらsingupがあるので押してみる。
なんか普通にフォームが出てきてsignupできそう。email入れたらメール宛に認証コード飛んでくるところまでやってくれる。
認証コード入れたらsignupができたようで、見慣れたテスト用lambdaのレスポンスが出る。
ところでサインアップフォーム、usernameを入れた途端passwordのバリデーションエラーが山盛り出てくる。まだpassword欄触っとらんわw不親切かw
ユーザプールを見ると、signupしたユーザが表示されている。なるほど。
そういえば最初に見たプールの作成ステップの中で、ユーザに自己サインアップを許すか、みたいなのを見た気がする。デフォルトだと許可になっていてこうなるのか。
なおこのcognito認証を通した場合でも、オリジンへのリクエストヘッダの x-amzn-oidc-data
にOIDCのときと同じようにJWTなデータが入っている。つまり全く同じコードで認証情報が取れる(取れてた)。
cognito、食わず嫌いしてたけど社内向けとかの雑なログイン認証したいときは便利かも。
生のcognitoはひとまず遊んだので、SAMLをやってみたい。
cognitoの画面からフェデレーテッドIdPって感じのものを作る。