Keycloakを使ってAWSへサインインする

3番煎じくらいのネタですが、SAMLを使ったSSOを学習するにあたってKeycloakを使ってAWSへのサインインをSSO化するのがちょうど良かったため、自分向けに備忘録としてアウトプット。

## 構成
クライアント：WindowsPC
IdP：WindowsPC上のWSLで起動したkeycloakのコンテナ
SP：AWS

## WSLでkeycloakを起動する

IdPとなるKeycloakは今回はローカルで起動します。当方の環境はWindowsPCなので、WSL上のDockerコンテナで起動します。以下のコマンドを使用しました。

```bash
 docker run -d -p 8080:8080 \
> -e KEYCLOAK_ADMIN=admin \
> -e KEYCLOAK_ADMIN_PASSWORD=password \
> --name keycloak quay.io/keycloak/keycloak start-dev
 docker ps
CONTAINER ID   IMAGE                                 COMMAND                  CREATED          STATUS          PORTS                                                NAMES
2cab731db4e9   quay.io/keycloak/keycloak             "/opt/keycloak/bin/k…"   28 seconds ago   Up 26 seconds   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 8443/tcp
```

バージョンを指定せずPullしたのですが、Keycloakのバージョンは「23.0.7」でした。数年前に触ったときは16系だったので、やはり開発が活発ですね。

## Keycloakの初回ログイン(Keycloak上の作業)

- ブラウザからログイン画面(<http://localhost:8080/admin>)へアクセスします。

![スクリーンショット 2024-03-02 100045.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389171/64861fc4-4967-b741-797c-b46eb31af9dd.png)

- コンテナ起動時に指定したAdminユーザの認証情報でサインインします。

- 左ペインから「Realm settings」をクリックします。
- 移動先の「General」タブの一番下にある「SAML 2.0 identity Provider Metadata」をクリックします。

画面![スクリーンショット 2024-03-02 103047.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389171/f3b1ee23-740a-08d5-8f76-0e719a304aac.png)

- 新しいタブでメタデータが取得できるので、これをXML形式のファイルとして保存おきます。

:::note info
このメタデータに含まれる公開鍵を使ってSP(AWS)はIdP(Keycloak)で発行されたSAMLアサーションの署名を検証します
:::

## IDプロバイダの作成(AWS上の作業)

- AWSマネジメントコンソールへサインインします
- IAMダッシュボードへ移動します
- 左ペインから「IDプロバイダ」を選択します
- 「プロバイダを追加」を選択します
- プロバイダのタイプに「SAML」を選択します
- プロバイダ名に任意のプロバイダ名を入力します
- メタデータドキュメントに前手順で作成したメタデータを指定します
- 「プロバイダを追加」をクリックします

## IAMロールの作成(AWS上の作業)

Keycloak上のユーザが使用するIAMロールを作成します。今回は検証用なので、ROアクセスのポリシーをつけたものを用意します。

作成手順は割愛しますが、肝は信頼関係ポリシーです。以下の設定が必要になります。[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_create_for-idp_saml.html)

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::123456789012:saml-provider/keycloak-sso"
            },
            "Action": "sts:AssumeRoleWithSAML",
            "Condition": {
                "StringEquals": {
                    "SAML:aud": "https://signin.aws.amazon.com/saml"
                }
            }
        }
    ]
}
```

## Client(SP)の追加(Keycloak上の作業)

- AWSをSPとして設定するためのClientの設定を追加します。

- 左ペインから「Clients」をクリックし、「Import client」をクリックします。

![スクリーンショット 2024-03-02 100500.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389171/419d87fd-edd5-b966-2fc8-a2555782bb0b.png)

- 前手順で取得したメタデータをXML形式でローカルに保存し、「Resource file」のボックスへドラッグ＆ドロップします。

![スクリーンショット 2024-03-02 101853.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389171/7f539bd1-22f3-4727-599a-e442f1e622dd.png)

- 「Save」をクリックし、「Client import successfully」が表示されることを確認します。

- 「Access settings」に以下の値を入力します。

|項目|値|
|---|---|
|Root URL|<http://localhost:8080>|
|Home URL|/auth/realms/master/protocol/saml/clients/aws-saml|
|IDP-Initiated SSO URL name|aws-saml|

- 「Save」をクリックします。

## Userとロールの追加(Keycloak上の作業)

- 「Clients」画面から「urn:amazon:webservices」を選択します。
- 「Roles」タブへ移動し、 「Create role」をクリックします。

![スクリーンショット 2024-03-02 104530.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389171/5242d56a-8a5e-71b2-668f-50ab739c5d32.png)

- 「Role name」へ「IAMロールのARN,IDプロバイダのARN」を入力し、「Save」をクリックします。
- 「Client scopes」の「role_list」の設定ボタンから「Remove」をクリックします。

![スクリーンショット 2024-03-02 105243.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389171/094e20fd-69d1-b250-a76f-3fbbefaadf56.png)

- 確認メッセージが出るので、「Delete」をクリックします。

Mapperを追加します。

- 「urn:amazon:webservices-dedicated」をクリックします。
- 「Add mapper」をクリックします。

![スクリーンショット 2024-03-02 110017.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389171/ae0e114a-5156-e18c-ca0e-86e535001182.png)

- 以下のMapperを作成してください。

![スクリーンショット 2024-03-03 095858.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389171/a9603ef3-b5b7-8851-38b3-decb08a9bfae.png)

これは、ユーザの名前をSAMLレスポンスの「Session Name」属性としてマッピングします。

```xml
            <saml:Attribute FriendlyName="Session Name"
                            Name="https://aws.amazon.com/SAML/Attributes/RoleSessionName"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                                     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                                     xsi:type="xs:string"
                                     >test-user</saml:AttributeValue>
            </saml:Attribute>
```

![スクリーンショット 2024-03-03 095810.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389171/f8494b04-27b8-3977-fbac-d1d5147c12af.png)

これは、ユーザに割り当てられたロールの一覧をSAMLレスポンスの「Session Role」属性としてマッピングします。

```xml
            <saml:Attribute FriendlyName="Session role"
                            Name="https://aws.amazon.com/SAML/Attributes/Role"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
                            >
                <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                                     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                                     xsi:type="xs:string"
                                     >arn:aws:iam::123456789012:role/SSO-test-role,arn:aws:iam::123456789012:saml-provider/keycloak-sso</saml:AttributeValue>
                <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                                     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                                     xsi:type="xs:string"
                                     >arn:aws:iam::123456789012:role/SSO-test-role-2,arn:aws:iam::123456789012:saml-provider/keycloak-sso</saml:AttributeValue>
            </saml:Attribute>
```

## Userとロールを紐付ける

GroupにRoleを紐づけて認証・認可の設定を行うのがセオリーですが、今回は検証なのでUserに直接Roleを紐づけます。また、今回は既存のUserを流用します。

- 「User」ページへ移動します。
- 対象とするUserを選択します。
- 「Role mapping」タブへ切り替えます。
- 「Assign role」をクリックします。
- フィルタをクリックして「Filter by clients」に切り替えます。(デフォルトだとレルムロールしか表示されないので注意)
- 前手順で作成したAWSサインイン用のロールへチェックを付けて「Assign」をクリックします。
- Userにロールが紐付けられていればOKです。

![スクリーンショット 2024-03-03 094154.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389171/cc9e80e1-1d7c-568c-27d4-384abcf0da58.png)

## サインインしてみる

今回はIdP-initiated方式で認証します。この場合は、まずIdP(Keycloak)でユーザ認証を行い、成功した場合に認証情報をSP(AWS)へ送信し、SPへのサインインを行う流れになります。

### IdPでのユーザ認証
http://{server-root}/realms/{realm}/protocol/saml/clients/{client-url-name}へアクセスします。
※変数はご自身の環境に合わせて置き換えてください

![スクリーンショット 2024-03-03 100801.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389171/c927b245-0ee4-bf98-e94b-ff804be5596a.png)

:::note info
ちなみに、このURLは「Client」->「Setings」->「IDP-Initiated SSO URL name」のテキストボックスの下に表示されています。
:::

前手順でAWSのロールを紐づけたユーザで認証します。

### Clientロールの選択

Userに紐づけたロールが２つ以上存在するときはロール選択画面が返却されるので、AssumeRoleしたいロールを選択して「サインイン」をクリックします。(一つしか無い場合はそのままAWSへ遷移します。)

![スクリーンショット 2024-03-03 101144.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389171/2ac0a946-65bb-5a54-9a61-a5f2255c82f8.png)

### AWSへのサインイン

ブラウザが自動でリダイレクト＆SAMLアサーションの転送を行ってくれます。
無事サインインできました。

![スクリーンショット 2024-03-03 092941.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389171/c321b3b2-1de4-13a0-d455-921c11fdb4ba.png)

## Tokenが有効期限切れだとAWSから怒られる(おまけ)

Keycloakで認証してAWSへリダイレクトするまでに1分も開けていないはずなのに、以下のエラーが発生…

![スクリーンショット 2024-03-03 092120.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389171/24394657-4e8e-d8ff-7776-85dfbbd11d69.png)

とりあえず、SAMLアサーションをデコードして中身を見てみると確かに日付が古い。
(試験をしていたのは3/3の午前中)

```xml
<saml:SubjectConfirmationData NotOnOrAfter="2024-03-02T08:26:41.525Z" Recipient="https://signin.aws.amazon.com/saml"/>
```

原因を調べるとWSLのクロックが狂っていました(赤面)
以下のコマンドを実行してハードウェアクロック(WindowsPC)の時間に合わせます。

```bash
sudo hwclock -s
```

クロックの同期後、認証が上手くいきました。
認証まわりの設計は各登場人物の時刻設定も重要であることを実感しました。