Repository: ansible/ansible

Issue:
sts_assume_role throws TypeError assume_role() takes at most 6 arguments (8 given)\r\n", "msg": "MODULE FAILURE"} 
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME

- sts_assume_role

##### ANSIBLE VERSION
```
ansible 2.2.0.0
  config file = /Users/revprez/.ansible.cfg
  configured module search path = Default w/o overrides
```

##### CONFIGURATION

- Using default configuration

##### OS / ENVIRONMENT

- **Source**: macOS Sierra (10.12.5)
- **Target**: Centos 7

##### SUMMARY

Configuring sts_assume_role results in a `TypeError` concerning the arguments supplied to a function called `assume_role`.  Specifically: `assume_role() takes at most 6 arguments (8 given)`.


##### STEPS TO REPRODUCE

Configure [cross account access](http://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_cross-account-with-roles.html) between two accounts (referred to from here on out as **Account-A** and **Account-B**). Say, **Account-A** has a cross-account-role `some-cross-account-role` with a trust relationship with **Account-B**, and Account-B has an instance launched with a role that attaches a policy permitting assuming of `some-cross-account-role` in **Account-A**.

Next, configure a playbook with the following tasks:
```
- name: Install Base Packages
  yum:
    name: "{{ item }}"
    state: present
  with_items:
    - python2-boto3

- name: Assume role
  sts_assume_role:
    role_arn: "arn:aws:iam::ACCOUNT-A-ID:role/some-cross-account-role"
    role_session_name: "some-session-id"
    region: "us-east-1"
  register: assumed_role

- name: Debug role
  debug: "msg={{ assumed_role }}"
```

Finally, execute playbook against some target in **Account-B**.

##### EXPECTED RESULTS

`assumed_role` is registered and populated with data necessary.

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
TASK [******** : Assume role] ******************************************
fatal: [10.0.50.110]: FAILED! => {"changed": false, "failed": true, "module_stderr": "Shared connection to 10.0.50.110 closed.\r\n", "module_stdout": "Traceback (most recent call last):\r\n  File \"/tmp/ansible_yTSWAA/ansible_module_sts_assume_role.py\", line 153, in <module>\r\n    main()\r\n  File \"/tmp/ansible_yTSWAA/ansible_module_sts_assume_role.py\", line 147, in main\r\n    assume_role_policy(connection, module)\r\n  File \"/tmp/ansible_yTSWAA/ansible_module_sts_assume_role.py\", line 110, in assume_role_policy\r\n    assumed_role = connection.assume_role(role_arn, role_session_name, policy, duration_seconds, external_id, mfa_serial_number, mfa_token)\r\nTypeError: assume_role() takes at most 6 arguments (8 given)\r\n", "msg": "MODULE FAILURE"}
```


Discussion:
Also having this issue. Same setup.
cc @bekelchik
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/cloud/amazon/sts_assume_role.py b/lib/ansible/modules/cloud/amazon/sts_assume_role.py
--- a/lib/ansible/modules/cloud/amazon/sts_assume_role.py
+++ b/lib/ansible/modules/cloud/amazon/sts_assume_role.py
@@ -18,7 +18,9 @@
 description:
     - Assume a role using AWS Security Token Service and obtain temporary credentials
 version_added: "2.0"
-author: Boris Ekelchik (@bekelchik)
+author:
+    - Boris Ekelchik (@bekelchik)
+    - Marek Piatek (@piontas)
 options:
   role_arn:
     description:
@@ -60,6 +62,33 @@
 extends_documentation_fragment:
     - aws
     - ec2
+requirements:
+    - boto3
+    - botocore
+    - python >= 2.6
+'''
+
+RETURN = '''
+sts_creds:
+    description: The temporary security credentials, which include an access key ID, a secret access key, and a security (or session) token
+    returned: always
+    type: dict
+    sample:
+      access_key: XXXXXXXXXXXXXXXXXXXX
+      expiration: 2017-11-11T11:11:11+00:00
+      secret_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+      session_token: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+sts_user:
+    description: The Amazon Resource Name (ARN) and the assumed role ID
+    returned: always
+    type: dict
+    sample:
+      assumed_role_id: arn:aws:sts::123456789012:assumed-role/demo/Bob
+      arn: ARO123EXAMPLE123:Bob
+changed:
+    description: True if obtaining the credentials succeeds
+    type: bool
+    returned: always
 '''
 
 EXAMPLES = '''
@@ -83,35 +112,53 @@
 
 '''
 
+from ansible.module_utils.aws.core import AnsibleAWSModule
+from ansible.module_utils.ec2 import (boto3_conn, get_aws_connection_info,
+                                      ec2_argument_spec, camel_dict_to_snake_dict)
+
 try:
-    import boto.sts
-    from boto.exception import BotoServerError
-    HAS_BOTO = True
+    from botocore.exceptions import ClientError, ParamValidationError
 except ImportError:
-    HAS_BOTO = False
+    pass  # caught by imported AnsibleAWSModule
 
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.ec2 import AnsibleAWSError, connect_to_aws, ec2_argument_spec, get_aws_connection_info
 
+def _parse_response(response):
+    credentials = response.get('Credentials', {})
+    user = response.get('AssumedRoleUser', {})
+
+    sts_cred = {
+        'access_key': credentials.get('AccessKeyId'),
+        'secret_key': credentials.get('SecretAccessKey'),
+        'session_token': credentials.get('SessionToken'),
+        'expiration': credentials.get('Expiration')
+
+    }
+    sts_user = camel_dict_to_snake_dict(user)
+    return sts_cred, sts_user
 
-def assume_role_policy(connection, module):
 
-    role_arn = module.params.get('role_arn')
-    role_session_name = module.params.get('role_session_name')
-    policy = module.params.get('policy')
-    duration_seconds = module.params.get('duration_seconds')
-    external_id = module.params.get('external_id')
-    mfa_serial_number = module.params.get('mfa_serial_number')
-    mfa_token = module.params.get('mfa_token')
+def assume_role_policy(connection, module):
+    params = {
+        'RoleArn': module.params.get('role_arn'),
+        'RoleSessionName': module.params.get('role_session_name'),
+        'Policy': module.params.get('policy'),
+        'DurationSeconds': module.params.get('duration_seconds'),
+        'ExternalId': module.params.get('external_id'),
+        'SerialNumber': module.params.get('mfa_serial_number'),
+        'TokenCode': module.params.get('mfa_token')
+    }
     changed = False
 
+    kwargs = dict((k, v) for k, v in params.items() if v is not None)
+
     try:
-        assumed_role = connection.assume_role(role_arn, role_session_name, policy, duration_seconds, external_id, mfa_serial_number, mfa_token)
+        response = connection.assume_role(**kwargs)
         changed = True
-    except BotoServerError as e:
-        module.fail_json(msg=e)
+    except (ClientError, ParamValidationError) as e:
+        module.fail_json_aws(e)
 
-    module.exit_json(changed=changed, sts_creds=assumed_role.credentials.__dict__, sts_user=assumed_role.user.__dict__)
+    sts_cred, sts_user = _parse_response(response)
+    module.exit_json(changed=changed, sts_creds=sts_cred, sts_user=sts_user)
 
 
 def main():
@@ -128,25 +175,18 @@ def main():
         )
     )
 
-    module = AnsibleModule(argument_spec=argument_spec)
-
-    if not HAS_BOTO:
-        module.fail_json(msg='boto required for this module')
+    module = AnsibleAWSModule(argument_spec=argument_spec)
 
-    region, ec2_url, aws_connect_params = get_aws_connection_info(module)
+    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True)
 
     if region:
-        try:
-            connection = connect_to_aws(boto.sts, region, **aws_connect_params)
-        except (boto.exception.NoAuthHandlerFound, AnsibleAWSError) as e:
-            module.fail_json(msg=str(e))
+        connection = boto3_conn(module, conn_type='client', resource='sts',
+                                region=region, endpoint=ec2_url, **aws_connect_kwargs)
+
     else:
         module.fail_json(msg="region must be specified")
 
-    try:
-        assume_role_policy(connection, module)
-    except BotoServerError as e:
-        module.fail_json(msg=e)
+    assume_role_policy(connection, module)
 
 
 if __name__ == '__main__':


Test patch:
diff --git a/hacking/aws_config/testing_policies/sts-policy.json b/hacking/aws_config/testing_policies/sts-policy.json
new file mode 100644
--- /dev/null
+++ b/hacking/aws_config/testing_policies/sts-policy.json
@@ -0,0 +1,23 @@
+{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Sid": "AllowSTSAnsibleTests",
+            "Action": [
+                "iam:Get*",
+                "iam:List*",
+                "iam:CreateRole",
+                "iam:DeleteRole",
+                "iam:DetachRolePolicy",
+                "sts:AssumeRole",
+                "iam:AttachRolePolicy",
+                "iam:CreateInstanceProfile"
+            ],
+            "Effect": "Allow",
+            "Resource": [
+                "arn:aws:iam::{{aws_account}}:role/ansible-test-sts-*",
+                "arn:aws:iam::{{aws_account}}:instance-profile/ansible-test-sts-*"
+            ]
+        }
+    ]
+}
diff --git a/test/integration/targets/sts_assume_role/aliases b/test/integration/targets/sts_assume_role/aliases
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/sts_assume_role/aliases
@@ -0,0 +1,2 @@
+cloud/aws
+posix/ci/cloud/group1/aws
diff --git a/test/integration/targets/sts_assume_role/meta/main.yml b/test/integration/targets/sts_assume_role/meta/main.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/sts_assume_role/meta/main.yml
@@ -0,0 +1,3 @@
+dependencies:
+  - prepare_tests
+  - setup_ec2
diff --git a/test/integration/targets/sts_assume_role/tasks/main.yml b/test/integration/targets/sts_assume_role/tasks/main.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/sts_assume_role/tasks/main.yml
@@ -0,0 +1,384 @@
+---
+# tasks file for sts_assume_role
+
+- block:
+
+    # ============================================================
+    # TODO create simple ansible sts_get_caller_identity module
+    - blockinfile:
+        path: "{{ output_dir }}/sts.py"
+        create: yes
+        block: |
+          #!/usr/bin/env python
+          import boto3
+          sts = boto3.client('sts')
+          response = sts.get_caller_identity()
+          print(response['Account'])
+
+    - name: get the aws account id
+      command: python "{{ output_dir }}/sts.py"
+      environment:
+        AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
+        AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
+        AWS_SESSION_TOKEN: "{{ security_token }}"
+      register: result
+
+    - name: register account id
+      set_fact:
+        aws_account: "{{ result.stdout | replace('\n', '') }}"
+
+    # ============================================================
+    - name: create test iam role
+      iam_role:
+        aws_access_key: "{{ aws_access_key }}"
+        aws_secret_key: "{{ aws_secret_key }}"
+        security_token: "{{ security_token }}"
+        name: "ansible-test-sts-{{ resource_prefix }}"
+        assume_role_policy_document:  "{{ lookup('template','policy.json.j2') }}"
+        create_instance_profile: False
+        managed_policy:
+          - arn:aws:iam::aws:policy/IAMReadOnlyAccess
+        state: present
+      register: test_role
+
+    # ============================================================
+    - name: pause to ensure role exists before using
+      pause:
+        seconds: 30
+
+    # ============================================================
+    - name: test with no parameters
+      sts_assume_role:
+      register: result
+      ignore_errors: true
+
+    - name: assert with no parameters
+      assert:
+        that:
+           - 'result.failed'
+           - "'missing required arguments:' in result.msg"
+
+    # ============================================================
+    - name: test with empty parameters
+      sts_assume_role:
+        aws_access_key: "{{ aws_access_key }}"
+        aws_secret_key: "{{ aws_secret_key }}"
+        security_token: "{{ security_token }}"
+        region: "{{ aws_region}}"
+        role_arn:
+        role_session_name:
+        policy:
+        duration_seconds:
+        external_id:
+        mfa_token:
+        mfa_serial_number:
+      register: result
+      ignore_errors: true
+
+    - name: assert with empty parameters
+      assert:
+        that:
+           - 'result.failed'
+           - "'Missing required parameter in input:' in result.msg"
+      when: result.module_stderr is not defined
+
+    - name: assert with empty parameters
+      assert:
+        that:
+          - 'result.failed'
+          - "'Member must have length greater than or equal to 20' in result.module_stderr"
+      when: result.module_stderr is defined
+
+    # ============================================================
+    - name: test with only 'role_arn' parameter
+      sts_assume_role:
+        aws_access_key: "{{ aws_access_key }}"
+        aws_secret_key: "{{ aws_secret_key }}"
+        security_token: "{{ security_token }}"
+        role_arn: "{{ test_role.iam_role.arn }}"
+      register: result
+      ignore_errors: true
+
+    - name: assert with only 'role_arn' parameter
+      assert:
+        that:
+           - 'result.failed'
+           - "'missing required arguments: role_session_name' in result.msg"
+
+    # ============================================================
+    - name: test with only 'role_session_name' parameter
+      sts_assume_role:
+        aws_access_key: "{{ aws_access_key }}"
+        aws_secret_key: "{{ aws_secret_key }}"
+        security_token: "{{ security_token }}"
+        role_session_name: "AnsibleTest"
+      register: result
+      ignore_errors: true
+
+    - name: assert with only 'role_session_name' parameter
+      assert:
+        that:
+           - 'result.failed'
+           - "'missing required arguments: role_arn' in result.msg"
+
+    # ============================================================
+    - name: test assume role with invalid policy
+      sts_assume_role:
+        aws_access_key: "{{ aws_access_key }}"
+        aws_secret_key: "{{ aws_secret_key }}"
+        security_token: "{{ security_token }}"
+        region: "{{ aws_region }}"
+        role_arn: "{{ test_role.iam_role.arn }}"
+        role_session_name: "AnsibleTest"
+        policy: "invalid policy"
+      register: result
+      ignore_errors: true
+
+    - name: assert assume role with invalid policy
+      assert:
+        that:
+          - 'result.failed'
+          - "'The policy is not in the valid JSON format.' in result.msg"
+      when: result.module_stderr is not defined
+
+    - name: assert assume role with invalid policy
+      assert:
+        that:
+          - 'result.failed'
+          - "'The policy is not in the valid JSON format.' in result.module_stderr"
+      when: result.module_stderr is defined
+
+    # ============================================================
+    - name: test assume role with invalid duration seconds
+      sts_assume_role:
+        aws_access_key: "{{ aws_access_key }}"
+        aws_secret_key: "{{ aws_secret_key }}"
+        security_token: "{{ security_token }}"
+        region: "{{ aws_region}}"
+        role_arn: "{{ test_role.iam_role.arn }}"
+        role_session_name: AnsibleTest
+        duration_seconds: invalid duration
+      register: result
+      ignore_errors: true
+
+    - name: assert assume role with invalid duration seconds
+      assert:
+        that:
+          - 'result.failed'
+          - "'unable to convert to int: invalid literal for int()' in result.msg"
+
+    # ============================================================
+    - name: test assume role with invalid external id
+      sts_assume_role:
+        aws_access_key: "{{ aws_access_key }}"
+        aws_secret_key: "{{ aws_secret_key }}"
+        security_token: "{{ security_token }}"
+        region: "{{ aws_region}}"
+        role_arn: "{{ test_role.iam_role.arn }}"
+        role_session_name: AnsibleTest
+        external_id: invalid external id
+      register: result
+      ignore_errors: true
+
+    - name: assert assume role with invalid external id
+      assert:
+        that:
+          - 'result.failed'
+          - "'Member must satisfy regular expression pattern:' in result.msg"
+      when: result.module_stderr is not defined
+
+    - name: assert assume role with invalid external id
+      assert:
+        that:
+          - 'result.failed'
+          - "'Member must satisfy regular expression pattern:' in result.module_stderr"
+      when: result.module_stderr is defined
+
+    # ============================================================
+    - name: test assume role with invalid mfa serial number
+      sts_assume_role:
+        aws_access_key: "{{ aws_access_key }}"
+        aws_secret_key: "{{ aws_secret_key }}"
+        security_token: "{{ security_token }}"
+        region: "{{ aws_region}}"
+        role_arn: "{{ test_role.iam_role.arn }}"
+        role_session_name: AnsibleTest
+        mfa_serial_number: invalid serial number
+      register: result
+      ignore_errors: true
+
+    - name: assert assume role with invalid mfa serial number
+      assert:
+        that:
+          - 'result.failed'
+          - "'Member must satisfy regular expression pattern:' in result.msg"
+      when: result.module_stderr is not defined
+
+    - name: assert assume role with invalid mfa serial number
+      assert:
+        that:
+          - 'result.failed'
+          - "'Member must satisfy regular expression pattern:' in result.module_stderr"
+      when: result.module_stderr is defined
+
+    # ============================================================
+    - name: test assume role with invalid mfa token code
+      sts_assume_role:
+        aws_access_key: "{{ aws_access_key }}"
+        aws_secret_key: "{{ aws_secret_key }}"
+        security_token: "{{ security_token }}"
+        region: "{{ aws_region}}"
+        role_arn: "{{ test_role.iam_role.arn }}"
+        role_session_name: AnsibleTest
+        mfa_token: invalid token code
+      register: result
+      ignore_errors: true
+
+    - name: assert assume role with invalid mfa token code
+      assert:
+        that:
+          - 'result.failed'
+          - "'Member must satisfy regular expression pattern:' in result.msg"
+      when: result.module_stderr is not defined
+
+    - name: assert assume role with invalid mfa token code
+      assert:
+        that:
+          - 'result.failed'
+          - "'Member must satisfy regular expression pattern:' in result.module_stderr"
+      when: result.module_stderr is defined
+
+    # ============================================================
+    - name: test assume role with invalid role_arn
+      sts_assume_role:
+        aws_access_key: "{{ aws_access_key }}"
+        aws_secret_key: "{{ aws_secret_key }}"
+        security_token: "{{ security_token }}"
+        region: "{{ aws_region}}"
+        role_arn: invalid role arn
+        role_session_name: AnsibleTest
+      register: result
+      ignore_errors: true
+
+    - name: assert assume role with invalid role_arn
+      assert:
+        that:
+          - result.failed
+          - "'Invalid length for parameter RoleArn' in result.msg"
+      when: result.module_stderr is not defined
+
+    - name: assert assume role with invalid role_arn
+      assert:
+        that:
+          - 'result.failed'
+          - "'Member must have length greater than or equal to 20' in result.module_stderr"
+      when: result.module_stderr is defined
+
+    # ============================================================
+    - name: test assume not existing sts role
+      sts_assume_role:
+        aws_access_key: "{{ aws_access_key }}"
+        aws_secret_key: "{{ aws_secret_key }}"
+        security_token: "{{ security_token }}"
+        region: "{{ aws_region}}"
+        role_arn: "arn:aws:iam::123456789:role/non-existing-role"
+        role_session_name: "AnsibleTest"
+      register: result
+      ignore_errors: true
+
+    - name: assert assume not existing sts role
+      assert:
+        that:
+          - 'result.failed'
+          - "'Not authorized to perform sts:AssumeRole' in result.msg"
+      when: result.module_stderr is not defined
+
+    - name: assert assume not existing sts role
+      assert:
+        that:
+          - 'result.failed'
+          - "'Not authorized to perform sts:AssumeRole' in result.module_stderr"
+      when: result.module_stderr is defined
+
+    # ============================================================
+    - name: test assume role
+      sts_assume_role:
+        aws_access_key: "{{ aws_access_key }}"
+        aws_secret_key: "{{ aws_secret_key }}"
+        security_token: "{{ security_token }}"
+        region: "{{ aws_region }}"
+        role_arn: "{{ test_role.iam_role.arn }}"
+        role_session_name: AnsibleTest
+      register: assumed_role
+
+    - name: assert assume role
+      assert:
+        that:
+           - 'not assumed_role.failed'
+           - "'sts_creds' in assumed_role"
+           - "'access_key' in assumed_role.sts_creds"
+           - "'secret_key' in assumed_role.sts_creds"
+           - "'session_token' in assumed_role.sts_creds"
+
+    # ============================================================
+    - name: test that assumed credentials have IAM read-only access
+      iam_role:
+        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 }}"
+        region: "{{ aws_region}}"
+        name: "ansible-test-sts-{{ resource_prefix }}"
+        assume_role_policy_document: "{{ lookup('template','policy.json.j2') }}"
+        create_instance_profile: False
+        state: present
+      register: result
+
+    - name: assert assumed role with privileged action (expect changed=false)
+      assert:
+        that:
+          - 'not result.failed'
+          - 'not result.changed'
+          - "'iam_role' in result"
+
+    # ============================================================
+    - name: test assumed role with unprivileged action
+      iam_role:
+        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 }}"
+        region: "{{ aws_region}}"
+        name: "ansible-test-sts-{{ resource_prefix }}-new"
+        assume_role_policy_document: "{{ lookup('template','policy.json.j2') }}"
+        state: present
+      register: result
+      ignore_errors: true
+
+    - name: assert assumed role with unprivileged action (expect changed=false)
+      assert:
+        that:
+          - 'result.failed'
+          - "'is not authorized to perform: iam:CreateRole' in result.msg"
+      # runs on Python2
+      when: result.module_stderr is not defined
+
+    - name: assert assumed role with unprivileged action (expect changed=false)
+      assert:
+        that:
+          - 'result.failed'
+          - "'is not authorized to perform: iam:CreateRole' in result.module_stderr"
+      # runs on Python3
+      when: result.module_stderr is defined
+
+    # ============================================================
+  always:
+
+    - name: delete test iam role
+      iam_role:
+        aws_access_key: "{{ aws_access_key }}"
+        aws_secret_key: "{{ aws_secret_key }}"
+        security_token: "{{ security_token }}"
+        name: "ansible-test-sts-{{ resource_prefix }}"
+        assume_role_policy_document:  "{{ lookup('template','policy.json.j2') }}"
+        managed_policy:
+          - arn:aws:iam::aws:policy/IAMReadOnlyAccess
+        state: absent
diff --git a/test/integration/targets/sts_assume_role/templates/policy.json.j2 b/test/integration/targets/sts_assume_role/templates/policy.json.j2
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/sts_assume_role/templates/policy.json.j2
@@ -0,0 +1,12 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Principal": {
+        "AWS": "arn:aws:iam::{{ aws_account }}:root"
+      },
+      "Action": "sts:AssumeRole"
+    }
+  ]
+}
\ No newline at end of file