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

aws_secret (lookup) - Add support for handling secrets marked for deletion #455

Merged
merged 1 commit into from
Aug 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelogs/fragments/455-lookup_aws_secret-deleted.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- aws_secret - added support for gracefully handling deleted secrets (https://github.com/ansible-collections/amazon.aws/pull/455).
30 changes: 26 additions & 4 deletions plugins/lookup/aws_secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@
- No effect when used with I(bypath).
type: boolean
default: false
on_deleted:
description:
- Action to take if the secret has been marked for deletion.
- C(error) will raise a fatal error when the secret has been marked for deletion.
- C(skip) will silently ignore the deleted secret.
- C(warn) will skip over the deleted secret but issue a warning.
default: error
type: string
choices: ['error', 'skip', 'warn']
tremble marked this conversation as resolved.
Show resolved Hide resolved
version_added: 2.0.0
on_missing:
description:
- Action to take if the secret is missing.
Expand Down Expand Up @@ -125,6 +135,7 @@
from ansible.plugins.lookup import LookupBase

from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code
from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_message
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import HAS_BOTO3


Expand All @@ -148,7 +159,7 @@ class LookupModule(LookupBase):
def run(self, terms, variables=None, boto_profile=None, aws_profile=None,
aws_secret_key=None, aws_access_key=None, aws_security_token=None, region=None,
bypath=False, nested=False, join=False, version_stage=None, version_id=None, on_missing='error',
on_denied='error'):
on_denied='error', on_deleted='error'):
'''
:arg terms: a list of lookups to run.
e.g. ['parameter_name', 'parameter_name_too' ]
Expand All @@ -164,12 +175,17 @@ def run(self, terms, variables=None, boto_profile=None, aws_profile=None,
:kwarg version_stage: Stage of the secret version
:kwarg version_id: Version of the secret(s)
:kwarg on_missing: Action to take if the secret is missing
:kwarg on_deleted: Action to take if the secret is marked for deletion
:kwarg on_denied: Action to take if access to the secret is denied
:returns: A list of parameter values or a list of dictionaries if bypath=True.
'''
if not HAS_BOTO3:
raise AnsibleError('botocore and boto3 are required for aws_ssm lookup.')

deleted = on_deleted.lower()
if not isinstance(deleted, string_types) or deleted not in ['error', 'warn', 'skip']:
raise AnsibleError('"on_deleted" must be a string and one of "error", "warn" or "skip", not %s' % deleted)

missing = on_missing.lower()
if not isinstance(missing, string_types) or missing not in ['error', 'warn', 'skip']:
raise AnsibleError('"on_missing" must be a string and one of "error", "warn" or "skip", not %s' % missing)
Expand Down Expand Up @@ -217,7 +233,8 @@ def run(self, terms, variables=None, boto_profile=None, aws_profile=None,
for term in terms:
value = self.get_secret_value(term, client,
version_stage=version_stage, version_id=version_id,
on_missing=missing, on_denied=denied, nested=nested)
on_missing=missing, on_denied=denied, on_deleted=deleted,
nested=nested)
if value:
secrets.append(value)
if join:
Expand All @@ -227,7 +244,7 @@ def run(self, terms, variables=None, boto_profile=None, aws_profile=None,

return secrets

def get_secret_value(self, term, client, version_stage=None, version_id=None, on_missing=None, on_denied=None, nested=False):
def get_secret_value(self, term, client, version_stage=None, version_id=None, on_missing=None, on_denied=None, on_deleted=None, nested=False):
params = {}
params['SecretId'] = term
if version_id:
Expand Down Expand Up @@ -258,7 +275,12 @@ def get_secret_value(self, term, client, version_stage=None, version_id=None, on
return str(ret_val)
else:
return response['SecretString']
except is_boto3_error_code('ResourceNotFoundException'):
except is_boto3_error_message('marked for deletion'):
if on_deleted == 'error':
raise AnsibleError("Failed to find secret %s (marked for deletion)" % term)
elif on_deleted == 'warn':
self._display.warning('Skipping, did not find secret (marked for deletion) %s' % term)
except is_boto3_error_code('ResourceNotFoundException'): # pylint: disable=duplicate-except
if on_missing == 'error':
raise AnsibleError("Failed to find secret %s (ResourceNotFound)" % term)
elif on_missing == 'warn':
Expand Down
69 changes: 55 additions & 14 deletions tests/integration/targets/lookup_aws_secret/tasks/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,41 +18,82 @@
block:
- name: define secret name
set_fact:
secret_name: "ansible-test-{{ resource_prefix | hash('md5') }}-secret"
secret_name: "ansible-test-{{ tiny_prefix }}-secret"
secret_value: "{{ lookup('password', '/dev/null chars=ascii_lowercase,digits,punctuation length=16') }}"
on_missing_secret: "skip"
on_deleted_secret: "skip"

- name: lookup missing secret
- name: lookup missing secret (skip)
set_fact:
missing_secret: "{{ lookup('amazon.aws.aws_secret', secret_name, on_missing=on_missing_secret, **connection_args) }}"
missing_secret: "{{ lookup('amazon.aws.aws_secret', secret_name, on_missing=on_missing_secret, on_deleted=on_deleted_secret, **connection_args) }}"

- name: assert that missing_secret is defined
assert:
that:
- missing_secret is defined
- missing_secret | list | length == 0


- name: lookup missing secret (error)
set_fact:
missing_secret: "{{ lookup('amazon.aws.aws_secret', secret_name, **connection_args) }}"
ignore_errors: True
register: get_missing_secret

- name: assert that setting the missing_secret failed
assert:
that:
- get_missing_secret is failed

- name: create secret "{{ secret_name }}"
aws_secret:
name: "{{ secret_name }}"
secret: "{{ secret_value }}"
tags:
ansible-test: "aws-tests-integration"
state: present

- name: read secret value
set_fact:
look_secret: "{{ lookup('amazon.aws.aws_secret', secret_name, **connection_args) }}"

- name: assert that secret was successfully retrieved
assert:
that:
- look_secret == secret_value


- name: delete secret
aws_secret:
name: "{{ secret_name }}"
state: absent
recovery_window: 7

- name: lookup deleted secret (skip)
set_fact:
deleted_secret: "{{ lookup('amazon.aws.aws_secret', secret_name, on_missing=on_missing_secret, on_deleted=on_deleted_secret, **connection_args) }}"

- name: assert that deleted_secret is defined
assert:
that:
- deleted_secret is defined
- deleted_secret | list | length == 0

- name: lookup deleted secret (error)
set_fact:
missing_secret: "{{ lookup('amazon.aws.aws_secret', secret_name, **connection_args) }}"
ignore_errors: True
register: get_deleted_secret

- name: assert that setting the deleted_secret failed
assert:
that:
- get_deleted_secret is failed

always:
# delete secret created
- name: delete secret
aws_secret:
name: "{{ secret_name }}"
state: absent
ignore_errors: yes

# delete secret created
- name: delete secret
aws_secret:
name: "{{ secret_name }}"
state: absent
recovery_window: 0
ignore_errors: yes