* Allow creation and deletion of keys (deletion just schedules for
deletion, recreating an old key is just cancelling its deletion)
* Allow grants to be set, thus enabling encryption contexts to be
used with keys
* Allow tags to be added and modified
* Add testing for KMS module
* Tidy up aws_kms module to latest standards
| ... | ... |
@@ -86,6 +86,7 @@ packaging/release/ansible_release |
| 86 | 86 |
/test/results/junit/*.xml |
| 87 | 87 |
/test/results/logs/*.log |
| 88 | 88 |
/test/results/data/*.json |
| 89 |
+/test/integration/cloud-config-aws.yml |
|
| 89 | 90 |
/test/integration/inventory.remote |
| 90 | 91 |
/test/integration/inventory.networking |
| 91 | 92 |
/test/integration/inventory.winrm |
| 0 | 2 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,54 @@ |
| 0 |
+{
|
|
| 1 |
+ "Version": "2012-10-17", |
|
| 2 |
+ "Statement": [ |
|
| 3 |
+ {
|
|
| 4 |
+ "Sid": "AllowAccessToUnspecifiedKMSResources", |
|
| 5 |
+ "Effect": "Allow", |
|
| 6 |
+ "Action": [ |
|
| 7 |
+ "iam:ListRoles", |
|
| 8 |
+ "kms:CancelKeyDeletion", |
|
| 9 |
+ "kms:CreateAlias", |
|
| 10 |
+ "kms:CreateGrant", |
|
| 11 |
+ "kms:CreateKey", |
|
| 12 |
+ "kms:DeleteAlias", |
|
| 13 |
+ "kms:Describe*", |
|
| 14 |
+ "kms:DisableKey", |
|
| 15 |
+ "kms:EnableKey", |
|
| 16 |
+ "kms:GenerateRandom", |
|
| 17 |
+ "kms:Get*", |
|
| 18 |
+ "kms:List*", |
|
| 19 |
+ "kms:RetireGrant", |
|
| 20 |
+ "kms:ScheduleKeyDeletion", |
|
| 21 |
+ "kms:TagResource", |
|
| 22 |
+ "kms:UntagResource", |
|
| 23 |
+ "kms:UpdateGrant", |
|
| 24 |
+ "kms:UpdateKeyDescription" |
|
| 25 |
+ ], |
|
| 26 |
+ "Resource": "*" |
|
| 27 |
+ }, |
|
| 28 |
+ {
|
|
| 29 |
+ "Sid": "AllowAccessToSpecifiedIAMResources", |
|
| 30 |
+ "Effect": "Allow", |
|
| 31 |
+ "Action": [ |
|
| 32 |
+ "iam:CreateRole", |
|
| 33 |
+ "iam:DeleteRole", |
|
| 34 |
+ "iam:GetRole", |
|
| 35 |
+ "iam:ListAttachedRolePolicies", |
|
| 36 |
+ "iam:ListInstanceProfilesForRole", |
|
| 37 |
+ "iam:PassRole", |
|
| 38 |
+ "iam:UpdateAssumeRolePolicy" |
|
| 39 |
+ ], |
|
| 40 |
+ "Resource": "arn:aws:iam::{{aws_account}}:role/ansible-test-*"
|
|
| 41 |
+ }, |
|
| 42 |
+ {
|
|
| 43 |
+ "Sid": "AllowInstanceProfileCreation", |
|
| 44 |
+ "Effect": "Allow", |
|
| 45 |
+ "Action": [ |
|
| 46 |
+ "iam:AddRoleToInstanceProfile", |
|
| 47 |
+ "iam:CreateInstanceProfile", |
|
| 48 |
+ "iam:RemoveRoleFromInstanceProfile" |
|
| 49 |
+ ], |
|
| 50 |
+ "Resource": "arn:aws:iam::{{aws_account}}:instance-profile/ansible-test-*"
|
|
| 51 |
+ } |
|
| 52 |
+ ] |
|
| 53 |
+} |
| ... | ... |
@@ -26,22 +26,29 @@ short_description: Perform various KMS management tasks. |
| 26 | 26 |
description: |
| 27 | 27 |
- Manage role/user access to a KMS key. Not designed for encrypting/decrypting. |
| 28 | 28 |
version_added: "2.3" |
| 29 |
-requirements: [ boto3 ] |
|
| 30 | 29 |
options: |
| 31 | 30 |
mode: |
| 32 | 31 |
description: |
| 33 | 32 |
- Grant or deny access. |
| 34 |
- required: true |
|
| 35 | 33 |
default: grant |
| 36 | 34 |
choices: [ grant, deny ] |
| 37 |
- key_alias: |
|
| 38 |
- description: |
|
| 39 |
- - Alias label to the key. One of C(key_alias) or C(key_arn) are required. |
|
| 35 |
+ alias: |
|
| 36 |
+ description: An alias for a key. For safety, even though KMS does not require keys |
|
| 37 |
+ to have an alias, this module expects all new keys to be given an alias |
|
| 38 |
+ to make them easier to manage. Existing keys without an alias may be |
|
| 39 |
+ referred to by I(key_id). Use M(aws_kms_facts) to find key ids. Required |
|
| 40 |
+ if I(key_id) is not given. Note that passing a I(key_id) and I(alias) |
|
| 41 |
+ will only cause a new alias to be added, an alias will never be renamed. |
|
| 42 |
+ The 'alias/' prefix is optional. |
|
| 40 | 43 |
required: false |
| 41 |
- key_arn: |
|
| 44 |
+ aliases: |
|
| 45 |
+ - key_alias |
|
| 46 |
+ key_id: |
|
| 42 | 47 |
description: |
| 43 |
- - Full ARN to the key. One of C(key_alias) or C(key_arn) are required. |
|
| 48 |
+ - Key ID or ARN of the key. One of C(alias) or C(key_id) are required. |
|
| 44 | 49 |
required: false |
| 50 |
+ aliases: |
|
| 51 |
+ - key_arn |
|
| 45 | 52 |
role_name: |
| 46 | 53 |
description: |
| 47 | 54 |
- Role to allow/deny access. One of C(role_name) or C(role_arn) are required. |
| ... | ... |
@@ -60,8 +67,62 @@ options: |
| 60 | 60 |
- Only cleans if changes are being made. |
| 61 | 61 |
type: bool |
| 62 | 62 |
default: true |
| 63 |
- |
|
| 64 |
-author: Ted Timmons (@tedder) |
|
| 63 |
+ state: |
|
| 64 |
+ description: Whether a key should be present or absent. Note that making an |
|
| 65 |
+ existing key absent only schedules a key for deletion. Passing a key that |
|
| 66 |
+ is scheduled for deletion with state present will cancel key deletion. |
|
| 67 |
+ required: False |
|
| 68 |
+ choices: |
|
| 69 |
+ - present |
|
| 70 |
+ - absent |
|
| 71 |
+ default: present |
|
| 72 |
+ version_added: 2.8 |
|
| 73 |
+ enabled: |
|
| 74 |
+ description: Whether or not a key is enabled |
|
| 75 |
+ default: True |
|
| 76 |
+ version_added: 2.8 |
|
| 77 |
+ type: bool |
|
| 78 |
+ description: |
|
| 79 |
+ description: |
|
| 80 |
+ A description of the CMK. Use a description that helps you decide |
|
| 81 |
+ whether the CMK is appropriate for a task. |
|
| 82 |
+ version_added: 2.8 |
|
| 83 |
+ tags: |
|
| 84 |
+ description: A dictionary of tags to apply to a key. |
|
| 85 |
+ version_added: 2.8 |
|
| 86 |
+ purge_tags: |
|
| 87 |
+ description: Whether the I(tags) argument should cause tags not in the list to |
|
| 88 |
+ be removed |
|
| 89 |
+ version_added: 2.8 |
|
| 90 |
+ default: False |
|
| 91 |
+ type: bool |
|
| 92 |
+ purge_grants: |
|
| 93 |
+ description: Whether the I(grants) argument should cause grants not in the list to |
|
| 94 |
+ be removed |
|
| 95 |
+ default: False |
|
| 96 |
+ version_added: 2.8 |
|
| 97 |
+ type: bool |
|
| 98 |
+ grants: |
|
| 99 |
+ description: |
|
| 100 |
+ - A list of grants to apply to the key. Each item must contain I(grantee_principal). |
|
| 101 |
+ Each item can optionally contain I(retiring_principal), I(operations), I(constraints), |
|
| 102 |
+ I(name). |
|
| 103 |
+ - Valid operations are C(Decrypt), C(Encrypt), C(GenerateDataKey), C(GenerateDataKeyWithoutPlaintext), |
|
| 104 |
+ C(ReEncryptFrom), C(ReEncryptTo), C(CreateGrant), C(RetireGrant), C(DescribeKey), C(Verify) and |
|
| 105 |
+ C(Sign) |
|
| 106 |
+ - Constraints is a dict containing C(encryption_context_subset) or C(encryption_context_equals), |
|
| 107 |
+ either or both being a dict specifying an encryption context match. |
|
| 108 |
+ See U(https://docs.aws.amazon.com/kms/latest/APIReference/API_GrantConstraints.html) |
|
| 109 |
+ - I(grantee_principal) and I(retiring_principal) must be ARNs |
|
| 110 |
+ version_added: 2.8 |
|
| 111 |
+ policy: |
|
| 112 |
+ description: |
|
| 113 |
+ - policy to apply to the KMS key |
|
| 114 |
+ - See U(https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html) |
|
| 115 |
+ version_added: 2.8 |
|
| 116 |
+author: |
|
| 117 |
+ - Ted Timmons (@tedder) |
|
| 118 |
+ - Will Thames (@willthames) |
|
| 65 | 119 |
extends_documentation_fragment: |
| 66 | 120 |
- aws |
| 67 | 121 |
- ec2 |
| ... | ... |
@@ -72,18 +133,199 @@ EXAMPLES = ''' |
| 72 | 72 |
aws_kms: |
| 73 | 73 |
args: |
| 74 | 74 |
mode: grant |
| 75 |
- key_alias: "alias/my_production_secrets" |
|
| 75 |
+ alias: "alias/my_production_secrets" |
|
| 76 | 76 |
role_name: "prod-appServerRole-1R5AQG2BSEL6L" |
| 77 | 77 |
grant_types: "role,role grant" |
| 78 | 78 |
- name: remove access to production secrets from role |
| 79 | 79 |
aws_kms: |
| 80 | 80 |
args: |
| 81 | 81 |
mode: deny |
| 82 |
- key_alias: "alias/my_production_secrets" |
|
| 82 |
+ alias: "alias/my_production_secrets" |
|
| 83 | 83 |
role_name: "prod-appServerRole-1R5AQG2BSEL6L" |
| 84 |
+ |
|
| 85 |
+# Create a new KMS key |
|
| 86 |
+- aws_kms: |
|
| 87 |
+ alias: mykey |
|
| 88 |
+ tags: |
|
| 89 |
+ Name: myKey |
|
| 90 |
+ Purpose: protect_stuff |
|
| 91 |
+ |
|
| 92 |
+# Update previous key with more tags |
|
| 93 |
+- aws_kms: |
|
| 94 |
+ alias: mykey |
|
| 95 |
+ tags: |
|
| 96 |
+ Name: myKey |
|
| 97 |
+ Purpose: protect_stuff |
|
| 98 |
+ Owner: security_team |
|
| 99 |
+ |
|
| 100 |
+# Update a known key with grants allowing an instance with the billing-prod IAM profile |
|
| 101 |
+# to decrypt data encrypted with the environment: production, application: billing |
|
| 102 |
+# encryption context |
|
| 103 |
+- aws_kms: |
|
| 104 |
+ key_id: abcd1234-abcd-1234-5678-ef1234567890 |
|
| 105 |
+ grants: |
|
| 106 |
+ - name: billing_prod |
|
| 107 |
+ grantee_principal: arn:aws:iam::1234567890123:role/billing_prod |
|
| 108 |
+ constraints: |
|
| 109 |
+ encryption_context_equals: |
|
| 110 |
+ environment: production |
|
| 111 |
+ application: billing |
|
| 112 |
+ operations: |
|
| 113 |
+ - Decrypt |
|
| 114 |
+ - RetireGrant |
|
| 84 | 115 |
''' |
| 85 | 116 |
|
| 86 | 117 |
RETURN = ''' |
| 118 |
+key_id: |
|
| 119 |
+ description: ID of key |
|
| 120 |
+ type: str |
|
| 121 |
+ returned: always |
|
| 122 |
+ sample: abcd1234-abcd-1234-5678-ef1234567890 |
|
| 123 |
+key_arn: |
|
| 124 |
+ description: ARN of key |
|
| 125 |
+ type: str |
|
| 126 |
+ returned: always |
|
| 127 |
+ sample: arn:aws:kms:ap-southeast-2:123456789012:key/abcd1234-abcd-1234-5678-ef1234567890 |
|
| 128 |
+key_state: |
|
| 129 |
+ description: The state of the key |
|
| 130 |
+ type: str |
|
| 131 |
+ returned: always |
|
| 132 |
+ sample: PendingDeletion |
|
| 133 |
+key_usage: |
|
| 134 |
+ description: The cryptographic operations for which you can use the key. |
|
| 135 |
+ type: str |
|
| 136 |
+ returned: always |
|
| 137 |
+ sample: ENCRYPT_DECRYPT |
|
| 138 |
+origin: |
|
| 139 |
+ description: The source of the key's key material. When this value is C(AWS_KMS), |
|
| 140 |
+ AWS KMS created the key material. When this value is C(EXTERNAL), the |
|
| 141 |
+ key material was imported or the CMK lacks key material. |
|
| 142 |
+ type: str |
|
| 143 |
+ returned: always |
|
| 144 |
+ sample: AWS_KMS |
|
| 145 |
+aws_account_id: |
|
| 146 |
+ description: The AWS Account ID that the key belongs to |
|
| 147 |
+ type: str |
|
| 148 |
+ returned: always |
|
| 149 |
+ sample: 1234567890123 |
|
| 150 |
+creation_date: |
|
| 151 |
+ description: Date of creation of the key |
|
| 152 |
+ type: str |
|
| 153 |
+ returned: always |
|
| 154 |
+ sample: "2017-04-18T15:12:08.551000+10:00" |
|
| 155 |
+description: |
|
| 156 |
+ description: Description of the key |
|
| 157 |
+ type: str |
|
| 158 |
+ returned: always |
|
| 159 |
+ sample: "My Key for Protecting important stuff" |
|
| 160 |
+enabled: |
|
| 161 |
+ description: Whether the key is enabled. True if C(KeyState) is true. |
|
| 162 |
+ type: str |
|
| 163 |
+ returned: always |
|
| 164 |
+ sample: false |
|
| 165 |
+aliases: |
|
| 166 |
+ description: list of aliases associated with the key |
|
| 167 |
+ type: list |
|
| 168 |
+ returned: always |
|
| 169 |
+ sample: |
|
| 170 |
+ - aws/acm |
|
| 171 |
+ - aws/ebs |
|
| 172 |
+policies: |
|
| 173 |
+ description: list of policy documents for the keys. Empty when access is denied even if there are policies. |
|
| 174 |
+ type: list |
|
| 175 |
+ returned: always |
|
| 176 |
+ sample: |
|
| 177 |
+ Version: "2012-10-17" |
|
| 178 |
+ Id: "auto-ebs-2" |
|
| 179 |
+ Statement: |
|
| 180 |
+ - Sid: "Allow access through EBS for all principals in the account that are authorized to use EBS" |
|
| 181 |
+ Effect: "Allow" |
|
| 182 |
+ Principal: |
|
| 183 |
+ AWS: "*" |
|
| 184 |
+ Action: |
|
| 185 |
+ - "kms:Encrypt" |
|
| 186 |
+ - "kms:Decrypt" |
|
| 187 |
+ - "kms:ReEncrypt*" |
|
| 188 |
+ - "kms:GenerateDataKey*" |
|
| 189 |
+ - "kms:CreateGrant" |
|
| 190 |
+ - "kms:DescribeKey" |
|
| 191 |
+ Resource: "*" |
|
| 192 |
+ Condition: |
|
| 193 |
+ StringEquals: |
|
| 194 |
+ kms:CallerAccount: "111111111111" |
|
| 195 |
+ kms:ViaService: "ec2.ap-southeast-2.amazonaws.com" |
|
| 196 |
+ - Sid: "Allow direct access to key metadata to the account" |
|
| 197 |
+ Effect: "Allow" |
|
| 198 |
+ Principal: |
|
| 199 |
+ AWS: "arn:aws:iam::111111111111:root" |
|
| 200 |
+ Action: |
|
| 201 |
+ - "kms:Describe*" |
|
| 202 |
+ - "kms:Get*" |
|
| 203 |
+ - "kms:List*" |
|
| 204 |
+ - "kms:RevokeGrant" |
|
| 205 |
+ Resource: "*" |
|
| 206 |
+tags: |
|
| 207 |
+ description: dictionary of tags applied to the key |
|
| 208 |
+ type: dict |
|
| 209 |
+ returned: always |
|
| 210 |
+ sample: |
|
| 211 |
+ Name: myKey |
|
| 212 |
+ Purpose: protecting_stuff |
|
| 213 |
+grants: |
|
| 214 |
+ description: list of grants associated with a key |
|
| 215 |
+ type: complex |
|
| 216 |
+ returned: always |
|
| 217 |
+ contains: |
|
| 218 |
+ constraints: |
|
| 219 |
+ description: Constraints on the encryption context that the grant allows. |
|
| 220 |
+ See U(https://docs.aws.amazon.com/kms/latest/APIReference/API_GrantConstraints.html) for further details |
|
| 221 |
+ type: dict |
|
| 222 |
+ returned: always |
|
| 223 |
+ sample: |
|
| 224 |
+ encryption_context_equals: |
|
| 225 |
+ "aws:lambda:_function_arn": "arn:aws:lambda:ap-southeast-2:012345678912:function:xyz" |
|
| 226 |
+ creation_date: |
|
| 227 |
+ description: Date of creation of the grant |
|
| 228 |
+ type: str |
|
| 229 |
+ returned: always |
|
| 230 |
+ sample: 2017-04-18T15:12:08+10:00 |
|
| 231 |
+ grant_id: |
|
| 232 |
+ description: The unique ID for the grant |
|
| 233 |
+ type: str |
|
| 234 |
+ returned: always |
|
| 235 |
+ sample: abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234 |
|
| 236 |
+ grantee_principal: |
|
| 237 |
+ description: The principal that receives the grant's permissions |
|
| 238 |
+ type: str |
|
| 239 |
+ returned: always |
|
| 240 |
+ sample: arn:aws:sts::0123456789012:assumed-role/lambda_xyz/xyz |
|
| 241 |
+ issuing_account: |
|
| 242 |
+ description: The AWS account under which the grant was issued |
|
| 243 |
+ type: str |
|
| 244 |
+ returned: always |
|
| 245 |
+ sample: arn:aws:iam::01234567890:root |
|
| 246 |
+ key_id: |
|
| 247 |
+ description: The key ARN to which the grant applies. |
|
| 248 |
+ type: str |
|
| 249 |
+ returned: always |
|
| 250 |
+ sample: arn:aws:kms:ap-southeast-2:123456789012:key/abcd1234-abcd-1234-5678-ef1234567890 |
|
| 251 |
+ name: |
|
| 252 |
+ description: The friendly name that identifies the grant |
|
| 253 |
+ type: str |
|
| 254 |
+ returned: always |
|
| 255 |
+ sample: xyz |
|
| 256 |
+ operations: |
|
| 257 |
+ description: The list of operations permitted by the grant |
|
| 258 |
+ type: list |
|
| 259 |
+ returned: always |
|
| 260 |
+ sample: |
|
| 261 |
+ - Decrypt |
|
| 262 |
+ - RetireGrant |
|
| 263 |
+ retiring_principal: |
|
| 264 |
+ description: The principal that can retire the grant |
|
| 265 |
+ type: str |
|
| 266 |
+ returned: always |
|
| 267 |
+ sample: arn:aws:sts::0123456789012:assumed-role/lambda_xyz/xyz |
|
| 87 | 268 |
changes_needed: |
| 88 | 269 |
description: grant types that would be changed/were changed. |
| 89 | 270 |
type: dict |
| ... | ... |
@@ -103,22 +345,385 @@ statement_label = {
|
| 103 | 103 |
'admin': 'Allow access for Key Administrators' |
| 104 | 104 |
} |
| 105 | 105 |
|
| 106 |
-# import module snippets |
|
| 107 |
-from ansible.module_utils.basic import AnsibleModule |
|
| 108 |
-from ansible.module_utils.ec2 import boto_exception |
|
| 106 |
+from ansible.module_utils.aws.core import AnsibleAWSModule, is_boto3_error_code |
|
| 107 |
+from ansible.module_utils.ec2 import ec2_argument_spec |
|
| 108 |
+from ansible.module_utils.ec2 import AWSRetry, camel_dict_to_snake_dict |
|
| 109 |
+from ansible.module_utils.ec2 import boto3_tag_list_to_ansible_dict, ansible_dict_to_boto3_tag_list |
|
| 110 |
+from ansible.module_utils.ec2 import compare_aws_tags |
|
| 109 | 111 |
from ansible.module_utils.six import string_types |
| 110 | 112 |
|
| 111 |
-# import a class, we'll use a fully qualified path |
|
| 112 |
-import ansible.module_utils.ec2 |
|
| 113 |
- |
|
| 114 |
-import traceback |
|
| 115 | 113 |
import json |
| 116 | 114 |
|
| 117 | 115 |
try: |
| 118 | 116 |
import botocore |
| 119 |
- HAS_BOTO3 = True |
|
| 120 | 117 |
except ImportError: |
| 121 |
- HAS_BOTO3 = False |
|
| 118 |
+ pass # caught by AnsibleAWSModule |
|
| 119 |
+ |
|
| 120 |
+ |
|
| 121 |
+@AWSRetry.backoff(tries=5, delay=5, backoff=2.0) |
|
| 122 |
+def get_iam_roles_with_backoff(connection): |
|
| 123 |
+ paginator = connection.get_paginator('list_roles')
|
|
| 124 |
+ return paginator.paginate().build_full_result() |
|
| 125 |
+ |
|
| 126 |
+ |
|
| 127 |
+@AWSRetry.backoff(tries=5, delay=5, backoff=2.0) |
|
| 128 |
+def get_kms_keys_with_backoff(connection): |
|
| 129 |
+ paginator = connection.get_paginator('list_keys')
|
|
| 130 |
+ return paginator.paginate().build_full_result() |
|
| 131 |
+ |
|
| 132 |
+ |
|
| 133 |
+@AWSRetry.backoff(tries=5, delay=5, backoff=2.0) |
|
| 134 |
+def get_kms_aliases_with_backoff(connection): |
|
| 135 |
+ paginator = connection.get_paginator('list_aliases')
|
|
| 136 |
+ return paginator.paginate().build_full_result() |
|
| 137 |
+ |
|
| 138 |
+ |
|
| 139 |
+def get_kms_aliases_lookup(connection): |
|
| 140 |
+ _aliases = dict() |
|
| 141 |
+ for alias in get_kms_aliases_with_backoff(connection)['Aliases']: |
|
| 142 |
+ # Not all aliases are actually associated with a key |
|
| 143 |
+ if 'TargetKeyId' in alias: |
|
| 144 |
+ # strip off leading 'alias/' and add it to key's aliases |
|
| 145 |
+ if alias['TargetKeyId'] in _aliases: |
|
| 146 |
+ _aliases[alias['TargetKeyId']].append(alias['AliasName'][6:]) |
|
| 147 |
+ else: |
|
| 148 |
+ _aliases[alias['TargetKeyId']] = [alias['AliasName'][6:]] |
|
| 149 |
+ return _aliases |
|
| 150 |
+ |
|
| 151 |
+ |
|
| 152 |
+@AWSRetry.backoff(tries=5, delay=5, backoff=2.0) |
|
| 153 |
+def get_kms_tags_with_backoff(connection, key_id, **kwargs): |
|
| 154 |
+ return connection.list_resource_tags(KeyId=key_id, **kwargs) |
|
| 155 |
+ |
|
| 156 |
+ |
|
| 157 |
+@AWSRetry.backoff(tries=5, delay=5, backoff=2.0) |
|
| 158 |
+def get_kms_grants_with_backoff(connection, key_id): |
|
| 159 |
+ params = dict(KeyId=key_id) |
|
| 160 |
+ paginator = connection.get_paginator('list_grants')
|
|
| 161 |
+ return paginator.paginate(**params).build_full_result() |
|
| 162 |
+ |
|
| 163 |
+ |
|
| 164 |
+@AWSRetry.backoff(tries=5, delay=5, backoff=2.0) |
|
| 165 |
+def get_kms_metadata_with_backoff(connection, key_id): |
|
| 166 |
+ return connection.describe_key(KeyId=key_id) |
|
| 167 |
+ |
|
| 168 |
+ |
|
| 169 |
+@AWSRetry.backoff(tries=5, delay=5, backoff=2.0) |
|
| 170 |
+def list_key_policies_with_backoff(connection, key_id): |
|
| 171 |
+ paginator = connection.get_paginator('list_key_policies')
|
|
| 172 |
+ return paginator.paginate(KeyId=key_id).build_full_result() |
|
| 173 |
+ |
|
| 174 |
+ |
|
| 175 |
+@AWSRetry.backoff(tries=5, delay=5, backoff=2.0) |
|
| 176 |
+def get_key_policy_with_backoff(connection, key_id, policy_name): |
|
| 177 |
+ return connection.get_key_policy(KeyId=key_id, PolicyName=policy_name) |
|
| 178 |
+ |
|
| 179 |
+ |
|
| 180 |
+def get_kms_tags(connection, module, key_id): |
|
| 181 |
+ # Handle pagination here as list_resource_tags does not have |
|
| 182 |
+ # a paginator |
|
| 183 |
+ kwargs = {}
|
|
| 184 |
+ tags = [] |
|
| 185 |
+ more = True |
|
| 186 |
+ while more: |
|
| 187 |
+ try: |
|
| 188 |
+ tag_response = get_kms_tags_with_backoff(connection, key_id, **kwargs) |
|
| 189 |
+ tags.extend(tag_response['Tags']) |
|
| 190 |
+ except is_boto3_error_code('AccessDeniedException'):
|
|
| 191 |
+ tag_response = {}
|
|
| 192 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except |
|
| 193 |
+ module.fail_json_aws(e, msg="Failed to obtain key tags") |
|
| 194 |
+ if tag_response.get('NextMarker'):
|
|
| 195 |
+ kwargs['Marker'] = tag_response['NextMarker'] |
|
| 196 |
+ else: |
|
| 197 |
+ more = False |
|
| 198 |
+ return tags |
|
| 199 |
+ |
|
| 200 |
+ |
|
| 201 |
+def get_kms_policies(connection, module, key_id): |
|
| 202 |
+ try: |
|
| 203 |
+ policies = list_key_policies_with_backoff(connection, key_id)['PolicyNames'] |
|
| 204 |
+ return [get_key_policy_with_backoff(connection, key_id, policy)['Policy'] for |
|
| 205 |
+ policy in policies] |
|
| 206 |
+ except is_boto3_error_code('AccessDeniedException'):
|
|
| 207 |
+ return [] |
|
| 208 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except |
|
| 209 |
+ module.fail_json_aws(e, msg="Failed to obtain key policies") |
|
| 210 |
+ |
|
| 211 |
+ |
|
| 212 |
+def key_matches_filter(key, filtr): |
|
| 213 |
+ if filtr[0] == 'key-id': |
|
| 214 |
+ return filtr[1] == key['key_id'] |
|
| 215 |
+ if filtr[0] == 'tag-key': |
|
| 216 |
+ return filtr[1] in key['tags'] |
|
| 217 |
+ if filtr[0] == 'tag-value': |
|
| 218 |
+ return filtr[1] in key['tags'].values() |
|
| 219 |
+ if filtr[0] == 'alias': |
|
| 220 |
+ return filtr[1] in key['aliases'] |
|
| 221 |
+ if filtr[0].startswith('tag:'):
|
|
| 222 |
+ return key['Tags'][filtr[0][4:]] == filtr[1] |
|
| 223 |
+ |
|
| 224 |
+ |
|
| 225 |
+def key_matches_filters(key, filters): |
|
| 226 |
+ if not filters: |
|
| 227 |
+ return True |
|
| 228 |
+ else: |
|
| 229 |
+ return all([key_matches_filter(key, filtr) for filtr in filters.items()]) |
|
| 230 |
+ |
|
| 231 |
+ |
|
| 232 |
+def camel_to_snake_grant(grant): |
|
| 233 |
+ ''' camel_to_snake_grant snakifies everything except the encryption context ''' |
|
| 234 |
+ constraints = grant.get('Constraints', {})
|
|
| 235 |
+ result = camel_dict_to_snake_dict(grant) |
|
| 236 |
+ if 'EncryptionContextEquals' in constraints: |
|
| 237 |
+ result['constraints']['encryption_context_equals'] = constraints['EncryptionContextEquals'] |
|
| 238 |
+ if 'EncryptionContextSubset' in constraints: |
|
| 239 |
+ result['constraints']['encryption_context_subset'] = constraints['EncryptionContextSubset'] |
|
| 240 |
+ return result |
|
| 241 |
+ |
|
| 242 |
+ |
|
| 243 |
+def get_key_details(connection, module, key_id): |
|
| 244 |
+ try: |
|
| 245 |
+ result = get_kms_metadata_with_backoff(connection, key_id)['KeyMetadata'] |
|
| 246 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 247 |
+ module.fail_json_aws(e, msg="Failed to obtain key metadata") |
|
| 248 |
+ result['KeyArn'] = result.pop('Arn')
|
|
| 249 |
+ |
|
| 250 |
+ try: |
|
| 251 |
+ aliases = get_kms_aliases_lookup(connection) |
|
| 252 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 253 |
+ module.fail_json_aws(e, msg="Failed to obtain aliases") |
|
| 254 |
+ |
|
| 255 |
+ result['aliases'] = aliases.get(result['KeyId'], []) |
|
| 256 |
+ |
|
| 257 |
+ result = camel_dict_to_snake_dict(result) |
|
| 258 |
+ |
|
| 259 |
+ # grants and tags get snakified differently |
|
| 260 |
+ try: |
|
| 261 |
+ result['grants'] = [camel_to_snake_grant(grant) for grant in |
|
| 262 |
+ get_kms_grants_with_backoff(connection, key_id)['Grants']] |
|
| 263 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 264 |
+ module.fail_json_aws(e, msg="Failed to obtain key grants") |
|
| 265 |
+ tags = get_kms_tags(connection, module, key_id) |
|
| 266 |
+ result['tags'] = boto3_tag_list_to_ansible_dict(tags, 'TagKey', 'TagValue') |
|
| 267 |
+ result['policies'] = get_kms_policies(connection, module, key_id) |
|
| 268 |
+ return result |
|
| 269 |
+ |
|
| 270 |
+ |
|
| 271 |
+def get_kms_facts(connection, module): |
|
| 272 |
+ try: |
|
| 273 |
+ keys = get_kms_keys_with_backoff(connection)['Keys'] |
|
| 274 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 275 |
+ module.fail_json_aws(e, msg="Failed to obtain keys") |
|
| 276 |
+ |
|
| 277 |
+ return [get_key_details(connection, module, key['KeyId']) for key in keys] |
|
| 278 |
+ |
|
| 279 |
+ |
|
| 280 |
+def convert_grant_params(grant, key): |
|
| 281 |
+ grant_params = dict(KeyId=key['key_id'], |
|
| 282 |
+ GranteePrincipal=grant['grantee_principal']) |
|
| 283 |
+ if grant.get('operations'):
|
|
| 284 |
+ grant_params['Operations'] = grant['operations'] |
|
| 285 |
+ if grant.get('retiring_principal'):
|
|
| 286 |
+ grant_params['RetiringPrincipal'] = grant['retiring_principal'] |
|
| 287 |
+ if grant.get('name'):
|
|
| 288 |
+ grant_params['Name'] = grant['name'] |
|
| 289 |
+ if grant.get('constraints'):
|
|
| 290 |
+ grant_params['Constraints'] = dict() |
|
| 291 |
+ if grant['constraints'].get('encryption_context_subset'):
|
|
| 292 |
+ grant_params['Constraints']['EncryptionContextSubset'] = grant['constraints']['encryption_context_subset'] |
|
| 293 |
+ if grant['constraints'].get('encryption_context_equals'):
|
|
| 294 |
+ grant_params['Constraints']['EncryptionContextEquals'] = grant['constraints']['encryption_context_equals'] |
|
| 295 |
+ return grant_params |
|
| 296 |
+ |
|
| 297 |
+ |
|
| 298 |
+def different_grant(existing_grant, desired_grant): |
|
| 299 |
+ if existing_grant.get('grantee_principal') != desired_grant.get('grantee_principal'):
|
|
| 300 |
+ return True |
|
| 301 |
+ if existing_grant.get('retiring_principal') != desired_grant.get('retiring_principal'):
|
|
| 302 |
+ return True |
|
| 303 |
+ if set(existing_grant.get('operations', [])) != set(desired_grant.get('operations')):
|
|
| 304 |
+ return True |
|
| 305 |
+ if existing_grant.get('constraints') != desired_grant.get('constraints'):
|
|
| 306 |
+ return True |
|
| 307 |
+ return False |
|
| 308 |
+ |
|
| 309 |
+ |
|
| 310 |
+def compare_grants(existing_grants, desired_grants, purge_grants=False): |
|
| 311 |
+ existing_dict = dict((eg['name'], eg) for eg in existing_grants) |
|
| 312 |
+ desired_dict = dict((dg['name'], dg) for dg in desired_grants) |
|
| 313 |
+ to_add_keys = set(desired_dict.keys()) - set(existing_dict.keys()) |
|
| 314 |
+ if purge_grants: |
|
| 315 |
+ to_remove_keys = set(existing_dict.keys()) - set(desired_dict.keys()) |
|
| 316 |
+ else: |
|
| 317 |
+ to_remove_keys = set() |
|
| 318 |
+ to_change_candidates = set(existing_dict.keys()) & set(desired_dict.keys()) |
|
| 319 |
+ for candidate in to_change_candidates: |
|
| 320 |
+ if different_grant(existing_dict[candidate], desired_dict[candidate]): |
|
| 321 |
+ to_add_keys.add(candidate) |
|
| 322 |
+ to_remove_keys.add(candidate) |
|
| 323 |
+ |
|
| 324 |
+ to_add = [] |
|
| 325 |
+ to_remove = [] |
|
| 326 |
+ for key in to_add_keys: |
|
| 327 |
+ grant = desired_dict[key] |
|
| 328 |
+ to_add.append(grant) |
|
| 329 |
+ for key in to_remove_keys: |
|
| 330 |
+ grant = existing_dict[key] |
|
| 331 |
+ to_remove.append(grant) |
|
| 332 |
+ return to_add, to_remove |
|
| 333 |
+ |
|
| 334 |
+ |
|
| 335 |
+def ensure_enabled_disabled(connection, module, key): |
|
| 336 |
+ changed = False |
|
| 337 |
+ if key['key_state'] == 'Disabled' and module.params['enabled']: |
|
| 338 |
+ try: |
|
| 339 |
+ connection.enable_key(KeyId=key['key_id']) |
|
| 340 |
+ changed = True |
|
| 341 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 342 |
+ module.fail_json_aws(e, msg="Failed to enable key") |
|
| 343 |
+ |
|
| 344 |
+ if key['key_state'] == 'Enabled' and not module.params['enabled']: |
|
| 345 |
+ try: |
|
| 346 |
+ connection.disable_key(KeyId=key['key_id']) |
|
| 347 |
+ changed = True |
|
| 348 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 349 |
+ module.fail_json_aws(e, msg="Failed to disable key") |
|
| 350 |
+ return changed |
|
| 351 |
+ |
|
| 352 |
+ |
|
| 353 |
+def update_key(connection, module, key): |
|
| 354 |
+ changed = False |
|
| 355 |
+ alias = module.params['alias'] |
|
| 356 |
+ if not alias.startswith('alias/'):
|
|
| 357 |
+ alias = 'alias/' + alias |
|
| 358 |
+ aliases = get_kms_aliases_with_backoff(connection)['Aliases'] |
|
| 359 |
+ key_id = module.params.get('key_id')
|
|
| 360 |
+ if key_id: |
|
| 361 |
+ # We will only add new aliases, not rename existing ones |
|
| 362 |
+ if alias not in [_alias['AliasName'] for _alias in aliases]: |
|
| 363 |
+ try: |
|
| 364 |
+ connection.create_alias(KeyId=key_id, AliasName=alias) |
|
| 365 |
+ changed = True |
|
| 366 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 367 |
+ module.fail_json_aws(msg="Failed create key alias") |
|
| 368 |
+ |
|
| 369 |
+ if key['key_state'] == 'PendingDeletion': |
|
| 370 |
+ try: |
|
| 371 |
+ connection.cancel_key_deletion(KeyId=key['key_id']) |
|
| 372 |
+ # key is disabled after deletion cancellation |
|
| 373 |
+ # set this so that ensure_enabled_disabled works correctly |
|
| 374 |
+ key['key_state'] = 'Disabled' |
|
| 375 |
+ changed = True |
|
| 376 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 377 |
+ module.fail_json_aws(e, msg="Failed to cancel key deletion") |
|
| 378 |
+ |
|
| 379 |
+ changed = ensure_enabled_disabled(connection, module, key) or changed |
|
| 380 |
+ |
|
| 381 |
+ description = module.params.get('description')
|
|
| 382 |
+ # don't update description if description is not set |
|
| 383 |
+ # (means you can't remove a description completely) |
|
| 384 |
+ if description and key['description'] != description: |
|
| 385 |
+ try: |
|
| 386 |
+ connection.update_key_description(KeyId=key['key_id'], Description=description) |
|
| 387 |
+ changed = True |
|
| 388 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 389 |
+ module.fail_json_aws(e, msg="Failed to update key description") |
|
| 390 |
+ |
|
| 391 |
+ desired_tags = module.params.get('tags')
|
|
| 392 |
+ to_add, to_remove = compare_aws_tags(key['tags'], desired_tags, |
|
| 393 |
+ module.params.get('purge_tags'))
|
|
| 394 |
+ if to_remove: |
|
| 395 |
+ try: |
|
| 396 |
+ connection.untag_resource(KeyId=key['key_id'], TagKeys=to_remove) |
|
| 397 |
+ changed = True |
|
| 398 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 399 |
+ module.fail_json_aws(e, msg="Unable to remove or update tag") |
|
| 400 |
+ if to_add: |
|
| 401 |
+ try: |
|
| 402 |
+ connection.tag_resource(KeyId=key['key_id'], |
|
| 403 |
+ Tags=[{'TagKey': tag_key, 'TagValue': desired_tags[tag_key]}
|
|
| 404 |
+ for tag_key in to_add]) |
|
| 405 |
+ changed = True |
|
| 406 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 407 |
+ module.fail_json_aws(e, msg="Unable to add tag to key") |
|
| 408 |
+ |
|
| 409 |
+ desired_grants = module.params.get('grants')
|
|
| 410 |
+ existing_grants = key['grants'] |
|
| 411 |
+ |
|
| 412 |
+ to_add, to_remove = compare_grants(existing_grants, desired_grants, |
|
| 413 |
+ module.params.get('purge_grants'))
|
|
| 414 |
+ if to_remove: |
|
| 415 |
+ for grant in to_remove: |
|
| 416 |
+ try: |
|
| 417 |
+ connection.retire_grant(KeyId=key['key_arn'], GrantId=grant['grant_id']) |
|
| 418 |
+ changed = True |
|
| 419 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 420 |
+ module.fail_json_aws(e, msg="Unable to retire grant") |
|
| 421 |
+ |
|
| 422 |
+ if to_add: |
|
| 423 |
+ for grant in to_add: |
|
| 424 |
+ grant_params = convert_grant_params(grant, key) |
|
| 425 |
+ try: |
|
| 426 |
+ connection.create_grant(**grant_params) |
|
| 427 |
+ changed = True |
|
| 428 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 429 |
+ module.fail_json_aws(e, msg="Unable to create grant") |
|
| 430 |
+ |
|
| 431 |
+ # make results consistent with kms_facts |
|
| 432 |
+ result = get_key_details(connection, module, key['key_id']) |
|
| 433 |
+ module.exit_json(changed=changed, **camel_dict_to_snake_dict(result)) |
|
| 434 |
+ |
|
| 435 |
+ |
|
| 436 |
+def create_key(connection, module): |
|
| 437 |
+ params = dict(BypassPolicyLockoutSafetyCheck=False, |
|
| 438 |
+ Tags=ansible_dict_to_boto3_tag_list(module.params['tags']), |
|
| 439 |
+ KeyUsage='ENCRYPT_DECRYPT', |
|
| 440 |
+ Origin='AWS_KMS') |
|
| 441 |
+ if module.params.get('description'):
|
|
| 442 |
+ params['Description'] = module.params['description'] |
|
| 443 |
+ if module.params.get('policy'):
|
|
| 444 |
+ params['Policy'] = module.params['policy'] |
|
| 445 |
+ |
|
| 446 |
+ try: |
|
| 447 |
+ result = connection.create_key(**params)['KeyMetadata'] |
|
| 448 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 449 |
+ module.fail_json_aws(e, msg="Failed to create initial key") |
|
| 450 |
+ key = get_key_details(connection, module, result['KeyId']) |
|
| 451 |
+ |
|
| 452 |
+ alias = module.params['alias'] |
|
| 453 |
+ if not alias.startswith('alias/'):
|
|
| 454 |
+ alias = 'alias/' + alias |
|
| 455 |
+ try: |
|
| 456 |
+ connection.create_alias(AliasName=alias, TargetKeyId=key['key_id']) |
|
| 457 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 458 |
+ module.fail_json_aws(e, msg="Failed to create alias") |
|
| 459 |
+ |
|
| 460 |
+ ensure_enabled_disabled(connection, module, key) |
|
| 461 |
+ for grant in module.params.get('grants'):
|
|
| 462 |
+ grant_params = convert_grant_params(grant, key) |
|
| 463 |
+ try: |
|
| 464 |
+ connection.create_grant(**grant_params) |
|
| 465 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 466 |
+ module.fail_json_aws(e, msg="Failed to add grant to key") |
|
| 467 |
+ |
|
| 468 |
+ # make results consistent with kms_facts |
|
| 469 |
+ result = get_key_details(connection, module, key['key_id']) |
|
| 470 |
+ module.exit_json(changed=True, **camel_dict_to_snake_dict(result)) |
|
| 471 |
+ |
|
| 472 |
+ |
|
| 473 |
+def delete_key(connection, module, key): |
|
| 474 |
+ changed = False |
|
| 475 |
+ |
|
| 476 |
+ if key['key_state'] != 'PendingDeletion': |
|
| 477 |
+ try: |
|
| 478 |
+ connection.schedule_key_deletion(KeyId=key['key_id']) |
|
| 479 |
+ changed = True |
|
| 480 |
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: |
|
| 481 |
+ module.fail_json_aws(e, msg="Failed to schedule key for deletion") |
|
| 482 |
+ |
|
| 483 |
+ result = get_key_details(connection, module, key['key_id']) |
|
| 484 |
+ module.exit_json(changed=changed, **camel_dict_to_snake_dict(result)) |
|
| 122 | 485 |
|
| 123 | 486 |
|
| 124 | 487 |
def get_arn_from_kms_alias(kms, aliasname): |
| ... | ... |
@@ -184,16 +789,19 @@ def do_grant(kms, keyarn, role_arn, granttypes, mode='grant', dry_run=True, clea |
| 184 | 184 |
|
| 185 | 185 |
if role_arn not in statement['Principal']['AWS']: # needs to be added. |
| 186 | 186 |
changes_needed[granttype] = 'add' |
| 187 |
- statement['Principal']['AWS'].append(role_arn) |
|
| 187 |
+ if not dry_run: |
|
| 188 |
+ statement['Principal']['AWS'].append(role_arn) |
|
| 188 | 189 |
elif role_arn in statement['Principal']['AWS']: # not one the places the role should be |
| 189 | 190 |
changes_needed[granttype] = 'remove' |
| 190 |
- statement['Principal']['AWS'].remove(role_arn) |
|
| 191 |
+ if not dry_run: |
|
| 192 |
+ statement['Principal']['AWS'].remove(role_arn) |
|
| 191 | 193 |
|
| 192 | 194 |
elif mode == 'deny' and statement['Sid'] == statement_label[granttype] and role_arn in statement['Principal']['AWS']: |
| 193 | 195 |
# we don't selectively deny. that's a grant with a |
| 194 | 196 |
# smaller list. so deny=remove all of this arn. |
| 195 | 197 |
changes_needed[granttype] = 'remove' |
| 196 |
- statement['Principal']['AWS'].remove(role_arn) |
|
| 198 |
+ if not dry_run: |
|
| 199 |
+ statement['Principal']['AWS'].remove(role_arn) |
|
| 197 | 200 |
|
| 198 | 201 |
try: |
| 199 | 202 |
if len(changes_needed) and not dry_run: |
| ... | ... |
@@ -236,43 +844,40 @@ def assert_policy_shape(policy): |
| 236 | 236 |
|
| 237 | 237 |
|
| 238 | 238 |
def main(): |
| 239 |
- argument_spec = ansible.module_utils.ec2.ec2_argument_spec() |
|
| 240 |
- argument_spec.update(dict( |
|
| 241 |
- mode=dict(choices=['grant', 'deny'], default='grant'), |
|
| 242 |
- key_alias=dict(required=False, type='str'), |
|
| 243 |
- key_arn=dict(required=False, type='str'), |
|
| 244 |
- role_name=dict(required=False, type='str'), |
|
| 245 |
- role_arn=dict(required=False, type='str'), |
|
| 246 |
- grant_types=dict(required=False, type='list'), |
|
| 247 |
- clean_invalid_entries=dict(type='bool', default=True), |
|
| 248 |
- ) |
|
| 239 |
+ argument_spec = ec2_argument_spec() |
|
| 240 |
+ argument_spec.update( |
|
| 241 |
+ dict( |
|
| 242 |
+ mode=dict(choices=['grant', 'deny'], default='grant'), |
|
| 243 |
+ alias=dict(aliases=['key_alias']), |
|
| 244 |
+ role_name=dict(), |
|
| 245 |
+ role_arn=dict(), |
|
| 246 |
+ grant_types=dict(type='list'), |
|
| 247 |
+ clean_invalid_entries=dict(type='bool', default=True), |
|
| 248 |
+ key_id=dict(aliases=['key_arn']), |
|
| 249 |
+ description=dict(), |
|
| 250 |
+ enabled=dict(type='bool', default=True), |
|
| 251 |
+ tags=dict(type='dict', default={}),
|
|
| 252 |
+ purge_tags=dict(type='bool', default=False), |
|
| 253 |
+ grants=dict(type='list', default=[]), |
|
| 254 |
+ policy=dict(), |
|
| 255 |
+ purge_grants=dict(type='bool', default=False), |
|
| 256 |
+ state=dict(default='present', choices=['present', 'absent']), |
|
| 257 |
+ ) |
|
| 249 | 258 |
) |
| 250 | 259 |
|
| 251 |
- module = AnsibleModule( |
|
| 260 |
+ module = AnsibleAWSModule( |
|
| 252 | 261 |
supports_check_mode=True, |
| 253 | 262 |
argument_spec=argument_spec, |
| 254 |
- required_one_of=[['key_alias', 'key_arn'], ['role_name', 'role_arn']], |
|
| 255 |
- required_if=[['mode', 'grant', ['grant_types']]] |
|
| 263 |
+ required_one_of=[['alias', 'key_id']], |
|
| 256 | 264 |
) |
| 257 |
- if not HAS_BOTO3: |
|
| 258 |
- module.fail_json(msg='boto3 required for this module') |
|
| 259 | 265 |
|
| 260 | 266 |
result = {}
|
| 261 | 267 |
mode = module.params['mode'] |
| 262 | 268 |
|
| 263 |
- try: |
|
| 264 |
- region, ec2_url, aws_connect_kwargs = ansible.module_utils.ec2.get_aws_connection_info(module, boto3=True) |
|
| 265 |
- kms = ansible.module_utils.ec2.boto3_conn(module, conn_type='client', resource='kms', region=region, endpoint=ec2_url, **aws_connect_kwargs) |
|
| 266 |
- iam = ansible.module_utils.ec2.boto3_conn(module, conn_type='client', resource='iam', region=region, endpoint=ec2_url, **aws_connect_kwargs) |
|
| 267 |
- except botocore.exceptions.NoCredentialsError as e: |
|
| 268 |
- module.fail_json(msg='cannot connect to AWS', exception=traceback.format_exc()) |
|
| 269 |
- |
|
| 270 |
- try: |
|
| 271 |
- if module.params['key_alias'] and not module.params['key_arn']: |
|
| 272 |
- module.params['key_arn'] = get_arn_from_kms_alias(kms, module.params['key_alias']) |
|
| 273 |
- if not module.params['key_arn']: |
|
| 274 |
- module.fail_json(msg='key_arn or key_alias is required to {}'.format(mode))
|
|
| 269 |
+ kms = module.client('kms')
|
|
| 270 |
+ iam = module.client('iam')
|
|
| 275 | 271 |
|
| 272 |
+ if module.params['grant_types'] or mode == 'deny': |
|
| 276 | 273 |
if module.params['role_name'] and not module.params['role_arn']: |
| 277 | 274 |
module.params['role_arn'] = get_arn_from_role_name(iam, module.params['role_name']) |
| 278 | 275 |
if not module.params['role_arn']: |
| ... | ... |
@@ -290,11 +895,31 @@ def main(): |
| 290 | 290 |
clean_invalid_entries=module.params['clean_invalid_entries']) |
| 291 | 291 |
result.update(ret) |
| 292 | 292 |
|
| 293 |
- except Exception as err: |
|
| 294 |
- error_msg = boto_exception(err) |
|
| 295 |
- module.fail_json(msg=error_msg, exception=traceback.format_exc()) |
|
| 296 |
- |
|
| 297 |
- module.exit_json(**result) |
|
| 293 |
+ module.exit_json(**result) |
|
| 294 |
+ else: |
|
| 295 |
+ all_keys = get_kms_facts(kms, module) |
|
| 296 |
+ key_id = module.params.get('key_id')
|
|
| 297 |
+ alias = module.params.get('alias')
|
|
| 298 |
+ if key_id: |
|
| 299 |
+ filtr = ('key-id', key_id)
|
|
| 300 |
+ elif module.params.get('alias'):
|
|
| 301 |
+ filtr = ('alias', alias)
|
|
| 302 |
+ |
|
| 303 |
+ candidate_keys = [key for key in all_keys if key_matches_filter(key, filtr)] |
|
| 304 |
+ |
|
| 305 |
+ if module.params.get('state') == 'present':
|
|
| 306 |
+ if candidate_keys: |
|
| 307 |
+ update_key(kms, module, candidate_keys[0]) |
|
| 308 |
+ else: |
|
| 309 |
+ if module.params.get('key_id'):
|
|
| 310 |
+ module.fail_json(msg="Could not find key with id %s to update") |
|
| 311 |
+ else: |
|
| 312 |
+ create_key(kms, module) |
|
| 313 |
+ else: |
|
| 314 |
+ if candidate_keys: |
|
| 315 |
+ delete_key(kms, module, candidate_keys[0]) |
|
| 316 |
+ else: |
|
| 317 |
+ module.exit_json(changed=False) |
|
| 298 | 318 |
|
| 299 | 319 |
|
| 300 | 320 |
if __name__ == '__main__': |
| 0 | 3 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,394 @@ |
| 0 |
+- block: |
|
| 1 |
+ |
|
| 2 |
+ # ============================================================ |
|
| 3 |
+ - name: See whether key exists and its current state |
|
| 4 |
+ aws_kms_facts: |
|
| 5 |
+ region: "{{ aws_region }}"
|
|
| 6 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 7 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 8 |
+ security_token: "{{ security_token }}"
|
|
| 9 |
+ filters: |
|
| 10 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 11 |
+ |
|
| 12 |
+ - name: create a key |
|
| 13 |
+ aws_kms: |
|
| 14 |
+ region: "{{ aws_region }}"
|
|
| 15 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 16 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 17 |
+ security_token: "{{ security_token }}"
|
|
| 18 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 19 |
+ state: present |
|
| 20 |
+ enabled: yes |
|
| 21 |
+ register: create_kms |
|
| 22 |
+ |
|
| 23 |
+ - name: assert that state is enabled |
|
| 24 |
+ assert: |
|
| 25 |
+ that: |
|
| 26 |
+ - create_kms.key_state == "Enabled" |
|
| 27 |
+ |
|
| 28 |
+ - name: find facts about the key |
|
| 29 |
+ aws_kms_facts: |
|
| 30 |
+ region: "{{ aws_region }}"
|
|
| 31 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 32 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 33 |
+ security_token: "{{ security_token }}"
|
|
| 34 |
+ filters: |
|
| 35 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 36 |
+ register: new_key |
|
| 37 |
+ |
|
| 38 |
+ - name: check that a key was found |
|
| 39 |
+ assert: |
|
| 40 |
+ that: |
|
| 41 |
+ - new_key["keys"]|length == 1 |
|
| 42 |
+ |
|
| 43 |
+ - name: create an IAM role that can do nothing |
|
| 44 |
+ iam_role: |
|
| 45 |
+ name: "{{ resource_prefix }}-kms-role"
|
|
| 46 |
+ state: present |
|
| 47 |
+ assume_role_policy_document: '{"Version": "2012-10-17", "Statement": {"Action": "sts:AssumeRole", "Principal": {"Service": "ec2.amazonaws.com"}, "Effect": "Deny"} }'
|
|
| 48 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 49 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 50 |
+ security_token: "{{ security_token }}"
|
|
| 51 |
+ register: iam_role_result |
|
| 52 |
+ |
|
| 53 |
+ - name: grant user-style access to production secrets |
|
| 54 |
+ aws_kms: |
|
| 55 |
+ mode: grant |
|
| 56 |
+ key_alias: "alias/{{ resource_prefix }}-kms"
|
|
| 57 |
+ role_name: "{{ resource_prefix }}-kms-role"
|
|
| 58 |
+ grant_types: "role,role grant" |
|
| 59 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 60 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 61 |
+ security_token: "{{ security_token }}"
|
|
| 62 |
+ region: "{{ aws_region }}"
|
|
| 63 |
+ |
|
| 64 |
+ - name: find facts about the key |
|
| 65 |
+ aws_kms_facts: |
|
| 66 |
+ region: "{{ aws_region }}"
|
|
| 67 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 68 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 69 |
+ security_token: "{{ security_token }}"
|
|
| 70 |
+ filters: |
|
| 71 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 72 |
+ register: new_key |
|
| 73 |
+ |
|
| 74 |
+ - name: remove access to production secrets from role |
|
| 75 |
+ aws_kms: |
|
| 76 |
+ mode: deny |
|
| 77 |
+ key_alias: "alias/{{ resource_prefix }}-kms"
|
|
| 78 |
+ role_arn: "{{ iam_role_result.iam_role.arn }}"
|
|
| 79 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 80 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 81 |
+ security_token: "{{ security_token }}"
|
|
| 82 |
+ region: "{{ aws_region }}"
|
|
| 83 |
+ |
|
| 84 |
+ - name: find facts about the key |
|
| 85 |
+ aws_kms_facts: |
|
| 86 |
+ region: "{{ aws_region }}"
|
|
| 87 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 88 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 89 |
+ security_token: "{{ security_token }}"
|
|
| 90 |
+ filters: |
|
| 91 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 92 |
+ register: new_key |
|
| 93 |
+ |
|
| 94 |
+ - fail: |
|
| 95 |
+ |
|
| 96 |
+ - name: set aws environment base fact |
|
| 97 |
+ set_fact: |
|
| 98 |
+ aws_environment_base: |
|
| 99 |
+ AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
|
|
| 100 |
+ AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
|
|
| 101 |
+ no_log: True |
|
| 102 |
+ |
|
| 103 |
+ - name: set aws environment fact |
|
| 104 |
+ set_fact: |
|
| 105 |
+ aws_environment: "{{ aws_environment_base|combine(security_token|ternary({'AWS_SECURITY_TOKEN': security_token}, {})) }}"
|
|
| 106 |
+ no_log: True |
|
| 107 |
+ |
|
| 108 |
+ - name: get ARN of calling user |
|
| 109 |
+ command: python -c 'import boto3,json; sts = boto3.client("sts"); print json.dumps(sts.get_caller_identity())'
|
|
| 110 |
+ changed_when: False |
|
| 111 |
+ environment: "{{ aws_environment }}"
|
|
| 112 |
+ register: sts_get_caller_results |
|
| 113 |
+ |
|
| 114 |
+ - name: set caller_arn |
|
| 115 |
+ set_fact: |
|
| 116 |
+ caller_arn: "{{ (sts_get_caller_results.stdout|from_json).Arn }}"
|
|
| 117 |
+ |
|
| 118 |
+ - name: Allow the IAM role to use a specific Encryption Context |
|
| 119 |
+ aws_kms: |
|
| 120 |
+ region: "{{ aws_region }}"
|
|
| 121 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 122 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 123 |
+ security_token: "{{ security_token }}"
|
|
| 124 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 125 |
+ state: present |
|
| 126 |
+ purge_grants: yes |
|
| 127 |
+ purge_tags: yes |
|
| 128 |
+ grants: |
|
| 129 |
+ - name: test_grant |
|
| 130 |
+ grantee_principal: "{{ iam_role_result.iam_role.arn }}"
|
|
| 131 |
+ retiring_principal: "{{ caller_arn }}"
|
|
| 132 |
+ constraints: |
|
| 133 |
+ encryption_context_equals: |
|
| 134 |
+ environment: test |
|
| 135 |
+ application: testapp |
|
| 136 |
+ operations: |
|
| 137 |
+ - Decrypt |
|
| 138 |
+ - RetireGrant |
|
| 139 |
+ register: grant_one |
|
| 140 |
+ |
|
| 141 |
+ - name: assert grant added |
|
| 142 |
+ assert: |
|
| 143 |
+ that: |
|
| 144 |
+ - grant_one.changed |
|
| 145 |
+ - grant_one.grants|length == 1 |
|
| 146 |
+ |
|
| 147 |
+ - name: Add a second grant |
|
| 148 |
+ kms: |
|
| 149 |
+ region: "{{ aws_region }}"
|
|
| 150 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 151 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 152 |
+ security_token: "{{ security_token }}"
|
|
| 153 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 154 |
+ state: present |
|
| 155 |
+ grants: |
|
| 156 |
+ - name: another_grant |
|
| 157 |
+ grantee_principal: "{{ iam_role_result.iam_role.arn }}"
|
|
| 158 |
+ retiring_principal: "{{ caller_arn }}"
|
|
| 159 |
+ constraints: |
|
| 160 |
+ encryption_context_equals: |
|
| 161 |
+ Environment: second |
|
| 162 |
+ Application: anotherapp |
|
| 163 |
+ operations: |
|
| 164 |
+ - Decrypt |
|
| 165 |
+ - RetireGrant |
|
| 166 |
+ register: grant_two |
|
| 167 |
+ |
|
| 168 |
+ - name: assert grant added |
|
| 169 |
+ assert: |
|
| 170 |
+ that: |
|
| 171 |
+ - grant_two.changed |
|
| 172 |
+ - grant_two.grants|length == 2 |
|
| 173 |
+ |
|
| 174 |
+ - name: Add a second grant again |
|
| 175 |
+ aws_kms: |
|
| 176 |
+ region: "{{ aws_region }}"
|
|
| 177 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 178 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 179 |
+ security_token: "{{ security_token }}"
|
|
| 180 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 181 |
+ state: present |
|
| 182 |
+ grants: |
|
| 183 |
+ - name: another_grant |
|
| 184 |
+ grantee_principal: "{{ iam_role_result.iam_role.arn }}"
|
|
| 185 |
+ retiring_principal: "{{ caller_arn }}"
|
|
| 186 |
+ constraints: |
|
| 187 |
+ encryption_context_equals: |
|
| 188 |
+ Environment: second |
|
| 189 |
+ Application: anotherapp |
|
| 190 |
+ operations: |
|
| 191 |
+ - Decrypt |
|
| 192 |
+ - RetireGrant |
|
| 193 |
+ register: grant_two_again |
|
| 194 |
+ |
|
| 195 |
+ - name: assert grant added |
|
| 196 |
+ assert: |
|
| 197 |
+ that: |
|
| 198 |
+ - not grant_two_again.changed |
|
| 199 |
+ - grant_two_again.grants|length == 2 |
|
| 200 |
+ |
|
| 201 |
+ - name: Update the grants with purge_grants set |
|
| 202 |
+ aws_kms: |
|
| 203 |
+ region: "{{ aws_region }}"
|
|
| 204 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 205 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 206 |
+ security_token: "{{ security_token }}"
|
|
| 207 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 208 |
+ state: present |
|
| 209 |
+ purge_grants: yes |
|
| 210 |
+ grants: |
|
| 211 |
+ - name: third_grant |
|
| 212 |
+ grantee_principal: "{{ iam_role_result.iam_role.arn }}"
|
|
| 213 |
+ retiring_principal: "{{ caller_arn }}"
|
|
| 214 |
+ constraints: |
|
| 215 |
+ encryption_context_equals: |
|
| 216 |
+ environment: third |
|
| 217 |
+ application: onemoreapp |
|
| 218 |
+ operations: |
|
| 219 |
+ - Decrypt |
|
| 220 |
+ - RetireGrant |
|
| 221 |
+ register: grant_three |
|
| 222 |
+ |
|
| 223 |
+ - name: assert grants replaced |
|
| 224 |
+ assert: |
|
| 225 |
+ that: |
|
| 226 |
+ - grant_three.changed |
|
| 227 |
+ - grant_three.grants|length == 1 |
|
| 228 |
+ |
|
| 229 |
+ - name: update third grant to change encryption context equals to subset |
|
| 230 |
+ aws_kms: |
|
| 231 |
+ region: "{{ aws_region }}"
|
|
| 232 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 233 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 234 |
+ security_token: "{{ security_token }}"
|
|
| 235 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 236 |
+ state: present |
|
| 237 |
+ grants: |
|
| 238 |
+ - name: third_grant |
|
| 239 |
+ grantee_principal: "{{ iam_role_result.iam_role.arn }}"
|
|
| 240 |
+ retiring_principal: "{{ caller_arn }}"
|
|
| 241 |
+ constraints: |
|
| 242 |
+ encryption_context_subset: |
|
| 243 |
+ environment: third |
|
| 244 |
+ application: onemoreapp |
|
| 245 |
+ operations: |
|
| 246 |
+ - Decrypt |
|
| 247 |
+ - RetireGrant |
|
| 248 |
+ register: grant_three_update |
|
| 249 |
+ |
|
| 250 |
+ - name: assert grants replaced |
|
| 251 |
+ assert: |
|
| 252 |
+ that: |
|
| 253 |
+ - "grant_three_update.changed" |
|
| 254 |
+ - "grant_three_update.grants|length == 1" |
|
| 255 |
+ - "'encryption_context_equals' not in grant_three_update.grants[0].constraints" |
|
| 256 |
+ - "'encryption_context_subset' in grant_three_update.grants[0].constraints" |
|
| 257 |
+ |
|
| 258 |
+ - name: tag encryption key |
|
| 259 |
+ aws_kms: |
|
| 260 |
+ region: "{{ aws_region }}"
|
|
| 261 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 262 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 263 |
+ security_token: "{{ security_token }}"
|
|
| 264 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 265 |
+ state: present |
|
| 266 |
+ tags: |
|
| 267 |
+ tag_one: tag_one |
|
| 268 |
+ tag_two: tag_two |
|
| 269 |
+ register: tag_kms |
|
| 270 |
+ |
|
| 271 |
+ - name: assert tags added and grants remain in place |
|
| 272 |
+ assert: |
|
| 273 |
+ that: |
|
| 274 |
+ - "tag_kms.changed" |
|
| 275 |
+ - "tag_kms.grants|length == 1" |
|
| 276 |
+ - "'tag_one' in tag_kms.tags" |
|
| 277 |
+ - "'tag_two' in tag_kms.tags" |
|
| 278 |
+ |
|
| 279 |
+ - name: add, replace, remove tags |
|
| 280 |
+ aws_kms: |
|
| 281 |
+ region: "{{ aws_region }}"
|
|
| 282 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 283 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 284 |
+ security_token: "{{ security_token }}"
|
|
| 285 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 286 |
+ state: present |
|
| 287 |
+ purge_tags: yes |
|
| 288 |
+ tags: |
|
| 289 |
+ tag_two: tag_two_updated |
|
| 290 |
+ tag_three: tag_three |
|
| 291 |
+ register: tag_kms_update |
|
| 292 |
+ |
|
| 293 |
+ - name: assert tags correctly changed |
|
| 294 |
+ assert: |
|
| 295 |
+ that: |
|
| 296 |
+ - "tag_kms_update.changed" |
|
| 297 |
+ - "'tag_one' not in tag_kms_update.tags" |
|
| 298 |
+ - "'tag_two' in tag_kms_update.tags" |
|
| 299 |
+ - "tag_kms_update.tags.tag_two == 'tag_two_updated'" |
|
| 300 |
+ - "'tag_three' in tag_kms_update.tags" |
|
| 301 |
+ |
|
| 302 |
+ - name: make no real tag change |
|
| 303 |
+ aws_kms: |
|
| 304 |
+ region: "{{ aws_region }}"
|
|
| 305 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 306 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 307 |
+ security_token: "{{ security_token }}"
|
|
| 308 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 309 |
+ state: present |
|
| 310 |
+ register: tag_kms_no_update |
|
| 311 |
+ |
|
| 312 |
+ - name: assert no change to tags |
|
| 313 |
+ assert: |
|
| 314 |
+ that: |
|
| 315 |
+ - "not tag_kms_no_update.changed" |
|
| 316 |
+ - "'tag_one' not in tag_kms_no_update.tags" |
|
| 317 |
+ - "'tag_two' in tag_kms_no_update.tags" |
|
| 318 |
+ - "tag_kms_no_update.tags.tag_two == 'tag_two_updated'" |
|
| 319 |
+ - "'tag_three' in tag_kms_no_update.tags" |
|
| 320 |
+ |
|
| 321 |
+ - name: update the key's description and disable it |
|
| 322 |
+ aws_kms: |
|
| 323 |
+ region: "{{ aws_region }}"
|
|
| 324 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 325 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 326 |
+ security_token: "{{ security_token }}"
|
|
| 327 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 328 |
+ state: present |
|
| 329 |
+ description: test key for testing |
|
| 330 |
+ enabled: no |
|
| 331 |
+ register: update_key |
|
| 332 |
+ |
|
| 333 |
+ - name: assert that state is enabled |
|
| 334 |
+ assert: |
|
| 335 |
+ that: |
|
| 336 |
+ - update_key.description == "test key for testing" |
|
| 337 |
+ - update_key.key_state == "Disabled" |
|
| 338 |
+ - update_key.changed |
|
| 339 |
+ |
|
| 340 |
+ - name: delete the key |
|
| 341 |
+ aws_kms: |
|
| 342 |
+ region: "{{ aws_region }}"
|
|
| 343 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 344 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 345 |
+ security_token: "{{ security_token }}"
|
|
| 346 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 347 |
+ state: absent |
|
| 348 |
+ register: delete_kms |
|
| 349 |
+ |
|
| 350 |
+ - name: assert that state is pending deletion |
|
| 351 |
+ assert: |
|
| 352 |
+ that: |
|
| 353 |
+ - delete_kms.key_state == "PendingDeletion" |
|
| 354 |
+ - delete_kms.changed |
|
| 355 |
+ |
|
| 356 |
+ - name: undelete and enable the key |
|
| 357 |
+ aws_kms: |
|
| 358 |
+ region: "{{ aws_region }}"
|
|
| 359 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 360 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 361 |
+ security_token: "{{ security_token }}"
|
|
| 362 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 363 |
+ state: present |
|
| 364 |
+ enabled: yes |
|
| 365 |
+ register: undelete_kms |
|
| 366 |
+ |
|
| 367 |
+ - name: assert that state is enabled |
|
| 368 |
+ assert: |
|
| 369 |
+ that: |
|
| 370 |
+ - undelete_kms.key_state == "Enabled" |
|
| 371 |
+ - undelete_kms.changed |
|
| 372 |
+ |
|
| 373 |
+ always: |
|
| 374 |
+ |
|
| 375 |
+ # ============================================================ |
|
| 376 |
+ - name: finish off by deleting key |
|
| 377 |
+ aws_kms: |
|
| 378 |
+ state: absent |
|
| 379 |
+ region: "{{ aws_region }}"
|
|
| 380 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 381 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 382 |
+ security_token: "{{ security_token }}"
|
|
| 383 |
+ alias: "{{ resource_prefix }}-kms"
|
|
| 384 |
+ register: destroy_result |
|
| 385 |
+ |
|
| 386 |
+ - name: remove the IAM role |
|
| 387 |
+ iam_role: |
|
| 388 |
+ name: "{{ resource_prefix }}-kms-role"
|
|
| 389 |
+ state: absent |
|
| 390 |
+ aws_access_key: "{{ aws_access_key }}"
|
|
| 391 |
+ aws_secret_key: "{{ aws_secret_key }}"
|
|
| 392 |
+ security_token: "{{ security_token }}"
|
|
| 393 |
+ register: iam_role_result |