Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ecs_ecr - Add encryption_configuration option #1623

2 changes: 2 additions & 0 deletions changelogs/fragments/1623-ecs_ecr-add-kms-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- ecs_ecr - add `encryption_configuration` option (https://github.com/ansible-collections/community.aws/pull/1623).
56 changes: 52 additions & 4 deletions plugins/modules/ecs_ecr.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,24 @@
default: false
type: bool
version_added: 1.3.0
encryption_configuration:
description:
- The encryption configuration for the repository.
required: false
suboptions:
encryption_type:
description:
- The encryption type to use.
choices: [AES256, KMS]
default: 'AES256'
type: str
kms_key:
description:
- If I(encryption_type=KMS), specify the KMS key to use for encryption.
- The alias, key ID, or full ARN of the KMS key can be specified.
type: str
type: dict
version_added: 5.2.0
author:
- David M. Lee (@leedm777)
extends_documentation_fragment:
Expand Down Expand Up @@ -161,6 +179,13 @@
community.aws.ecs_ecr:
name: needs-no-lifecycle-policy
purge_lifecycle_policy: true

- name: set-encryption-configuration
community.aws.ecs_ecr:
name: uses-custom-kms-key
encryption_configuration:
encryption_type: KMS
kms_key: custom-kms-key-alias
'''

RETURN = '''
Expand Down Expand Up @@ -201,6 +226,7 @@
except ImportError:
pass # Handled by AnsibleAWSModule

from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict
from ansible.module_utils.six import string_types

from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
Expand Down Expand Up @@ -248,17 +274,21 @@ def get_repository_policy(self, registry_id, name):
except is_boto3_error_code(['RepositoryNotFoundException', 'RepositoryPolicyNotFoundException']):
return None

def create_repository(self, registry_id, name, image_tag_mutability):
def create_repository(self, registry_id, name, image_tag_mutability, encryption_configuration):
if registry_id:
default_registry_id = self.sts.get_caller_identity().get('Account')
if registry_id != default_registry_id:
raise Exception('Cannot create repository in registry {0}.'
'Would be created in {1} instead.'.format(registry_id, default_registry_id))

if encryption_configuration is None:
encryption_configuration = dict(encryptionType='AES256')
markuman marked this conversation as resolved.
Show resolved Hide resolved

if not self.check_mode:
repo = self.ecr.create_repository(
repositoryName=name,
imageTagMutability=image_tag_mutability).get('repository')
imageTagMutability=image_tag_mutability,
encryptionConfiguration=encryption_configuration).get('repository')
self.changed = True
return repo
else:
Expand Down Expand Up @@ -411,6 +441,7 @@ def run(ecr, params):
lifecycle_policy_text = params['lifecycle_policy']
purge_lifecycle_policy = params['purge_lifecycle_policy']
scan_on_push = params['scan_on_push']
encryption_configuration = snake_dict_to_camel_dict(params['encryption_configuration'])

# Parse policies, if they are given
try:
Expand All @@ -437,10 +468,16 @@ def run(ecr, params):
result['created'] = False

if not repo:
repo = ecr.create_repository(registry_id, name, image_tag_mutability)
repo = ecr.create_repository(
registry_id, name, image_tag_mutability, encryption_configuration)
result['changed'] = True
result['created'] = True
else:
if encryption_configuration is not None:
if repo.get('encryptionConfiguration') != encryption_configuration:
result['msg'] = 'Cannot modify repository encryption type'
return False, result

repo = ecr.put_image_tag_mutability(registry_id, name, image_tag_mutability)
result['repository'] = repo

Expand Down Expand Up @@ -550,7 +587,18 @@ def main():
purge_policy=dict(required=False, type='bool'),
lifecycle_policy=dict(required=False, type='json'),
purge_lifecycle_policy=dict(required=False, type='bool'),
scan_on_push=(dict(required=False, type='bool', default=False))
scan_on_push=(dict(required=False, type='bool', default=False)),
encryption_configuration=dict(
required=False,
type='dict',
options=dict(
encryption_type=dict(required=False, type='str', default='AES256', choices=['AES256', 'KMS']),
kms_key=dict(required=False, type='str', no_log=False),
),
required_if=[
['encryption_type', 'KMS', ['kms_key']],
],
),
)
mutually_exclusive = [
['policy', 'purge_policy'],
Expand Down
66 changes: 66 additions & 0 deletions tests/integration/targets/ecs_ecr/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@
- set_fact:
ecr_name: '{{ resource_prefix }}-ecr'

- name: get ARN of calling user
aws_caller_info:
register: aws_caller_info

- name: create KMS key for testing
aws_kms:
alias: "{{ resource_prefix }}-ecr"
description: a key used for testing ECR
state: present
enabled: yes
key_spec: SYMMETRIC_DEFAULT
key_usage: ENCRYPT_DECRYPT
policy: "{{ lookup('template', 'kms_policy.j2') }}"
tags:
Name: "{{ resource_prefix }}-ecr"
AnsibleTest: AnsibleTestVpc
register: kms_test_key

- name: When creating with check mode
ecs_ecr:
name: '{{ ecr_name }}'
Expand Down Expand Up @@ -54,6 +72,11 @@
that:
- result.repository.imageTagMutability == "MUTABLE"

- name: it should use AES256 encryption by default
assert:
that:
- result.repository.encryptionConfiguration.encryptionType == "AES256"

- name: When pulling an existing repository that has no existing policy
ecs_ecr:
name: '{{ ecr_name }}'
Expand Down Expand Up @@ -538,9 +561,52 @@
- result is changed
- not result.repository.imageScanningConfiguration.scanOnPush

- name: When modifying the encryption setting of an existing repository
ecs_ecr:
name: '{{ ecr_name }}'
encryption_configuration:
encryption_type: KMS
kms_key: '{{ kms_test_key.key_arn }}'
register: result
ignore_errors: true

- name: it should fail
assert:
that:
- result is failed

- name: delete repository
ecs_ecr:
name: '{{ ecr_name }}'
state: absent

- name: When creating a repo using KMS encryption
ecs_ecr:
name: '{{ ecr_name }}'
encryption_configuration:
encryption_type: KMS
kms_key: '{{ kms_test_key.key_arn }}'
register: result

- name: it should create the repo and use KMS encryption
assert:
that:
- result is changed
- result.repository.encryptionConfiguration.encryptionType == "KMS"

- name: it should use the provided KMS key
assert:
that:
- result.repository.encryptionConfiguration.kmsKey == '{{ kms_test_key.key_arn }}'

always:

- name: Delete lingering ECR repository
ecs_ecr:
name: '{{ ecr_name }}'
state: absent

- name: Delete KMS key
aws_kms:
key_id: '{{ kms_test_key.key_arn }}'
state: absent
72 changes: 72 additions & 0 deletions tests/integration/targets/ecs_ecr/templates/kms_policy.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"Id": "key-ansible-test-policy-123",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Allow access for root user",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::{{ aws_caller_info.account }}:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow access for calling user",
"Effect": "Allow",
"Principal": {
"AWS": "{{ aws_caller_info.arn }}"
},
"Action": [
"kms:Create*",
"kms:Describe*",
"kms:Enable*",
"kms:List*",
"kms:Put*",
"kms:Update*",
"kms:Revoke*",
"kms:Disable*",
"kms:Get*",
"kms:Delete*",
"kms:TagResource",
"kms:UntagResource",
"kms:ScheduleKeyDeletion",
"kms:CancelKeyDeletion"
],
"Resource": "*"
},
{
"Sid": "Allow use of the key",
"Effect": "Allow",
"Principal": {
"AWS": "{{ aws_caller_info.arn }}"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
},
{
"Sid": "Allow attachment of persistent resources",
"Effect": "Allow",
"Principal": {
"AWS": "{{ aws_caller_info.arn }}"
},
"Action": [
"kms:CreateGrant",
"kms:ListGrants",
"kms:RevokeGrant"
],
"Resource": "*",
"Condition": {
"Bool": {
"kms:GrantIsForAWSResource": "true"
}
}
}
]
}