Closed38

ALBでOIDCとかSAMLとかやってみる

ピン留めされたアイテム
inomotoinomoto
  • ALB + OIDCを使い、Google (GCP)のアカウントでの認証ができた
  • Amazon Cognitoで遊べた
  • それらの認証を通した後ろのアプリケーションにて、認証されたユーザのemailを取れた
  • SAMLは個人でノリで遊ぶ感じではなさそうとわかった
inomotoinomoto

これまでアクセス制御系は(楽なので)雑にIP制限したりbasic認証でなんとかしていたが、2022年にもなったのでもうちょっと近代的なやつを試しておく。

置き換え元があくまでIP制限やbasic認証といった簡易なものなので、想定用途もあまり複雑な(対外向けのSSOとかのような)ものは考えない。
かつ、アプリケーションに認証系をゴリっと組み込むのはやりたくないので、ALBなどアプリケーションの外のレイヤでなんとかしたい。

inomotoinomoto

とりあえず適当にALBを作る。
必要な関連リソースとして

  • Security Group (適当にマイIPから80, 443を通すとかでok)
  • Target Group (適当にlambdaターゲットにでもしておき、関数は後から入れるにする)
  • SSL証明書 (80のリスナーでは認証できないのでなんらか必要)

がある。証明書どうするかな...

inomotoinomoto

昔に取った開発用ドメインは手放していたんだった。というわけでGoogle Domainsに行って適当に新規のを作る。

やってみたところ、devドメインはSSL強制されるらしい(chromeなどでそのドメインにアクセスすると非SSLはハネられるらしい)。目的もdevでちょうどいいし、そもそも非SSLならドメイン使う理由がないので全然ok。

inomotoinomoto

ドメインを取ったらACMで適当に証明書を発行&検証。ここまでできたらALB作成できる

inomotoinomoto

ALBのリスナールールを確認すると、認証アクションにはOIDCとCognitoが選択できる。
OIDCはそのままとして、確かCognito経由だとSAMLができた気がする。たしか。

まずはOIDCを試しに行く。

inomotoinomoto

googleのOAuthやつのダイアログを進めると、同意画面のところでUser Typeというのを選べた。いや個人アカウントなので実質選べないけど。

説明を見る限り、Workspace使ってるなら認証できるユーザを組織に絞れるように見える。今回やりたい用途(雑にIP制限するような用途)の代替としてはこれだけでよさそう。

inomotoinomoto

ん?なんかやること間違えてた気がしなくもないが、改めてOAuthクライアントIDを作り直し、jsonを取得まで済み

inomotoinomoto

jsonまで(クライアントID, クライアントシークレットまで)取得できたらALBに戻る。

リスナールールの例のOIDCの設定項目を埋めていくが、前述のクラメソさんとこの記事にあるようにcurl https://accounts.google.com/.well-known/openid-configurationすると情報が返るので、それに従ってコピペしていく。
レスポンスのkeyと設定画面の項目はまぁ見たらわかる。

inomotoinomoto

これらが設定できる&正常系のレスポンス(lambda)の設定ができてると、ALBに貼ったドメインにアクセスすると見慣れたgoogleの認証画面が出て、それを通過するととてもテストな感じのlambdaのレスポンスが返ってくる。

...正常系はかなりあっさり完了。

inomotoinomoto

異常系をやってみたいので、IP制限を外す(443 from anyする)かつ適当な他のgoogleアカウントの元から試す。

inomotoinomoto

異常系も何もworkspaceじゃないからどのアカウントでもよいんだった。
というか、OAuth同意画面で設定したテストユーザの制限が効いてないように見えるんだけどどうなってるのか。

inomotoinomoto

そういえばALBのターゲットグループの先のlambdaを作っていなかったので作る。
一旦はレスポンスが返りさえすれば何でもいいので、何も設定は変えずに適当に作る。

inomotoinomoto

ALBにくっつける場合はレスポンス(lambdaの戻り値)の形式をいい感じにしないとだめらしい。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/services-alb.html

該当ページからそのまま引用。こんな感じ(というかこれそのもの)を返しておけばテストとしてはok

{
    "statusCode": 200,
    "statusDescription": "200 OK",
    "isBase64Encoded": False,
    "headers": {
        "Content-Type": "text/html"
    },
    "body": "<h1>Hello from Lambda!</h1>"
}
inomotoinomoto

OIDCについて、とりあえず認証はできたっちゃできたので、アプリケーション側を見ていく。

inomotoinomoto

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が展開される。

inomotoinomoto

認証したgoogleアカウントの画像とか見えるので、良い感じに情報が入ってそう。
とりあえず、signerとしてALBのARNが入っている(つまりAWSアカウント番号が入っている)ので、レスポンスをコピーして退避したらlambdaはサクッと元に戻す

inomotoinomoto

データは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;
};
inomotoinomoto

よくわからないけど、ALBがそれをリクエストしていないとかなのかもしれない。わからんけど。

inomotoinomoto

ALB側のドキュメントあった(先に読め)
https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/listener-authenticate-users.html

scopeを設定するところがあるようで、リスナールールのOIDCの設定項目の詳細設定を開いたらたしかにあった。デフォルトでは openid になっているのだが、ここを画像のように email にすると、JWTのbody部分に emailemail_verified フィールドが現れた。

inomotoinomoto

ということで、ALBとOIDC(google)を使って認証ができた。emailが取れてれば認可はアプリケーション側で適当に書けば良いと思う。
いまは個人アカウントなので試せていないが、エンタープライズなIdPだと組織内しか通さないとかできるだろうし、それなら認可は何もしないということもできそう。たぶん。

なお、jwtの検証は一応やっておけと各種ドキュメントには書いてある。今回はlambda上で色々実装するのが面倒なのでスキップしているが。
逆に言えば、認証&email読み取りまではリクエストヘッダの読み取りとbase64デコードのみでできている。大変楽である。

inomotoinomoto

ではSAMLもいってみますかー。
まずは下調べから まずはcognitoを普通に試すところから(になった)

inomotoinomoto

GCPでのSAMLはなんかありそう(調べたらなんとかできそう)に見えるので、先にcognitoをなんらか使ってみる。

AWSコンソールでcognitoの画面に行くと、新UIがあるとのことでそっちへ。
そこからひとまずユーザプールを作るが、cognitoナニモワカラナイの状態だと全然太刀打ちできなかった。
軽く調査すると、cognitoでなにかやってみてる記事があり、どうも旧UIだと適当なデフォルト設定で雑にユーザプールを作ることができるようだ。
https://oji-cloud.net/2021/11/08/post-6634/

というわけで旧UIに移り、適当にプールを作る。

inomotoinomoto

何も編集していないがひとまずプールを作り、ALBのリスナールールの認証の設定を試みる。
先程までOIDCで設定していたところをcognitoに変更し、作成したプールを選ぶと

このユーザープールにはドメインがありません。Cognito コンソールを開き、ドメインを追加します。

とメッセージが出るので、なにやらドメイン設定が要るらしい。ついでに、その下にアプリクライアントの選択があるので、そっちも要るらしい。

inomotoinomoto

cognitoの設定画面に戻り、作成したプールを開き、(旧UIの)サイドバーを見ると アプリの統合 > ドメイン名 とそれっぽいのがあるので適当に設定する。
何が起こるかイマイチわからないままだが、ひとまずAmazon Cognitoドメインとして適当な名前をつけて保存。

inomotoinomoto

「アプリクライアント」については、ユーザプールを作成した直後に出てる全般設定のビューの下の方にアプリクライアントを作る的なボタンがあるので押し、適当な名前をつけて作成。

inomotoinomoto

その後そのままALBに設定して保存...しようとするとエラーになる(ユーザプールにOAuthを設定しろ的な)。

そこで参考ページ(下記、再掲)を見ると、「アプリクライアントを設定する」のセクションで色々設定されているので、その通りに設定する。
https://oji-cloud.net/2021/11/08/post-6634/

そして保存すると、ALB側も設定が保存できる。

inomotoinomoto

そして満を持してALBに設定したドメインにアクセスするとこうなる

どうもcognitoデフォルトのログイン画面っぽい。そしてドメインを見ると、 アプリの統合 > ドメイン名 というメニューからよくわからんまま設定したドメインになっている。これだったのか。

現時点ではSAMLとかは設定してない生cognitoなので、ユーザIDやパスワードは無い。しかしなにやらsingupがあるので押してみる。

inomotoinomoto

なんか普通にフォームが出てきてsignupできそう。email入れたらメール宛に認証コード飛んでくるところまでやってくれる。
認証コード入れたらsignupができたようで、見慣れたテスト用lambdaのレスポンスが出る。

ところでサインアップフォーム、usernameを入れた途端passwordのバリデーションエラーが山盛り出てくる。まだpassword欄触っとらんわw不親切かw

inomotoinomoto

ユーザプールを見ると、signupしたユーザが表示されている。なるほど。
そういえば最初に見たプールの作成ステップの中で、ユーザに自己サインアップを許すか、みたいなのを見た気がする。デフォルトだと許可になっていてこうなるのか。

inomotoinomoto

なおこのcognito認証を通した場合でも、オリジンへのリクエストヘッダの x-amzn-oidc-data にOIDCのときと同じようにJWTなデータが入っている。つまり全く同じコードで認証情報が取れる(取れてた)。

cognito、食わず嫌いしてたけど社内向けとかの雑なログイン認証したいときは便利かも。

inomotoinomoto

生のcognitoはひとまず遊んだので、SAMLをやってみたい。

cognitoの画面からフェデレーテッドIdPって感じのものを作る。

inomotoinomoto

(そろそろ新UIを使ってあげよう)
ユーザプールのサインインエクスペリエンスというタブのところにそれっぽいのがあるのでここから。

inomotoinomoto

いやこれなんか違くない?googleじゃないIdPを使ってGCP上のアプリにサインインする側っぽい話に見える、わからんけど

このスクラップは2022/04/16にクローズされました