Repository: ansible-collections/community.aws

Issue:
Cannot connect to ec2 instance via aws_ssm if AWS_SESSION_TOKEN is missing
<!--- Verify first that your issue is not already reported on GitHub -->
<!--- Also test if the latest release and devel branch are affected too -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
<!--- Explain the problem briefly below -->
Cannot connect to ec2 instance via aws_ssm if AWS_SESSION_TOKEN is not set.
(I do not configure any AWS variables in OS via `aws configure`)

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below, use your best guess if unsure -->
`ansible-collections/community.aws/blob/main/plugins/connection/aws_ssm.py
`
##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes -->
```paste below
ansible 2.9.9
  config file = None
  configured module search path = ['/Users/it-ops/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.7/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.7.7 (default, Mar 10 2020, 15:43:03) [Clang 11.0.0 (clang-1100.0.33.17)]
```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```paste below

```

##### OS / ENVIRONMENT
<!--- Provide all relevant information below, e.g. target OS versions, network device firmware, etc. -->
OSX 10.14.6 (18G3020)

##### STEPS TO REPRODUCE
<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->
I created a role to ping ec2 instance.
<!--- Paste example playbooks or commands between quotes below -->
```yaml
# roles/ssm/tasks/main.yml
- name: ping
  ping:

# roles/ssm/defaults/main.yml
ansible_connection: aws_ssm
ansible_aws_ssm_region: ap-southeast-1
ansible_aws_ssm_access_key_id: XXXXXXXXX
ansible_aws_ssm_secret_access_key: XXXXXXXXX
ansible_aws_ssm_instance_id: i-XXXXXXXX
ansible_aws_ssm_bucket_name: bucket-name
```

<!--- HINT: You can paste gist.github.com links for larger files -->
I guess this is related with this loc:
```
    def _get_boto_client(self, service, region_name=None):
        ''' Gets a boto3 client based on the STS token '''

        aws_access_key_id = self.get_option('access_key_id')
        aws_secret_access_key = self.get_option('secret_access_key')
        aws_session_token = self.get_option('session_token')
        if aws_access_key_id is None or aws_secret_access_key is None or aws_session_token is None:
            aws_access_key_id = os.environ.get("AWS_ACCESS_KEY_ID", None)
            aws_secret_access_key = os.environ.get("AWS_SECRET_ACCESS_KEY", None)
            aws_session_token = os.environ.get("AWS_SESSION_TOKEN", None)
```
Because I do not need `aws_session_token` (yet) to connect to ec2 instances via aws_ssm to do a ping, and I do not set any OS environment variables, all my `aws_access_key_id` and `aws_secret_access_key` are set to None again because the `aws_session_token` is empty.

Suggested fixes:
```
    def _get_boto_client(self, service, region_name=None):
        ''' Gets a boto3 client based on the STS token '''

        aws_access_key_id = self.get_option('access_key_id')
        aws_secret_access_key = self.get_option('secret_access_key')
        aws_session_token = self.get_option('session_token')

        if aws_access_key_id is None:
            aws_access_key_id = os.environ.get("AWS_ACCESS_KEY_ID", None)
        if aws_secret_access_key is None:
            aws_secret_access_key = os.environ.get("AWS_SECRET_ACCESS_KEY", None)
        if aws_session_token is None:
            aws_session_token = os.environ.get("AWS_SESSION_TOKEN", None)
```

##### EXPECTED RESULTS
<!--- Describe what you expected to happen when running the steps above -->
```
TASK [ssm : ping] *****************************************************************************************************************************************************************************
ok: [ec2]

PLAY RECAP ************************************************************************************************************************************************************************************
ec2                        : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

##### ACTUAL RESULTS
<!--- Describe what actually happened. If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes -->
```paste below
PLAY [nodes] **********************************************************************************************************************************************************************************

TASK [ssm : ping] *****************************************************************************************************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: botocore.exceptions.NoCredentialsError: Unable to locate credentials
fatal: [ec2]: FAILED! => {"msg": "Unexpected failure during module execution.", "stdout": ""}

PLAY RECAP ************************************************************************************************************************************************************************************
ec2                        : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
```



Discussion:
Files identified in the description:
* [`plugins/modules/aws_ssm_parameter_store.py`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws']/blob/main/plugins/modules/aws_ssm_parameter_store.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @jillr @mikedlr @nathanwebsterdotme @ozbillwang @s-hertel @tremble @wimnat
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@ru-rocker thank you for filing the issue and the suggested fix. Would you like to open a PR for this?
cc @116davinder
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Perhaps this issue could be addressed differently. The ansible options parser already supports the ability to pick-up environment variables. Why not use that instead?
Right now these fallbacks including region and remote_addr behavior could be addressed by updating configs.

Say something like:
```
  session_token:
    description: The STS session token to use when connecting via session-manager.
    vars:
    - name: ansible_aws_ssm_session_token
    env:
    - name: AWS_SESSION_TOKEN
      version_added: 1.5.0
    version_added: 1.3.0
```

I just had to address this in our environment because as designed, the code will override the configured vars with `None`'s because there are no such environment variables set. I'll look at attaching a pull request to address this since I just had to work-around it manually.
Sorry for the late reply. 

The reason I opened this issue is I need dynamic access_key_id and access_secret_key_id due to security requirements.
I need to store those keys inside some password manager and it is prohibited to store them as environment variables.

Resolving patch:
diff --git a/plugins/connection/aws_ssm.py b/plugins/connection/aws_ssm.py
--- a/plugins/connection/aws_ssm.py
+++ b/plugins/connection/aws_ssm.py
@@ -24,16 +24,22 @@
     description: The STS access key to use when connecting via session-manager.
     vars:
     - name: ansible_aws_ssm_access_key_id
+    env:
+    - name: AWS_ACCESS_KEY_ID
     version_added: 1.3.0
   secret_access_key:
     description: The STS secret key to use when connecting via session-manager.
     vars:
     - name: ansible_aws_ssm_secret_access_key
+    env:
+    - name: AWS_SECRET_ACCESS_KEY
     version_added: 1.3.0
   session_token:
     description: The STS session token to use when connecting via session-manager.
     vars:
     - name: ansible_aws_ssm_session_token
+    env:
+    - name: AWS_SESSION_TOKEN
     version_added: 1.3.0
   instance_id:
     description: The EC2 instance ID.
@@ -43,6 +49,9 @@
     description: The region the EC2 instance is located.
     vars:
     - name: ansible_aws_ssm_region
+    env:
+    - name: AWS_REGION
+    - name: AWS_DEFAULT_REGION
     default: 'us-east-1'
   bucket_name:
     description: The name of the S3 bucket used for file transfers.
@@ -57,6 +66,8 @@
     description: Sets AWS profile to use.
     vars:
     - name: ansible_aws_ssm_profile
+    env:
+    - name: AWS_PROFILE
     version_added: 1.5.0
   reconnection_retries:
     description: Number of attempts to connect.
@@ -736,15 +747,6 @@ def _get_boto_client(self, service, region_name=None, profile_name=None, endpoin
         aws_secret_access_key = self.get_option('secret_access_key')
         aws_session_token = self.get_option('session_token')
 
-        if aws_access_key_id is None:
-            aws_access_key_id = os.environ.get("AWS_ACCESS_KEY_ID", None)
-        if aws_secret_access_key is None:
-            aws_secret_access_key = os.environ.get("AWS_SECRET_ACCESS_KEY", None)
-        if aws_session_token is None:
-            aws_session_token = os.environ.get("AWS_SESSION_TOKEN", None)
-        if not profile_name:
-            profile_name = os.environ.get("AWS_PROFILE", None)
-
         session_args = dict(
             aws_access_key_id=aws_access_key_id,
             aws_secret_access_key=aws_secret_access_key,


Test patch: