見出し画像

AWS SSO を利用した AWS 認証情報

POL では AWS SSO を利用して各 AWS アカウントへのログインや認証情報を管理しています。今回は AWS SSO を利用した AWS 認証情報管理の詳細と工夫点について共有したいと思います。

AWS マネジメントコンソールへのログイン

AWS SSO 管理下では、AWS マネジメントコンソールへログインする際には、はじめに下記画像のような AWS SSO のユーザーポータルにアクセスします。

画像1

その後、ログインしたい AWS アカウントの “Management console” (マネジメントコンソールへのリンク) をクリックすることで目的の AWS アカウントのマネジメントコンソールへログインすることができます。

ちなみに POL では、AWS SSO のユーザー情報は Google Workspace と同期しており、Google Workspace の認証情報がない場合は AWS SSO のユーザーポータルへはアクセスできません。また、Google Workspace の機能を利用して職能ごとに AWS アカウントへのアクセス権限を割り与えているため、 Google Workspace の認証情報を保有していても全ての AWS アカウントへログインできるようにはなっておりません。AWS アカウントが複数ある場合でも、AWS SSO を利用することで適切に認証情報の管理を実施することができます。

AWS SSO のユーザー情報と Google Workspace のユーザー情報を同期する仕組みについては [1] を参考にしています。

プログラムで利用する認証情報

さて、AWS SSO のユーザーポータルではマネジメントコンソールへのアクセス機能以外にも AWS CLI やプログラムから利用するための一時的な認証情報 (アクセスキー、シークレットアクセスキー、セッショントークンのセット)を発行することができます。シェル変数や環境変数として、この一時的な認証情報を設定することで、プログラムなどから認証情報を読み取り、AWS の API を利用できます(~/.aws/credentials に記述することもできます)。また、この認証情報は一時的であるため、有効期限(デフォルトで 1 時間、最大 12 時間まで)を超えると利用できなくなるため、認証情報が流出した際のリスクを軽減することができます。

しかし、この方法ではプログラムの利用者・開発者に対して、手動にて環境変数などを通じて AWS の認証情報を設定させるという負荷を与えることになります。また、認証情報が一時的であるため、有効期限が切れるたびに更新する必要があります。この事実を忘れて、プログラムが動かない → デバックする → AWS へうまくアクセスできなかっただけ、なんてことをさせてしまえば、時間の無駄となってしまいます。セキュリティ上の都合とはいえ、不便さをなくしたく、少しでも楽に認証情報を取得できないか検証してみました。

AWS SSO OIDC API

AWS CLI v2 では aws sso login コマンドを利用することで、下記のようなフローにて一時的な認証情報の取得・設定を実施できます。

1. AWS CLI v2 で aws sso login コマンドを叩く
2. ブラウザが自動で開き、ユーザー認証を実施する
3. (ユーザー認証が完了したら) AWS CLI v2 が認証情報を設定する
4. AWS 認証情報が必要な AWS CLI コマンド ( aws s3 ls など) が実行できる

AWS SSO のユーザーポータルで実施していたときは、認証情報の取得・設定をユーザーが手動にて実施していましたが、aws sso login コマンドでは、AWS CLI 自体が認証情報の取得・設定を実施してくれます。この仕組を利用すれば、プログラムで利用する認証情報の管理をスマートに実施できそうです。

ということで、まず、AWS CLI v2 のソースコードを読み取り、AWS CLI v2 がどのように認証情報の取得・設定しているか読み解いて見ました。

aws sso login コマンドを実行すると、下記のソースコードの箇所が動きます。

・ https://github.com/aws/aws-cli/blob/v2/awscli/customizations/sso/utils.py#L43
・ https://github.com/boto/botocore/blob/v2/botocore/utils.py#L2293

実行されている処理を順番に書いてみると、下記のようになっております。

1. AWS SSO OIDC の RegisterClient を呼び出し
2. AWS SSO OIDC の StartDeviceAuthorization を呼び出し
3. (AWS CLI が) StartDeviceAuthorization のレスポンスに含まれる verificationUriComplete をブラウザで開く
4. (ユーザーが) 開いたブラウザでユーザー認証
5. AWS SSO OIDC の CreateToken を呼び出して認証情報を取得する

これは AWS SSO OIDC のドキュメント [2] にも記載がありますが、RFC 8628 を実装したものであります(RFC 8628 のフローについては [3] が参考になりました)。

これと同じ処理を AWS の認証情報を利用したいプログラムにも記述してあげることで、開発者が AWS の認証情報を心配する負荷を下げることができます。

例えば、Python では下記のように記述することができます。

import time
import boto3
import botocore


class CredentialHelper:

   _SSO_REGION = 'us-east-1'
   _SSO_CLIENT_NAME = 'SAMPLE_CLIENT_NAME'
   _SSO_START_URL = 'https://ssostarturl.example.com/start'
   _SSO_ACCCOUNT_ID = '1234567890'
   _SSO_ROLE_NAME = 'SAMPLESSOAWSAccess'
   _SSO_DEFAULT_INTERVAL = 5

   def get_sso_credentials(self):
       """ AWS SSO から認証情報を取得します
       Raises:
           botocore.exceptions.PendingAuthorizationExpiredError: see https://docs.aws.amazon.com/singlesignon/latest/OIDCAPIReference/API_CreateToken.html
       Returns:
           AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN を含む dict 型を返却する
           例)
               {
                   "accessKeyId": "アクセスキー",
                   "secretAccessKey": "シークレットアクセスキー",
                   "sessionToken": "セッショントークン",
               }
       """

       ssooidc_client = boto3.client('sso-oidc', region_name=self._SSO_REGION)
       register_client_response = ssooidc_client.register_client(
           clientName=self._SSO_CLIENT_NAME,
           clientType='public',
       )

       start_device_authorization_response = ssooidc_client.start_device_authorization(
           clientId=register_client_response['clientId'],
           clientSecret=register_client_response['clientSecret'],
           startUrl=self._SSO_START_URL
       )

       print('開けゴマ! ページが開いたら "Sign In to AWS CLI" をクリックしてね: ' +
               start_device_authorization_response['verificationUriComplete'])

       # verificationUriComplete がブラウザ上で開かれるまで polling
       interval = start_device_authorization_response.get(
           'interval', self._SSO_DEFAULT_INTERVAL)
       while True:
           try:
               create_token_response = ssooidc_client.create_token(
                   clientId=register_client_response['clientId'],
                   clientSecret=register_client_response['clientSecret'],
                   grantType="urn:ietf:params:oauth:grant-type:device_code",
                   deviceCode=start_device_authorization_response['deviceCode'],
               )
               sso_client = boto3.client('sso', region_name=self._SSO_REGION)
               get_role_credentials_response = sso_client.get_role_credentials(
                   roleName=self._SSO_ROLE_NAME,
                   accountId=self._SSO_ACCCOUNT_ID,
                   accessToken=create_token_response['accessToken']
               )
               # save cache
               self.__set_cached_token(
                   get_role_credentials_response['roleCredentials'])
               return get_role_credentials_response['roleCredentials']
           except ssooidc_client.exceptions.SlowDownException:
               interval += self._SLOW_DOWN_DELAY
           except ssooidc_client.exceptions.AuthorizationPendingException:
               pass
           except ssooidc_client.exceptions.ExpiredTokenException:
               raise botocore.exceptions.PendingAuthorizationExpiredError()
           time.sleep(interval)

一点、注意しなければならない点があり、「5. AWS SSO OIDC の CreateToken を呼び出し」で取得できるトークンは一般的な AWS の認証情報 (アクセスキー、シークレットアクセスキー、セッショントークンのセット)ではなく、AWS SSO API  である GetRoleCredentials を呼び出す必要があります。

これでプログラムで利用する認証情報について、少しだけ便利に管理することができました。

終わりに

株式会社POLではエンジニア、デザイナー、プロダクトマネージャーを大募集してます!お話しだけでも構いませんのでお気軽にお声がけください!!!

https://jobs.forkwell.com/pol/jobs/6372

https://jobs.forkwell.com/pol/jobs/6373

参考資料

[1] How to use G Suite as an external identity provider for AWS SSO | AWS Security Blog
https://aws.amazon.com/jp/blogs/security/how-to-use-g-suite-as-external-identity-provider-aws-sso/

[2] Welcome - AWS Single Sign-On
https://docs.aws.amazon.com/singlesignon/latest/OIDCAPIReference/Welcome.html

[3] 図解デバイスフロー(RFC 8628) - Qiita
https://qiita.com/TakahikoKawasaki/items/78eff94cef92741131f0

この記事が気に入ったらサポートをしてみませんか?