AWSコンソールのSAML認証を解剖してみた

# はじめに
　AWSコンソールは，外部IdP経由でSAML認証が利用できます。その設定例として，KeycloakをIdP(Identify Provider)とした事例は幾つか情報があります。ただ，それらのサイトでは設定方法の説明はあるものの，設定がSAML認証や送受される情報にどう影響するのかについて詳しく言及したサイトはあまりありませんでした。私がSAML認証（KeyCloak＋AWSコンソール）を試して手こずった際に調査したことを備忘のために記事にしておきます。
　なお，この記事は2023/11時点の情報です。
## 参考：KeyCloakでAWSコンソールにSSOする設定手順
本記事の情報ですが，別記事の手順で構築した環境に基づくものです。

https://qiita.com/gaichi/items/4e58d1d1c3ad4a024ffd

その他，以下のサイトを参考にしました。

https://aws.amazon.com/jp/blogs/big-data/federate-amazon-quicksight-access-with-open-source-identity-provider-keycloak/

https://xp-cloud.jp/blog/2017/11/09/2261/


https://www.ryuzee.com/contents/blog/7129


# AWSから提供されるsaml-metadata.xml
KeycloakでClientを新規作成する際に，[AWSから提供されるSAML設定用のXML](https://signin.aws.amazon.com/static/saml-metadata.xml)をインポートしました。このファイルは，SAML経由でAWSサービスを利用するために必要な接続情報が示されたXMLです。

## saml-metadata.xmlの内容

```XML:saml-metadata.xml(一部省略)
<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
  entityID="urn:amazon:webservices" validUntil="2024-11-09T00:00:00Z">
  <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"
    WantAssertionsSigned="true">
    <KeyDescriptor use="signing">
      <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:X509Data>
          <ds:X509Certificate>MIIDcTCCAlmgAwIBAgIIU2CuU5Ql5cUwDQYJKoZIhvcNAQELBQAwZzEfMB0GA1UE
            略
            zxmEl43A0ClSkQdJKjY3+MRxMLYA</ds:X509Certificate>
        </ds:X509Data>
      </ds:KeyInfo>
    </KeyDescriptor>
    <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
    <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
    <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
    <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
    <AssertionConsumerService index="1" isDefault="true"
      Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
      Location="https://signin.aws.amazon.com/saml" />
    <AssertionConsumerService index="2" isDefault="false"
      Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
      Location="https://eu-north-1.signin.aws.amazon.com/saml" />
    <略>各リージョンのURI<略/> 
    <AssertionConsumerService index="28" isDefault="false"
      Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
      Location="https://il-central-1.signin.aws.amazon.com/saml" />
    <AttributeConsumingService index="1">
      <ServiceName xml:lang="en">AWS Management Console Single Sign-On</ServiceName>
      <RequestedAttribute isRequired="true" Name="https://aws.amazon.com/SAML/Attributes/Role"
        FriendlyName="RoleEntitlement" />
      <RequestedAttribute isRequired="true"
        Name="https://aws.amazon.com/SAML/Attributes/RoleSessionName" FriendlyName="RoleSessionName" />
      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1"
        FriendlyName="eduPersonAffiliation" />
      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.2"
        FriendlyName="eduPersonNickname" />
      <略>isRequired="false"な属性<略/> 
      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.2.1.6"
        FriendlyName="eduOrgWhitePagesURI" />
      <RequestedAttribute isRequired="false" Name="urn:oid:2.5.4.3" FriendlyName="cn" />
    </AttributeConsumingService>
  </SPSSODescriptor>
  <Organization>
    <OrganizationName xml:lang="en">Amazon Web Services, Inc.</OrganizationName>
    <OrganizationDisplayName xml:lang="en">AWS</OrganizationDisplayName>
    <OrganizationURL xml:lang="en">https://aws.amazon.com</OrganizationURL>
  </Organization>
</EntityDescriptor>
```

## 主に提供される情報
- AWSのX.509証明書
\<ds:X509Certificate\>の箇所です。証明書の中には公開鍵も入っています。keycloakのclients>Keysタブ内のCertificateに反映されます。
- SAML認証のリクエスト先URI
\<AssertionConsumerService\>の箇所です。共通的に使うURIの他，各リージョン毎のURIも示されています。keycloakのclients>Settingタブ内のValid Redirect URIsに反映されます。なお，keycloakへのインポート時には，"https://signin.aws.amazon.com/saml" だけが有効になりました。
- 要求する属性値
　\<RequestedAttribute\>の箇所です。"https://aws.amazon.com/SAML/Attributes/Role"と"https://aws.amazon.com/SAML/Attributes/RoleSessionName" の2つが必須属性です。その他は必須ではありません。KeyCloakのClients>Mapperに反映されます。
　
# keycloak側の情報descriptor.xml
AWS IAMのIDプロバイダの設定画面に登録した，IdP(KeyCloak)側の情報です。含まれる情報のうち一番重要なのはX.509証明書です。この中にIdP(KeyCloak)の公開鍵が含まれています。SAML ResponseをIdPから（クライアントを経由して）SPに送信する際，IdPはSAML Responseに署名を含めています。この署名をSP側で検証するために公開鍵を使います。
```XML:descriptor.xml(一部省略)
<md:EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
    xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
    entityID="http://10.0.99.9:18080/auth/realms/master">
    <md:IDPSSODescriptor WantAuthnRequestsSigned="true"
        protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <md:KeyDescriptor use="signing">
            <ds:KeyInfo>
                <ds:KeyName>dfAGt******ApJGcs</ds:KeyName>
                <ds:X509Data>
                    <ds:X509Certificate>
                        MIICmz****rSQ6J6KMEk4zGBj8=</ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </md:KeyDescriptor>
        <md:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
            Location="http://10.0.99.9:18080/auth/realms/master/protocol/saml/resolve" index="0"></md:ArtifactResolutionService>
        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
            Location="http://10.0.99.9:18080/auth/realms/master/protocol/saml"></md:SingleLogoutService>
        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
            Location="http://10.0.99.9:18080/auth/realms/master/protocol/saml"></md:SingleLogoutService>
        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
            Location="http://10.0.99.9:18080/auth/realms/master/protocol/saml"></md:SingleLogoutService>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
            Location="http://10.0.99.9:18080/auth/realms/master/protocol/saml"></md:SingleSignOnService>
        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
            Location="http://10.0.99.9:18080/auth/realms/master/protocol/saml"></md:SingleSignOnService>
        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
            Location="http://10.0.99.9:18080/auth/realms/master/protocol/saml"></md:SingleSignOnService>
        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
            Location="http://10.0.99.9:18080/auth/realms/master/protocol/saml"></md:SingleSignOnService>
    </md:IDPSSODescriptor>
</md:EntityDescriptor>
```
## 主に提供される情報
- Keycloak側のX.509証明書
\<ds:X509Certificate\>の箇所です。公開鍵も入っています。
- KeyCloak側の各種URI
SP Initiated方式の際に使うのであろう，IdP（KeyCloak)側のURIが示されています。今回の方式では，ユーザが最初にアクセスするのはKeyCloak（IdP)なので，AWS側ではおそらく利用しないと思います。（SP InitiatedとIdP Initiatedの違いですが，ログインするユーザが最初にアクセスするのがSPであればSP Initiated，IdPであればIdP Initiatedです。くわしくは[こちら](https://qiita.com/Shinya-Yamaguchi/items/434fab8c39e806e69a88)）

# SAML Response

## SAML Responseとは
 IdP（Keycloak）による認証完了後，ブラウザからSP(AWS）のSAMLサインインURIに送信されるのがSAML Responseです。SAMLレスポンスには，IdPからSPに通知する属性値がXML形式で表現されています。
 [公式](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_providers_enable-console-saml.html)にある図表の「④Client posts SAML assertion to sign-in URL」にあたります。
 
## SAML Responseの確認方法
SAML Responseの内容を見るには一手間必要です。
ブラウザの開発者ツール(F12)を有効化してKeyCloakにログインしてください。
ロールの選択画面までリダイレクトされたら，開発者ツールのネットワークタブからペイロードを選択し，SAMLResponseの箇所で右クリック，「値をコピー」します。この値は，SAML ResponseをBase64でエンコードした結果です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457890/13892954-2134-478c-6855-4a26f5d38885.png)

取得した値はhttps://www.samltool.com/decode.php でデコードできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457890/b62019c5-49df-7089-de0a-c93295c25793.png)

デコードすると次のようなXML（抜粋，整形済み）が見えます。

```XML:SAML Response(抜粋)
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    Destination="https://signin.aws.amazon.com/saml" ID="ID_b5d37c1d************"
    IssueInstant="2023-11-22T14:23:05.428Z" Version="2.0">
    <略></略>
    <saml:Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion"
        ID="ID_792e589d-0025-4010-b6b0-************" IssueInstant="2023-11-22T14:23:05.428Z"
        Version="2.0">
        <略>X.509証明書など</略>
        <dsig:SignatureValue>
            IQS1cc/QfwCNSF/dUa0CfEr******f9w==</dsig:SignatureValue>
        <dsig:KeyInfo>
        <略></略>
        <saml:Conditions NotBefore="2023-11-22T14:23:03.428Z"
            NotOnOrAfter="2023-11-22T14:24:03.428Z">
            <saml:AudienceRestriction>
                <saml:Audience>urn:amazon:webservices</saml:Audience>
            </saml:AudienceRestriction>
        </saml:Conditions>
        <略></略>
        <saml:AttributeStatement>
            <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">
                    keycloak-user1</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute FriendlyName="Session Duration"
                Name="https://aws.amazon.com/SAML/Attributes/SessionDuration"
                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">28800</saml:AttributeValue>
            </saml:Attribute>
            <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::20**********:role/TestRoleSamlKeycloak-Viewaccess,arn:aws:iam::20**********:saml-provider/local-keycloak</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::20**********:role/TestRoleSamlKeycloak-Adminaccess,arn:aws:iam::20**********:saml-provider/local-keycloak</saml:AttributeValue>
            </saml:Attribute>
        </saml:AttributeStatement>
    </saml:Assertion>
```

## 主に提供される情報
### 属性値
Mappersメニューで設定した三つのProtcol Mapperが，Attributeとして送信されました。
- Session Name(https://aws.amazon.com/SAML/Attributes/RoleSessionName )※必須項目
    - keycloakにログインしたユーザ名です。MapperTypeがUser Propertyなので，ユーザ名が代入されました。
- Session Role(https://aws.amazon.com/SAML/Attributes/Role )※必須項目
    - Rolesで設定したRolenameの内容が2件入っています。Role listの名の通り，ユーザが持つロール一覧です。
- Session Duration(https://aws.amazon.com/SAML/Attributes/SessionDuration)
    - 固定値（Hardcoded attribute）として設定した28800です。
    
これらの属性値の詳細は，[公式](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_providers_create_saml_assertions.html)にリファレンスがあります。
### 署名
KeyCloakの秘密鍵による，送信データの署名が入っています。AWS側ではこの署名をKeycloakの公開鍵を使って検証します。

# 認証？認可？
　[別記事](https://qiita.com/gaichi/items/4e58d1d1c3ad4a024ffd)で扱ったように，AWSコンソールでSAML認証すると，ロールの割当て（＝認可）ができます。SAMLはもともと認証フレームワークなのに，「なぜ認可もできているの？」という疑問を持つ方がいらっしゃるかもしれません。
　この疑問に対しての答えは，「結果的に認可を行っているように見えるだけ」です。実際に認可を行うのはAWSコンソール側です。OAuth2.0やOIDCのように，トークンを使った本格的な認可制御を行っているわけではありません。
　SAML Responseには，Session Roleとしてユーザに割り当てられたロール情報が含まれます。このロール情報をどう解釈するのか，どう扱うのかは，受信側であるSP（AWSコンソール）に委ねられます。つまり，IdPからSPに対する，一方的な情報提供のみであるということです。AWSコンソール側では，Keycloakから提供されたロール情報を基に，自身が持つIAMロールと照らし合わせてロールの選択画面を表示します。ユーザの目線では，結果的に認可が行われているように見えます。

# 参考情報
- [サードパーティーの SAML ソリューションプロバイダーと AWS の統合](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_providers_saml_3rd-party.html)には，AWSコンソールのとSAML連携できるIDaaSのリストがあります。
- [SAML 2.0 フェデレーティッドユーザーが AWS Management Consoleにアクセス可能にする](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_providers_enable-console-saml.html)