AnsibleでAssumeRoleを行いbotoによるMFA入力を回避する
渡辺です。
複数のAWSアカウントに対し、IAMロールを管理しようとして色々ハマりました・・・。 管理方法として、Ansible、Terraform、CloudFormationと検討しましたが、最終的にAnsibleに落ち着きました。
前提として次のような制約があります。
- メインアカウントのみMFAを有効にしたIAMユーザを作成し、アクセスキー/シークレットキーは作成する
- 管理対象のAWSアカウントのアクセスキー/シークレットキーは作成しない
- 設定ファイルなどで構成管理を行う
なお、構成管理を行うには、Ansible以外に、CloudFormationやTerraformなどが選択肢としてあります。
iam_role モジュールとwith_items
Ansibleでは、AWS関連モジュールも多く提供されています。
はじめに、 iam_roleモジュールと、繰り返し処理を行う with_items
を利用してPlaybookを作成してみました。
- name: "IAM Roles" iam_role: name: "{{ item.name }}" assume_role_policy_document: Version: '2012-10-17' Statement: - Action: sts:AssumeRole Effect: Allow Principal: AWS: "arn:aws:iam::{{item.aws_account_id}}:root" Condition: Bool: "aws:MultiFactorAuthPresent": "true" managed_policy: - "AdministratorAccess" profile: "xxx" with_items: "{{ iam_roles }}"
iam_roles: - name: "role-1" aws_account_id: "XXXXXXXXXXXX" - name: "role-2" aws_account_id: "XXXXXXXXXXXX"
iam_roleモジュールでは、profile
にクレデンシャル情報に定義したプロファイル名を指定できます。
Ansibleの実装で利用しているboto3はprofileやAssumeRoleに対応しています。 次のように適切な設定が行われていれば、上手く動きます。
[default] aws_access_key_id = アクセスキーID aws_secret_access_key = シークレットアクセスキー
[profile xxxx] role_arn = arn:aws:iam::【対象AWSアカウントID】:role/yyyy mfa_serial = arn:aws:iam::【主AWSアカウントID】:mfa/zzzz source_profile = default
しかし、実行してみるとループ毎にMFAトークンの入力を求められてしまいます。 おそらく、モジュール毎に新しいプロセスが起動するため、AssumeRoleを行った情報が引き継がれないのでしょう。 これでは、使い物になりません・・・。
sts_assume_roleでAssuemRoleを行う
iam_roleモジュールで、profile
を指定した場合、クレデンシャル情報を元にPythonのbotoでAssumeRoleが行われます。
通常はこの仕組みを利用すれば良いでしょう。
しかし、今回のようにMFAを有効化している場合は、sts_assume_roleモジュールでAssumeRoleを行うと上手くいきます。
- name: "Assume Role" sts_assume_role: role_arn: "{{ role_arn }}" role_session_name: "ansible-session" mfa_serial_number: "{{ mfa_serial }}" mfa_token: "{{ mfa_token }}" region: "ap-northeast-1" register: assumed_role changed_when: False - debug: var: assumed_role verbosity: 1
sts_assume_roleモジュールでは、role_arn
を指定し、AssumeRoleを行います。
変数は、group_varsなどに設定しておきましょう。
MFAを有効にしている場合は、mfa_serial_number
とmfa_token
が必要です。
mfa_token
は実行時のトークンを指定するため、コマンドライン引数で指定します。
$ ansible-playbook iam-role.yml --extra-vars="mfa_token=000000"
AssumeRoleを行った時の情報は、変数assumed_role
に登録されます。
TASK [debug] *************************************************************************************** ok: [localhost] => { "assumed_role": { "changed": false, "failed": false, "sts_creds": { "access_key": "XXXXXXXXXXXXXXXXXXXXXX", "expiration": "2018-02-07T08:02:10Z", "parent": null, "request_id": null, "secret_key": "zzzzzzzzzzzzzzzzzzzzzzzzz", "session_token": "xxxxxxxxxxxxxxxxxxxx==" }, "sts_user": { "arn": "arn:aws:sts::XXXXXXXXXXXX:assumed-role/zzzzzzzzzzz/ansible-session", "assume_role_id": "HHHHHHHHHHHHHHHH:ansible-session" } } }
AssuemRoleした情報をiam_roleモジュールに設定する
AssumeRoleを行うと、一時的なクレデンシャル情報が発行されます。
すなわち、access_key
、secret_key
、session_token
です。
これらをiam_roleモジュールに設定してください。
- name: "IAM Roles" iam_role: name: "{{ item.name }}" assume_role_policy_document: Version: '2012-10-17' Statement: - Action: sts:AssumeRole Effect: Allow Principal: AWS: "arn:aws:iam::{{item.aws_account_id}}:root" Condition: Bool: "aws:MultiFactorAuthPresent": "true" managed_policy: - "AdministratorAccess" aws_access_key: "{{ assumed_role.sts_creds.access_key }}" aws_secret_key: "{{ assumed_role.sts_creds.secret_key }}" security_token: "{{ assumed_role.sts_creds.session_token }}" with_items: "{{ iam_roles }}"
これでコマンド実行時に1回だけMFAトークンを指定すれば、一時的なクレデンシャル情報を再利用してモジュールを実行可能です。
まとめ
AssumeRoleやスイッチロールは、複数のAWSアカウントを運用する上で便利な機能です。 ツールでは、AssumeRoleを意識せずに利用できることが多いと思います。 しかし、MFAが有効な場合、ツールなどが利用する時に、ハマりやすいポイントとなります。
そのような場合は、AssumeRoleの仕組みを思い出してください。
一時的なクレデンシャル情報を取得するのがAssumeRoleです。
単発でAssumeRoleを行い、access_key
、secret_key
、session_token
を取得できれば、ツールで上手く利用できるかもしれません。
最後にリファクタリングしたIAM Roleを管理するPlaybookを置いておきます。 MFAのシリアルはiam_mfa_device_factsモジュールで取得できます。
--- - hosts: localhost connection: local gather_facts: False become: False tasks: - iam_mfa_device_facts: register: mfa_devices changed_when: False - debug: var: mfa_devices verbosity: 1 - name: "Assume Role" sts_assume_role: role_arn: "arn:aws:iam::{{ aws_account_id }}:role/{{ user_name }}" role_session_name: "ansible-session" mfa_serial_number: "{{ mfa_devices.mfa_devices[0].serial_number }}" mfa_token: "{{ mfa_token }}" region: "ap-northeast-1" register: assumed_role changed_when: False - debug: var: assumed_role verbosity: 1 - name: "IAM Roles" iam_role: name: "{{ item.name }}" state: "{{ item.state }}" assume_role_policy_document: Version: '2012-10-17' Statement: - Action: sts:AssumeRole Effect: Allow Principal: AWS: "arn:aws:iam::{{item.aws_account_id}}:root" Condition: Bool: "aws:MultiFactorAuthPresent": "true" managed_policy: - "AdministratorAccess" aws_access_key: "{{ assumed_role.sts_creds.access_key }}" aws_secret_key: "{{ assumed_role.sts_creds.secret_key }}" security_token: "{{ assumed_role.sts_creds.session_token }}" with_items: "{{ iam_roles }}"
--- aws_account_id: "XXXXXXXXXXXX" user_name: "role-name-when-assume-role" iam_roles: - name: "role-1" aws_account_id: "XXXXXXXXXXXX" state: present - name: "role-2" aws_account_id: "XXXXXXXXXXXX" state: present - name: "role-3" aws_account_id: "XXXXXXXXXXXX" state: absent