From c1ef7a9fdef0330b223bf817ec65d55452a59d5b Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:39:05 +0100 Subject: [PATCH] aws_ssm connection: add SSE encryption parameters. (#763) (#823) aws_ssm connection: add SSE encryption parameters. SUMMARY Add the following parameters to aws_ssm.py connection plugin: ansible_aws_ssm_bucket_sse_mode ansible_aws_ssm_bucket_sse_kms_key_id ISSUE TYPE Feature Pull Request COMPONENT NAME aws_ssm connection plugin ADDITIONAL INFORMATION This allows the connection plugin to work when encryption parameters are required for uploads on the file transfer bucket by policy / SCP (see here for an example). Reviewed-by: Brian Scholer Reviewed-by: Maxime Reviewed-by: Jill R Reviewed-by: Markus Bergholz Reviewed-by: None (cherry picked from commit 08f95cc601828196d3ddefdeaaf5cd1f608ce885) Co-authored-by: Maxime <87057695+fh-maxime-froment@users.noreply.github.com> --- ...-aws_ssm_connection-add-sse-parameters.yml | 2 + plugins/connection/aws_ssm.py | 58 ++++++++++++++++--- .../targets/connection_aws_ssm/aliases | 6 -- .../defaults/main.yml | 5 ++ .../tasks/main.yml | 46 +++++++++------ .../tasks/redhat.yml | 2 + .../templates/inventory-combined.aws_ssm.j2 | 4 +- .../templates/s3_vars_to_delete.yml.j2 | 2 +- .../tasks/main.yml | 5 ++ 9 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 changelogs/fragments/763-aws_ssm_connection-add-sse-parameters.yml diff --git a/changelogs/fragments/763-aws_ssm_connection-add-sse-parameters.yml b/changelogs/fragments/763-aws_ssm_connection-add-sse-parameters.yml new file mode 100644 index 00000000000..9074a5e6862 --- /dev/null +++ b/changelogs/fragments/763-aws_ssm_connection-add-sse-parameters.yml @@ -0,0 +1,2 @@ +minor_changes: + - aws_ssm connection plugin - add parameters to explicitly specify SSE mode and KMS key id for uploads on the file transfer bucket. (https://github.com/ansible-collections/community.aws/pull/763) diff --git a/plugins/connection/aws_ssm.py b/plugins/connection/aws_ssm.py index c0c64046ec6..40724cfbbdb 100644 --- a/plugins/connection/aws_ssm.py +++ b/plugins/connection/aws_ssm.py @@ -70,6 +70,18 @@ type: integer vars: - name: ansible_aws_ssm_timeout + bucket_sse_mode: + description: Server-side encryption mode to use for uploads on the S3 bucket used for file transfer. + choices: [ 'AES256', 'aws:kms' ] + required: false + version_added: 2.2.0 + vars: + - name: ansible_aws_ssm_bucket_sse_mode + bucket_sse_kms_key_id: + description: KMS key id to use when encrypting objects using C(bucket_sse_mode=aws:kms). Ignored otherwise. + version_added: 2.2.0 + vars: + - name: ansible_aws_ssm_bucket_sse_kms_key_id ''' EXAMPLES = r''' @@ -162,6 +174,20 @@ state: directory # Execution: ansible-playbook win_file.yaml -i aws_ec2.yml # The playbook tasks will get executed on the instance ids returned from the dynamic inventory plugin using ssm connection. + +# Install a Nginx Package on Linux Instance; with specific SSE for file transfer +- name: Install a Nginx Package + vars: + ansible_connection: aws_ssm + ansible_aws_ssm_bucket_name: nameofthebucket + ansible_aws_ssm_region: us-west-2 + ansible_aws_ssm_bucket_sse_mode: 'aws:kms' + ansible_aws_ssm_bucket_sse_kms_key_id: alias/kms-key-alias + tasks: + - name: Install a Nginx Package + yum: + name: nginx + state: present ''' import os @@ -506,11 +532,14 @@ def _flush_stderr(self, subprocess): return stderr - def _get_url(self, client_method, bucket_name, out_path, http_method, profile_name): + def _get_url(self, client_method, bucket_name, out_path, http_method, profile_name, extra_args=None): ''' Generate URL for get_object / put_object ''' region_name = self.get_option('region') or 'us-east-1' client = self._get_boto_client('s3', region_name=region_name, profile_name=profile_name) - return client.generate_presigned_url(client_method, Params={'Bucket': bucket_name, 'Key': out_path}, ExpiresIn=3600, HttpMethod=http_method) + params = {'Bucket': bucket_name, 'Key': out_path} + if extra_args is not None: + params.update(extra_args) + return client.generate_presigned_url(client_method, Params=params, ExpiresIn=3600, HttpMethod=http_method) def _get_boto_client(self, service, region_name=None, profile_name=None): ''' Gets a boto3 client based on the STS token ''' @@ -554,14 +583,29 @@ def _file_transport_command(self, in_path, out_path, ssm_action): profile_name = self.get_option('profile') + put_args = dict() + put_headers = dict() + if self.get_option('bucket_sse_mode'): + put_args['ServerSideEncryption'] = self.get_option('bucket_sse_mode') + put_headers['x-amz-server-side-encryption'] = self.get_option('bucket_sse_mode') + if self.get_option('bucket_sse_mode') == 'aws:kms' and self.get_option('bucket_sse_kms_key_id'): + put_args['SSEKMSKeyId'] = self.get_option('bucket_sse_kms_key_id') + put_headers['x-amz-server-side-encryption-aws-kms-key-id'] = self.get_option('bucket_sse_kms_key_id') + if self.is_windows: - put_command = "Invoke-WebRequest -Method PUT -InFile '%s' -Uri '%s' -UseBasicParsing" % ( - in_path, self._get_url('put_object', self.get_option('bucket_name'), s3_path, 'PUT', profile_name)) + put_command_headers = "; ".join(["'%s' = '%s'" % (h, v) for h, v in put_headers.items()]) + put_command = "Invoke-WebRequest -Method PUT -Headers @{%s} -InFile '%s' -Uri '%s' -UseBasicParsing" % ( + put_command_headers, in_path, + self._get_url('put_object', self.get_option('bucket_name'), s3_path, 'PUT', profile_name, + extra_args=put_args)) get_command = "Invoke-WebRequest '%s' -OutFile '%s'" % ( self._get_url('get_object', self.get_option('bucket_name'), s3_path, 'GET', profile_name), out_path) else: - put_command = "curl --request PUT --upload-file '%s' '%s'" % ( - in_path, self._get_url('put_object', self.get_option('bucket_name'), s3_path, 'PUT', profile_name)) + put_command_headers = "".join(["-H '%s: %s' " % (h, v) for h, v in put_headers.items()]) + put_command = "curl --request PUT %s--upload-file '%s' '%s'" % ( + put_command_headers, in_path, + self._get_url('put_object', self.get_option('bucket_name'), s3_path, 'PUT', profile_name, + extra_args=put_args)) get_command = "curl '%s' -o '%s'" % ( self._get_url('get_object', self.get_option('bucket_name'), s3_path, 'GET', profile_name), out_path) @@ -572,7 +616,7 @@ def _file_transport_command(self, in_path, out_path, ssm_action): client.download_fileobj(self.get_option('bucket_name'), s3_path, data) else: with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as data: - client.upload_fileobj(data, self.get_option('bucket_name'), s3_path) + client.upload_fileobj(data, self.get_option('bucket_name'), s3_path, ExtraArgs=put_args) (returncode, stdout, stderr) = self.exec_command(get_command, in_data=None, sudoable=False) # Remove the files from the bucket after they've been transferred diff --git a/tests/integration/targets/connection_aws_ssm/aliases b/tests/integration/targets/connection_aws_ssm/aliases index f15dd4b0a6e..c086d4b7b6b 100644 --- a/tests/integration/targets/connection_aws_ssm/aliases +++ b/tests/integration/targets/connection_aws_ssm/aliases @@ -1,10 +1,4 @@ # reason: slow # This test suite can take almost 25 minutes (on a good day) unstable - cloud/aws - -destructive -non_local -needs/root -needs/target/connection diff --git a/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/defaults/main.yml b/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/defaults/main.yml index 30c2ccddc51..513c35dae33 100644 --- a/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/defaults/main.yml +++ b/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/defaults/main.yml @@ -3,3 +3,8 @@ instance_type: t3.micro linux_ami_name: amzn-ami-hvm-2018.03*x86_64-ebs # Windows AMIs get replaced every few months, don't be too specific windows_ami_name: Windows_Server-2019-English-Full-Base-* + +# see: +# - https://github.com/mattclay/aws-terminator/pull/181 +# - https://github.com/ansible-collections/community.aws/pull/763 +s3_bucket_name: ssm-encrypted-test-bucket diff --git a/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/tasks/main.yml b/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/tasks/main.yml index 2b1cc70d8be..d81d756c34e 100644 --- a/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/tasks/main.yml +++ b/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/tasks/main.yml @@ -28,26 +28,30 @@ linux_ami_id: '{{ latest_linux_ami.image_id }}' windows_ami_id: '{{ latest_windows_ami.image_id }}' - - name: Install Session Manager Plugin for Debian/Ubuntu + - name: Install Session Manager Plugin for Fedora/Debian/Ubuntu include_tasks: debian.yml when: ansible_distribution == "Ubuntu" or ansible_distribution == "Debian" register: install_plugin_debian - name: Install Session Manager Plugin for RedHat/Amazon include_tasks: redhat.yml - when: ansible_distribution == "CentOS" or ansible_distribution == "RedHat" or ansible_distribution == "Amazon" + when: ansible_distribution in ["CentOS", "RedHat", "Amazon", "Fedora"] register: install_plugin_redhat - - name: Fail if the plugin was not installed - fail: - msg: The distribution does not contain the required Session Manager Plugin - when: - - install_plugin_debian is skipped - - install_plugin_redhat is skipped + - block: + - name: Fail if the plugin was not installed + fail: + msg: The distribution does not contain the required Session Manager Plugin + when: + - install_plugin_debian is skipped + - install_plugin_redhat is skipped + always: + - debug: + var: ansible_distribution - name: Ensure IAM instance role exists iam_role: - name: "ansible-test-{{resource_prefix}}-aws-ssm-role" + name: "ansible-test-{{tiny_prefix}}-aws-ssm-role" assume_role_policy_document: "{{ lookup('file','ec2-trust-policy.json') }}" state: present create_instance_profile: yes @@ -99,10 +103,16 @@ wait_for_connection: delay: 360 - - name: Create S3 bucket - s3_bucket: - name: "{{resource_prefix}}-aws-ssm-s3" - register: s3_output + - name: create a key + aws_kms: + alias: '{{ resource_prefix }}-kms' + tags: + ansible-test: '{{ resource_prefix }}' + + # - name: Create S3 bucket + # s3_bucket: + # name: "{{resource_prefix}}-aws-ssm-s3" + # register: s3_output - name: Create Inventory file template: @@ -128,11 +138,11 @@ src: ec2_windows_vars_to_delete.yml.j2 ignore_errors: yes - - name: Create S3 vars_to_delete.yml - template: - dest: "{{playbook_dir}}/s3_vars_to_delete.yml" - src: s3_vars_to_delete.yml.j2 - ignore_errors: yes + # - name: Create S3 vars_to_delete.yml + # template: + # dest: "{{playbook_dir}}/s3_vars_to_delete.yml" + # src: s3_vars_to_delete.yml.j2 + # ignore_errors: yes - name: Create IAM Role vars_to_delete.yml template: diff --git a/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/tasks/redhat.yml b/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/tasks/redhat.yml index d111b4d30a4..f2cd5201451 100644 --- a/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/tasks/redhat.yml +++ b/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/tasks/redhat.yml @@ -5,7 +5,9 @@ mode: '0440' tags: setup_infra - name: Install SSM Plugin + become: true yum: name: /tmp/session-manager-plugin.rpm state: present + disable_gpg_check: true tags: setup_infra diff --git a/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/templates/inventory-combined.aws_ssm.j2 b/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/templates/inventory-combined.aws_ssm.j2 index 1788a9a8445..f8296e23044 100644 --- a/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/templates/inventory-combined.aws_ssm.j2 +++ b/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/templates/inventory-combined.aws_ssm.j2 @@ -20,10 +20,12 @@ aws_ssm_linux [aws_ssm:vars] ansible_connection=community.aws.aws_ssm -ansible_aws_ssm_bucket_name={{s3_output.name}} +ansible_aws_ssm_bucket_name={{s3_bucket_name}} ansible_aws_ssm_plugin=/usr/local/sessionmanagerplugin/bin/session-manager-plugin ansible_python_interpreter=/usr/bin/env python local_tmp=/tmp/ansible-local- +ansible_aws_ssm_bucket_sse_mode='aws:kms' +ansible_aws_ssm_bucket_sse_kms_key_id=alias/{{ resource_prefix }}-kms # support tests that target testhost [testhost:children] diff --git a/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/templates/s3_vars_to_delete.yml.j2 b/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/templates/s3_vars_to_delete.yml.j2 index 3839fb3c6ea..d29071a9f98 100644 --- a/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/templates/s3_vars_to_delete.yml.j2 +++ b/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_setup/templates/s3_vars_to_delete.yml.j2 @@ -1,2 +1,2 @@ --- -bucket_name: {{s3_output.name}} +#bucket_name: {{s3_output.name}} diff --git a/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_teardown/tasks/main.yml b/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_teardown/tasks/main.yml index d9d174ab039..744fa26896f 100644 --- a/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_teardown/tasks/main.yml +++ b/tests/integration/targets/connection_aws_ssm/aws_ssm_integration_test_teardown/tasks/main.yml @@ -78,6 +78,11 @@ ignore_errors: yes when: iam_role_vars_file.stat.exists == true + - name: Delete the KMS key + aws_kms: + state: absent + alias: '{{ resource_prefix }}-kms' + - name: Delete AWS keys environement file: path: "{{playbook_dir}}/aws-env-vars.sh"