IAMアクセスキーなくすためにAssumeRoleを使ってみた話

Agency事業部所属でアプリケーションエンジニアの松井隆起です!
在宅勤務で仕事部屋の環境整備が大事ということに気づき、最近除湿機を買いました!
湿度は低ければ低いほど働きやすいですね!

今回は一時的に有効な認証情報を受け取るAWSのAssumeRoleの機能を検証してみたので、その内容を記述します。

背景

現在、RailsアプリケーションのデプロイにAnsibleを使用し、そのTaskの中でAWSのサービスにアクセスしています。デプロイはCI/CDで自動化されているわけではなく、各個人のDocker環境でAnsibleを実行している状態で、CPUアーキテクチャの違いから環境差異が発生し、デプロイできない人が増えてきました。
そこでチームでは上記の課題解決に取り組むために、デプロイをEC2インスタンスから行うように移行作業に取り組みました。

また、AnsibleのTaskでAWSサービスにアクセスするためにIAMユーザのアクセスキーを発行し使用していましたが、弊社ではIAMユーザのアクセスキーをセキュアに管理するために90日で交換するルールがあり交換作業も定期的に発生します。

そのためIAMユーザで発行したアクセスキーを使用せず、AssumeRoleで受け取った一時的に有効な認証情報を使用する構成に変更するため、デプロイ手段をEC2インスタンスへ移行する際にAssumeRoleで認証情報を受け取る実装に取り組みました!

AssumeRoleってなんや?

AssumeRoleとは、STSの1機能のこと。
IAMロールに付与した権限を使用できる一時的に有効なアクセスキー、シークレットアクセスキー、セッショントークンを発行する仕組みのこと。

やったこと

  • デプロイを行うEC2インスタンスにSTSへのアクセス権限のみを付与したIAMロールをセット
  • EC2インスタンス内でPythonスクリプトを実行し、IAMロールを引き受けるためにAssumeRoleする
  • 受け取ったアクセスキー、シークレットアクセスキー、セッショントークンをDocker環境内の環境変数にセット

手順・作業内容

① IAMロールの作成

必要なIAMロールは下記の2つです。

  • デプロイを行うEC2インスタンスにセットするIAMロール (ロールA)
  • デプロイを行うEC2インスタンスがSTSを通して引き受けるIAMロール (ロールB)

まず、ロールAのポリシーを作成します。
このEC2インスタンスはSTSにアクセスできれば良いので、STSへのアクセス権限のみ付与します。

ポリシーを作成したら次にIAMロールを作成します。
信頼されたエンティティタイプ(信頼ポリシー)にはEC2を指定します。信頼ポリシーで指定されたサービスがこのIAMロールを使用することが可能になります!

先ほど作成したポリシーをアタッチします。

あとは名前を設定して作成完了です。

作成したIAMロールをEC2にセットします。
これで ロールAの作成、EC2へのセットは完了です!
これでこのロールがセットされたEC2はSTSへのアクセスのみ可能になります。

続いてロールBを作成します。
IAMロールの作成画面で信頼されたエンティティのカスタム信頼ポリシーにチェックを入れます。
信頼ポリシーのエディタで下記のように記述します。
こうすることでロールAがセットされているリソースのみ、ロールBを使用することができます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Statement1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::{アカウントID}:role/{ロールAの名前}"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

EC2インスタンスへのアクセスが必要なためEC2へのアクセス権限を付与します。

ロールBの名前を指定して作成します。
AWSコンソール上での作業は以上です!

② AssumeRoleする

一時的に有効な認証情報を受け取るためにSTSのAPIを使用します。
これで認証情報の受け取りは完了!
(Pythonで記述)

import boto3

# ロールBのarnを指定
role_arn = 'arn:aws:iam::{アカウント ID}:role/{ロールBの名前}'

# AssumeRole操作を行うためのセッションを作成
sts_client = boto3.client('sts')
# RoleSessionNameは好きな名前でOK
response = sts_client.assume_role(RoleArn=role_arn, RoleSessionName='XXX_DEPLOY')

access_key = response['Credentials']['AccessKeyId']
secret_access_key = response['Credentials']['SecretAccessKey']
session_token = response['Credentials']['SessionToken']

# Docker環境で使用するためdocker-compose.envへの書き込みを行う
file = open('docker-compose.env', 'w')
file.write('AWS_DEFAULT_REGION=ap-northeast-1\nAWS_DEFAULT_OUTPUT=json\nAWS_ACCESS_KEY_ID={}\nAWS_SECRET_ACCESS_KEY={}\nAWS_SESSION_TOKEN={}\n'.format(access_key, secret_access_key, session_token))

file.close()

実際に受け取った値が↓です

③ 一時認証情報を使用してRailsアプリケーションのデプロイを実行する

RailsアプリケーションのデプロイにはAnsibleを使用しており、そのTaskの中でAWSサービスにアクセスしています。
実行後、CloudTrailでログを確認してみるとPythonスクリプトで指定したRoleSessionNameで実行されていることが確認できました。

まとめ

以上がAssumeRoleの実装方法でした!
AWSはアクセスキー漏洩の危険性からアクセスキーを生成しない方法を推奨しています。
そのベストプラクティスに沿って実装できたことやアクセスキーの管理が不要になったことなど良いことしかなくAssumeRoleによる認証情報の受け取りを実装できてよかったと感じました!

これを機にAWSでの権限管理を見直してみてはいかがでしょうか!