こんにちは。X(クロス)イノベーション本部 ソフトウェアデザインセンター セキュリティグループの耿です。
AWSのマネジメントコンソールへのユーザー認証ではMFAの利用が一般的になってきましたが、アクセスキー利用時にMFAを必須とするには少しコツがいるので、この記事でまとめてみます。
ちなみに Security Hub のコントロール IAM.19「すべての IAM ユーザーに対して MFA を有効にする必要があります」を満たすには、マネジメントコンソールのユーザーだけではなく、アクセスキーで利用するIAMユーザーに対してもMFAの設定をしなければなりません。
- 前提:IAMユーザーの状態
- 通常通りポリシーをつけても、MFA不要でアクセスできてしまう
- MFAを必須にする方法1:ユーザーポリシーに条件を追加する
- MFAを必須にする方法2:スイッチロールを利用し、コードの取得を簡単にする
- Tips:OSSツールAWSumeで簡単にスイッチロール
- まとめ
前提:IAMユーザーの状態
IAMユーザーを作成し、MFAを設定した状態を前提として、この後の話を進めます。(IAMユーザー・アクセスキーを配布するケースは事前にMFAを設定しておくことはできませんが、この記事では割愛します。)
このユーザーに対してアクセスキーを発行しておきます。
~/.aws/credentials
に test-user
というプロファイル名で、アクセスキーとシークレットアクセスキーを保存しておきます。
[test-user] aws_access_key_id = <アクセスキーID> aws_secret_access_key = <シークレットアクセスキー> region = ap-northeast-1
通常通りポリシーをつけても、MFA不要でアクセスできてしまう
このユーザーに対して、以下のようなポリシーを付けてみます。
この記事では一貫して ec2:DescribeRegions
を例としていますが、一般的なリソースアクセスの許可に適宜置き換えて考えてください。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "ec2:DescribeRegions", "Resource": "*" } ] }
このユーザーを利用し DescribeRegions
APIをコールすると、MFAを設定しているにも関わらず、MFAコードを入力しなくてもアクセスが成功してしまいました。
Security Hub のIAM.19コントロールをクリアするだけであれば、このようにMFAを有効にするだけで良いのですが、それだとMFAを利用しなくても権限を使用できてしまいます。せっかくMFAを有効にするのですから、MFAを利用しないとAPIアクセスできないようにきっちり設定したいですね。
MFAを必須にする方法1:ユーザーポリシーに条件を追加する
MFAの利用を必須とする方法の1つ目として、IAMユーザーのポリシーを次のように書き換えます。Condition
句を追加し、aws:MultiFactorAuthPresent
を利用してMFAを行ったことを条件とします。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "ec2:DescribeRegions", "Resource": "*", "Condition": { "Bool": { "aws:MultiFactorAuthPresent": "true" } } } ] }
再び DescribeRegions
APIをコールすると、MFAを利用していないので今度はちゃんとエラーとなります。
MFAを利用するためには STS の GetSessionToken
APIを呼び出し、一時的な認証情報を取得します。
このとき --serial-number
オプションには設定した MFA デバイスの識別子(ARN)を、--token-code
にはMFAトークンを指定します。
発行された一時的な認証情報を使用するために ~/.aws/credentials
に新しくプロファイルを作成しても良いですが、ここではシンプルに環境変数に設定します。
(環境変数に設定されたAWS認証情報は、認証情報ファイルよりも優先されます。)
そして環境変数に設定した認証情報を利用してもらうために、AWS CLIの --profile
オプションをつけずにDescribeRegions
APIをコールすると、再びアクセスできるようになりました。
以上の方法でMFAの利用を必須にはできましたが、
GetSessionToken
APIを呼び出して一時的な認証情報を取得する- 取得した認証情報を利用するように設定する
といった手間がかかってしまいます。
MFAを必須にする方法2:スイッチロールを利用し、コードの取得を簡単にする
上で説明した手間を省くために、スイッチロールを利用してMFAを強制する方法があります。
IAMロールを1つ作成します。信頼ポリシーでは同じアカウントからの AssumeRole
を許可すると同時に、aws:MultiFactorAuthPresent
を利用してMFAを行ったことを条件とします。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::<アカウントID>:root" }, "Action": "sts:AssumeRole", "Condition": { "Bool": { "aws:MultiFactorAuthPresent": "true" } } } ] }
このロールの許可ポリシーは、上で作成したユーザーと同じとします。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "ec2:DescribeRegions", "Resource": "*" } ] }
そして元のIAMユーザーの許可ポリシーから DescribeRegions
の許可を除外し、作成した IAMロールへの AssumeRole
のみを許可します。(aws:MultiFactorAuthPresent
の条件も不要です。)
{ "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::<アカウントID>:role/<ロール名>", "Effect": "Allow" } ] }
スイッチロールを簡単にするために、 ~/.aws/config
に以下のように追記します。 [profile test-user]
の部分は ~/.aws/credentials
に記載したプロファイル名 test-user
と名前を合わせる必要があります。
[profile test-user] region = ap-northeast-1 [profile my-test-role] region = ap-northeast-1 source_profile = test-user role_arn = arn:aws:iam::<アカウントID>:role/<スイッチロール用のロール名> mfa_serial = arn:aws:iam::<アカウントID>:mfa/<デバイス名>
DescribeRegions
APIを呼び出す時に、スイッチロールした先のプロファイル名(ここでは my-test-role
)を指定すると、そのままMFAコードを尋ねられます。入力するとAPI実行が成功します。
この方法の良いところは、実行したいAPIをそのままコールすればよく、デバイスの識別子(ARN)も ~/.aws/config
にあらかじめ保存してあるので毎回入力する必要がないことです。
Tips:OSSツールAWSumeで簡単にスイッチロール
MFAを強制するスイッチロールを簡単にするツールとして、OSSのAWSume もおすすめです。
必要なロール・ポリシー構成は「方法2」と同じで、ローカルでAWS SDKを利用する時にも一時的なクレデンシャル情報を簡単なコマンドでセットアップできたりするので便利です。
セットアップ後、awsume <プロファイル名>
で一時的な認証情報を取得でき、MFAが必要な場合はここで尋ねられます。そのあとは、一時的な認証情報を利用してAPIコールできるようになります。
まとめ
これまで説明したパターンを図にまとめてみました。
IAMアクセスキーは漏えいしないように細心の注意を払うことが大前提ですが、万が一漏えいしたとしてもすぐに使えないように、MFAで二重のガードをしておきましょう。
私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。
セキュリティエンジニア執筆:@kou.kinyo、レビュー:寺山 輝 (@terayama.akira)
(Shodoで執筆されました)