AWS Cloudformation と IAM Policy での IP アドレス制限

AWS で、マネージメントコンソールや API 等で AWS 上のリソースの操作を特定の IP アドレスからしか実行できないようにするために、IAM ポリシーの aws:SourceIp を使って制限する、というのは常套手段として知られていると思います。

AWS管理コンソールへのアクセスをIPアドレスで制限したい | Developers.IO

このページの最後でも書かれていますが、この常套句の設定は CloudFormation を使ったスタックの生成、更新、削除といった時に問題になります。

AWS Identity and Access Management によるアクセスの制御 - AWS CloudFormation

aws:SourceIp 条件を使用しないでください。AWS CloudFormation はリクエストの送信元 IP アドレスではなく、独自の IP アドレスを使用してリソースをプロビジョニングします。たとえば、スタックを作成する際、Amazon EC2 インスタンスを起動したり Amazon S3 バケットを作成したりするために、CreateStack 呼び出しや aws cloudformation create-stack コマンドによって得られる IP アドレスではなく、スタックの IP アドレスからリクエストを行います。

(2015 年 3 月現在)

「独自の IP アドレスを使用してリソースをプロビジョニングします」といっている「独自の IP アドレス」というのが分かれば、それを許可してやれば、などと思ったのですが、公開されている AWS の IP アドレスの範囲*1を試したり、プライベートアドレスの範囲を指定したりして見ましたが、うまくいきませんでした。

Cloud Trail で見ても、Source IP は cloudformation.amazonaws.com となっていて、この名前で DNS に問い合わせても A レコードは持っていません。

AWS で su

UNIX 系 OS であれば、ホストのファイヤーウォール設定や、/etc/hosts.allow, /etc/hosts.deny を使って、特定の IP アドレスから接続を許可し、/etc/ssh/sshd_config で root でのログインを拒否して、一般ユーザでログインするようにしておいて、特権が必要な時には su で root になるか、sudo を使う、という運用をしているケースが多いと思います。

これと同じように、「通常は、制限されたアクセス権で、必要に応じて大きな権利を取得する」という手段が AWS に用意されています。それが「AssumeRole」です。

AssumeRole は、他の AWS アカウントを操作する為の権限を設定させたり、外部の認証基盤を使った認証処理を行う、という文脈で説明されることが多いです。

しかし、別の AWS アカウントじゃなくて、自分自身の AWS アカウントを指定しても OK?、と思ったきっかけがありました。

AWS CLIがAssumeRoleによる自動クレデンシャル取得とMFAに対応しました! | Developers.IO

作成した IAM アカウントに MFA を設定して、マネージメントコンソールに対しては MFA 無しでは利用できないことは確認したのですが、AWS CLI の時にはどうなるんだ? と思ってたどり着いたのが上記の記事です。

これを読むと、あるバージョンの AWS CLI からは、MFA が必要な時にはそのトークンを要求する、といった動きができるようになったことが分かります。

よく読むと、信頼ポリシー(trust plolicy)で「MFA での認証に成功している場合に AssumeRole を許可」としています。じゃぁ、この条件を「特定の IP アドレスからの場合だったら」としてみたら...

ということで、思いついたシナリオは以下の通りです。

  • IAM ユーザに、AssumeRole を許可したポリシーを割り当てる。
  • 信頼ポリシーで、先の「MFA での認証が〜」の条件の所を、「特定の IP アドレスだったら〜」に変えたものを設定する。

実際にやってみる

まず、IAM ロールを作成します。ロール名を入力して、ロールタイプを選択する画面が出たら、「Role for Cross-Account Access」の「Provide access between AWS accounts you own」を選択します。元々は「あなたが持っている AWS アカウント間でのアクセスを提供」という意味ですが、持っているアカウントが1つでもこれを選択します*2

次に、「どのアカウントに対してアクセスを提供するか?」を指定するのですが、本来のクロスアカウントでの利用であれば、アクセスを付与する先の AWS アカウント番号を入れる所に、自分自身のアカウント番号を入力します。

上記画面の「Require MFA」をチェックすれば、前述の「AWS CLI で MFA を使う」ための信頼ポリシーが生成されます。

後は、付与したい権利の内容を選択すれば、ロールの作成はひとまず完了です。

作成したロールの内容を確認すると、画面の下の方にある「Trust Relationship」で生成された信頼ポリシーが確認できます。

実際に生成されたポリシーは、こんな感じになります。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::自分の AWS Account ID:root"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

これを編集して、Condition 句で IP アドレスを指定します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::自分の AWS Account ID:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "xxx.xxx.xxx.xxx/xx"
        }
      }
    }
  ]
}

これで、ロールの準備は OK です。

次に、IAM ユーザに割り当てる為のポリシーを用意します。任意のロールに切り替えられるのを過剰な権利なので、Resource に先に作ったロールを指定します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": "arn:aws:iam::自分の AWS Account ID:role/先に作ったロール名"
        }
    ]
}

後は、このポリシーをユーザに適用すれば OK です。

使ってみる

分かりやすいように、前述の AssumeRole を許可したポリシーだけをユーザに付与した状態にしてみます。AssumeRole が許可されているだけなので、信頼ポリシーで許可されているアドレスか否かにかかわらず、AWS 上のリソースに対する一切の操作、一切の情報取得ができない状態になっています。

で、許可された IP アドレスからアクセスすると、ロールを切り替えることができます。

マネージメントコンソールでロールを切り替えるのは、画面右上のログイン中のアカウント情報を表示している所からメニューを表示させて、「Switch Role」を選択します。

ロールの切り替え画面では、切り替え先の AWS アカウントと、先に作ったロールの名称を入力します。

*3

「表示名」の欄は、アカウントとロールを入力すれば「ロール@アカウント」の形式になります。

で、実際に切り替わると、下図のように、それまで「IAM ユーザ名@アカウント」だったのが、「ロール名@アカウント」になっています*4。実際に、ロールを切り替える前にでは軒並み「権利がない」と言われていたのが、切り替え後はきちんと表示されています。

  • ロール切り替え前:
  • ロール切り替え後:

信頼ポリシーで指定した IP アドレスとは違うところからログインしてロールを切り替えると、下図のように切り替えに失敗します。

ロールを切り替えられれば、そのロールで付与されているポリシーが適用されます。この時、ロールに付与したポリシーに aws:SourceIp を使わなければ、CloudFormation でスタック生成も問題なく実行できます。

AWS CLI での利用

先に紹介した、AWS CLI で MFA を使うケースに似ています。

AWS CLI のプロファイルで

  • 元になるプロファイル(source_profile)
  • 切り替え先のロール(role_arn)

を指定した物を作成し、実際の処理の際に「--profile プロファイル名」を付けて実行する事になります。MFA との例の違いは MFA デバイスを指定する「mfa_serial」の有無だけです。

直接、ファイルを編集しても良いですが、AWS CLI を使って必要な設定を書き込む事もできます。

今、AssumeRole が許可されたユーザのアクセスキー、シークレットキーがデフォルトで使われるように設定されているとします。

ここに「admin」というプロファイルを追加して、AdministrationRole という名前のロールへ切り替えるようにするには、下記のように AWS CLI を実行します。なお、AWS Account ID が 111122223333 だったとします。

aws configure set profile.admin.region ap-northeast-1
aws configure set profile.admin.role_arn arn:aws:iam::111122223333:role/AdministrationRole
aws configure set profile.admin.source_profile default

これで、config ファイルの中身は下記のようになります。

[default]
region = ap-northeast-1
[profile admin]
region = ap-northeast-1
source_profile = default
role_arn = arn:aws:iam::111122223333:role/AdminRole

この状態で「aws cloudformation --profile admin create-stack --stack-name ......」とすれば、スタックの生成ができます。

注意点

AssumeRole で切り替えた後の権限は、切り替え先のロールに全面的に切り替わります。

aws:SourceIp を使ったポリシーを設定する際、「このアドレスに合致したら許可する」ではなく「このアドレスに合致しなかったら、全てを拒否する」というポリシーを適用するのが良い、と言われます。複数のポリシーが割り当てられている場合、先に「拒否」のポリシーを評価するので、「このアドレスに合致しなかったらすべてを拒否する」というポリシーがあれば、他のポリシーで IP アドレス制限がなくても、確実に制限できます。

故に、CloudFormation の実行の際に問題になった訳ですが、切り替え先のロールに全面的に切り替わるからこそ、元々のポリシーに制限があっても、CloudFormation のスタック操作に成功する事になります。

ですので、切替後の操作に制限を加えたければ、あくまでも、ロールの中身で制限する必要があります。切替前のポリシーは切替後には反映されない事を十分に注意する必要があります。

*1:AWS IP アドレスの範囲 - アマゾン ウェブ サービス

*2:実はドキュメントにこのケースがきちんと書かれていました。IAM ユーザーにアクセス権限を委任するロールの作成 - AWS Identity and Access Management「このオプションは、ユーザー、ロール、アクセスされるリソースがすべて同じアカウントに属している場合にも選択します。」

*3:なぜかこの画面は日本語...

*4:切り替え先のロールを指定する画面で「表示名」に設定したものになる、はずです(試してない)。