KeycloakのToken Exchangeを活用した一時的なAWSアクセスキーの発行

[NRI OpenStandia Advent Calendar 2020](https://qiita.com/advent-calendar/2020/nri-openstandia)の3日目は、2日目の続きとしてKeycloakの[Token Exchange](https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange)を活用した話を紹介します。具体的には、AWSのCLIツールなどの利用で必要なAWSアクセスキーの発行をToken Exchange経由で行うというものです。

# やりたいこと

皆さんAWSアクセスキーの管理はどのようにされているでしょうか？誤ってGitリポジトリにコミットしちゃって漏洩し、たくさんEC2インスタンスを起動されて高額請求されちゃう、なんていう事故をたまに聞きますよね。ベストプラクティスとしてそもそも発行しない (EC2インスタンスロールなど、IAMロールで制御する)という考え方がありますが、ローカルPCなどから作業したい場合はどうしても発行が必要になります。そのような場合は、有効期限付きの一時的なAWSアクセスキーを使うと漏洩したときのリスクを減らすことができます。

# 一時的なAWSアクセスキーの発行

いくつかやり方があります。

## AWS SSOを使う
AWS SSOを使って認証を一元化しているのであれば、AWS SSOの画面から一時的なAWSアクセスキーを発行して取得することができます。詳しくは、https://dev.classmethod.jp/articles/aws-sso-temporary-credential/ などで紹介されています。
また、[AWS CLI バージョン2からはCLIからもAWS SSOを利用して発行することができます](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-sso.html)。
(AWS SSOを使っている人には本記事はまったく役に立ちません... :angel_tone1:)

## サードパーティ製ツールを使う
企業によっては諸事情によりAWS SSOを使わず、AWS管理コンソールへのログインを、KeycloakやADFS、Azure AD、Oktaなどの外部IdPと直接SAMLで連携させている場合があります。この場合は、例えば https://github.com/Versent/saml2aws のようなツールを使うとコマンドラインで一時的なAWSアクセスキーを取得可能になります。ただし saml2aws の実装はスクレイピング方式であり、各IdPのログイン画面に対してログイン処理をエミュレートする形になっています。https://github.com/Versent/saml2aws/blob/3968f0fa20fb71b2e78e1364bce68acc3f4b201a/pkg/provider/keycloak/keycloak.go#L76 あたりを見ると、Keycloakのログイン画面をパースしてログインしていることがわかります。

この方式ですと、ログイン画面のHTMLや認証シーケンスに強く依存しますので、バージョンアップで動かなくなったり、認証フローをカスタマイズしていると対応できなかったりします。また、**パスワードをサードパーティ製ツールに渡す**というところで懸念があります。理想的にはOpenID Connectなどのフェデレーションを利用して、スクレイピングなしで安全に行いたいところです。

# というわけで作ってみた

(随分前ですが) https://github.com/openstandia/aws-cli-oidc というツールを勉強も兼ねて作ってみました。クライアントPCで動作するCLIのネイティブアプリケーションですので、[RFC8252 OAuth 2.0 for Native Apps](https://tools.ietf.org/html/rfc8252)を参考にしつつ実装しています。CLIツールを起動するとブラウザが起動し、OpenID Provider(今回だとKeycloak)で認証が行われます。 **ブラウザを使ってKeycloakに認証しているので、パスワードなどのクレデンシャルがCLIツールに流れることありません。** 認証後、CLIツールは認可コードフローを経てIDトークンを受け取ります。このIDトークンをAWS STSに渡すことで一時的なアクセスキーを発行することができます(事前に、KeycloakとAWSアカウントとの間でOpenID Connectによるフェデレーション設定が必要です)。

<img src="https://github.com/openstandia/aws-cli-oidc/blob/master/flow-with-oidc.png?raw=true"/>

なお、お気づきかもしれませんが、この方式だとToken Exchangeは出てきません(AWS STSがAWS側の独自実装のToken Exchangeではありますが)。

この方式を利用するにはAWS STSがIDトークンの検証を行うために、Keycloakに対して公開鍵を取得するためにHTTPSアクセスが必要になります(図中の番号9の箇所)。よって、Keycloakを社内に立てている場合はネットワーク制約のため実現は難しいでしょう。インターネット上にあるOpenID Provider(Googleやその他IDaaS、パブリッククラウド上にKeycloakを立てているなど)であればこの方式を使うことができますが、社内ネットワーク内にある場合は残念ながら使えません。

# そこでSAML2とToken Exchangeを使う

そこで https://github.com/openstandia/aws-cli-oidc では、SAML2とToken Exchangeを使った方式も実装しています。事前に、社内にあるKeycloakとAWSをSAML2でフェデレーション設定をしておきます。SAML2の場合は、フェデレーション設定時にKeycloakの公開鍵を設定する方式になっているため、AWS STSからKeycloakに対して通信が行われることはありません。よって社内ネットワークに配置しても使うことができます。

問題は、CLIツールからKeycloakで認証した後にどうやってAWS STSに渡すSAMLResponseを取得するかです。CLIツールとKeycloakの間はOpenID Connectでフェデレーションしているだけであり、通常だと取得可能なトークンはCLIツール向けに発行されたIDトークンとアクセストークンのみです。ここからどうやってAWS向けのSAMLResponseを生成すればよいかが課題となります。

ここでようやくToken Exchangeが出てきます。つまり、 **CLIツール向けに発行されたIDトークンまたはアクセストークンを、AWS向けのSAML2トークンに交換ができればいい** わけです。下図でいうと、番号8の箇所ですね。ここでアクセストークンをKeycloakのToken Exchangeを使ってAWS STS用のSAML2アサーションに交換しています。Keycloakバージョン10まではSAML2トークンへの交換に対応していませんでしたが、[2日目の記事](https://qiita.com/wadahiro/private/856ec84c1429ad8536d9)で紹介したとおり、プルリクエストを送りバージョン11以降は対応しています。

<img src="https://github.com/openstandia/aws-cli-oidc/blob/master/flow-with-saml2.png?raw=true" />

SAML2アサーションに交換できたらそれをSAMLResponseとして少々ラップしてあげて(アサーションのみではAWS STS側が受け付けないため)、番号10のところでAWS STS APIを呼び出してあげれば無事に一時的なAWSアクセスキーを受け取ることができます。

# 終わりに

というわけで、本記事では2日目に書きました「KeycloakのToken ExchangeのSAML2対応」をどのように活用しているかを紹介しました。意外とSAML2トークンに交換するユースケースが他にあるかもしれません。是非皆さんも探してみてください。