AWSのAPIアクセスをMFAで保護する

よく訓練されたアップル信者、都元です。突然ですがMFA使ってますか。使ってますよね。使ってますよね! 今どき大事なAWSアカウントをパスワードでしか保護しないのが許されるのは小(自粛

さて、本エントリーですが、下記の流れの追補的な位置づけになります。

なので前提知識として上記を読んでおいて頂きたいのですが、忙しい方には下記引用を。

まず、AWSにおけるクレデンシャルは大きく2種類に分かれます。

  • Sign-In Credential:Management Consoleログインのためのクレデンシャル(要するにパスワード)
  • Access Credentials:APIアクセスのためのクレデンシャル(要するにAPIキー)

MFAは、主にManagement Consoleへのログイン時、つまり「Sign-In Credential」に伴って使うもの、というのが多くの方の認識だと思います。しかしMFAはAccess Credentialsに対しても適用できることをご存知でしょうか。

MFA-Protected API アクセス

さて、一般的に「ログイン」のような操作を伴わないAccess Credentialsによるアクセスで、どのようにMFAを使うのでしょうか。具体的に見て行きましょう。

ここではまず、exampleというS3バケット内にセンシティブな情報が入っている想定をし、このバケットに対するGetObjectをMFAで保護したいという場合を考えます。このデータにアクセスする必要がある担当者のIAMユーザexample-userに、当該バケットへのアクセス権を付与しますが、この人のAccess Credentialsが万一流出しても、MFAデバイスの盗難等さえ無ければひとまずの情報保護ができるようにしたいわけです。

準備

まず、この担当者のIAMユーザには、MFAを設定しておきましょう。手順はAWSアカウントのセキュリティを強化する 〜 MFAの利用を御覧ください。

次に、担当者IAMユーザに付与するポリシーステートメントはこんな感じ。ポイントはConditionの部分ですね。この記述があることにより、当該バケットへのGetObjectアクセスは無条件に許可されるわけではなく、MFAによる認証を経た状態でなければ許可されなくなりました。

{
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example/*",
      "Condition": {
        "Null": { "aws:MultiFactorAuthAge": "false" }
      }
    

ちなみに、以下のようなConditionを付与すれば、MFA認証から10分以内等という、より厳しい条件を付けることもできます。

"Condition": { "NumericLessThan": { "aws:MultiFactorAuthAge": 600 } }

閑話休題。このポリシーを適用した担当者のAccess Credentialsで、GetObjectを試してみましょう。

$ aws s3api get-object --bucket example --key foo/bar/baz.txt /dev/null

A client error (AccessDenied) occurred when calling the GetObject operation: Access Denied

単純にアクセスするだけではアクセス拒否となることが確認できました。では、担当者が正規のアクセスを行う場合、MFAはどこで使えばいいのでしょうか。

その話題に入る前に、このIAMユーザに対して下記ポリシーステートメントを追加しておいてください。

{
      "Effect": "Allow",
      "Action": "sts:GetSessionToken",
      "Resource": "*"
   

正規のアクセス手順

AWSのAPIキーには、これもまた大きく分けて2種類があります。

  • long lived credentials (永続キー)
  • short lived session credentials (一時キー)

前者は一度発行した後は明示的に無効化するまで長期間有効なキーで、後者は短時間 *1で有効期限が切れるキーです。

IAMによるAWS権限管理運用ベストプラクティス (2)では、永続キーから一時キーを得るGetSessionTokenというAPIをご紹介しました。が、一体何に使うAPIアクションなのか、いまひとつパッとしない説明しかしていませんでした。

GetSessionTokenは下記の通り、永続キーから(とは限りませんが)一時キーを得るためのAPIです。

$ aws sts get-session-token
{
    "Credentials": {
        "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY",
        "SessionToken": "AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtpZ3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE",
        "Expiration": "2011-07-11T19:55:29Z",
        "AccessKeyId": "ASIAIOSFODNN7EXAMPLE"
    }
}

上記例は単に「永続キーから、1時間で有効期限が切れる一時キー」を作れただけですが、今回のように「MFA認証されていない永続キーから、MFA認証された状態の一時キーを得る」時に使えます。下記のように。

$ aws sts get-session-token \
  --serial-number arn:aws:iam::123456789012:mfa/example-user \
  --token-code 123456

このコマンドで得られたAccess Credentialsは、MFA認証を通った状態となりますので、GetObjectが許可される、というわけです。ちなみに、awscliで試す場合は、~/.aws/credentials上でaws_session_tokenというキーにセッショントークンを設定すれば、一時キーによるアクセスを行うこともできます。

[foobar]
aws_access_key_id = ...
aws_secret_access_key = ...
aws_session_token = ...

このキーを使ってGetObjectしてみます。

$ aws --profile foobar s3api get-object --bucket example --key "foo/bar/baz.txt" /dev/null
{
    "AcceptRanges": "bytes",
    "ContentType": "text/plain",
    "LastModified": "Thu, 18 Sep 2014 07:41:59 GMT",
    "ContentLength": "6",
    "ETag": "\"c24fed3164b2b594562a4729c2685053\""
}

問題なくアクセスできましたね。

まとめ

というわけで、少々運用はめんどくさくなりますが、Access CredentialsによるAPIアクセスについてもMFAによる保護が可能であることがお分かり頂けたかと思います。

ちなみに、この担当者が管理コンソールからログインした場合、MFAによる認証が済んだ状態になりますので、権限的にはexampleバケット内へのアクセスはシームレスに行うことができます。(ただ、上記の例ではGetObjectしか許していないので、ListBuckets等ができず、管理コンソールは上手く使えません。)

脚注

  1. 細かい種類によってレンジは異なるが、最長でも36時間、短い時は15分間。