diff --git a/roles/aws/aws_admin_tools/tasks/main.yml b/roles/aws/aws_admin_tools/tasks/main.yml index 10488618d..21c1f0799 100644 --- a/roles/aws/aws_admin_tools/tasks/main.yml +++ b/roles/aws/aws_admin_tools/tasks/main.yml @@ -1,4 +1,4 @@ -- name: Create API gateway. +- name: Get account ID for ARN. ansible.builtin.command: >- aws sts get-caller-identity --query Account diff --git a/roles/aws/aws_backup_validation/defaults/main.yml b/roles/aws/aws_backup_validation/defaults/main.yml index 2e2632376..26f4613d0 100644 --- a/roles/aws/aws_backup_validation/defaults/main.yml +++ b/roles/aws/aws_backup_validation/defaults/main.yml @@ -1,6 +1,6 @@ --- aws_backup_validation: - s3_bucket: "codeenigma-{{ _aws_profile }}-general-storage-{{ _aws_region }}" + s3_bucket: "ce-{{ _aws_profile }}-lambda-functions" name: "RestoreValidation" description: "Restore validation is running every Sunday at 00:00AM, and validation reporting is triggered on Monday 00:00AM" timeout: 60 diff --git a/roles/aws/aws_backup_validation/tasks/main.yml b/roles/aws/aws_backup_validation/tasks/main.yml index 5ebb5d90b..37366f4b2 100644 --- a/roles/aws/aws_backup_validation/tasks/main.yml +++ b/roles/aws/aws_backup_validation/tasks/main.yml @@ -1,162 +1,102 @@ --- -- name: Create a role and attach policies - amazon.aws.iam_role: - name: LambdaBackupRestoreRole - assume_role_policy_document: "{{ lookup('file', 'trusted_entitites.j2') }}" - managed_policies: - - arn:aws:iam::aws:policy/AmazonEC2FullAccess - - arn:aws:iam::aws:policy/AWSBackupFullAccess - - arn:aws:iam::aws:policy/AmazonRDSFullAccess - - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess - - arn:aws:iam::aws:policy/AmazonSESFullAccess - - arn:aws:iam::aws:policy/AmazonSSMFullAccess - register: _created_iam_lambda_role - -- name: Create an IAM Managed Policy for passing roles - amazon.aws.iam_managed_policy: - policy_name: "PassRole" - policy: - Version: "2012-10-17" - Statement: - - Effect: "Allow" - Action: "iam:PassRole" - Resource: "*" - state: present - register: _pass_role - -- name: Update AWSBackupDefaultServiceRole - amazon.aws.iam_role: - name: AWSBackupDefaultServiceRole - assume_role_policy_document: "{{ lookup('file', 'pass_role_backup.j2') }}" - managed_policies: - - arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup - - arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores - - "{{ _pass_role.policy.arn }}" +- name: Create a role and attach policies for Lambda backup validation. + ansible.builtin.include_role: + name: aws/aws_iam_role + vars: + aws_iam_role: + name: LambdaBackupRestoreRole + aws_profile: "{{ _aws_profile }}" + managed_policies: + - arn:aws:iam::aws:policy/AmazonEC2FullAccess + - arn:aws:iam::aws:policy/AWSBackupFullAccess + - arn:aws:iam::aws:policy/AmazonRDSFullAccess + - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess + - arn:aws:iam::aws:policy/AmazonSSMFullAccess + policy_document: "{{ lookup('file', 'trusted_entitites.j2') }}" + +- name: Create backup validation Lambda functions. + ansible.builtin.include_role: + name: aws/aws_lambda + vars: + aws_lambda: + name: "{{ aws_backup_validation.name }}_{{ item }}" + description: "{{ aws_backup_validation.description }}" + timeout: "{{ aws_backup_validation.timeout }}" + role: "{{ aws_iam_role._result['LambdaBackupRestoreRole'] }}" + runtime: "{{ aws_backup_validation.runtime }}" + function_file: "{{ lookup('template', item + '_validation.py.j2') }}" + s3_bucket: "ce-{{ _aws_profile }}-lambda-functions" + tags: + Name: "{{ item }}_backup_validation" + loop: "{{ aws_backup_validation.resources }}" -- name: Sleep for 10 seconds for IAM before Lambda creation - ansible.builtin.wait_for: - timeout: 10 +#- name: Remove variables containing "-". +# ansible.builtin.set_fact: +# aws_lambda: "{{ aws_lambda | ansible.utils.remove_keys(target=['response_metadata', 'function_file']) }}" + +- name: Create an IAM Managed Policy for passing roles and setup IAM role. + ansible.builtin.include_role: + name: aws/aws_iam_role + vars: + aws_iam_role: + name: AWSBackupDefaultServiceRole + aws_profile: "{{ _aws_profile }}" + inline_policies: + name: "PassRole" + resource: "*" + action: "iam:PassRole" + policy_document: "{{ lookup('file', 'pass_role_backup.j2') }}" + managed_policies: + - arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup + - arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores # TODO: Not all clients have verified identity #- name: Get verified domain. # ansible.builtin.include_tasks: get_valid_email.yml -- name: Clean and set python functions - block: - - name: Create S3 bucket for lambda functions - amazon.aws.s3_bucket: - name: "{{ aws_backup_validation.s3_bucket }}" - region: "{{ _aws_region }}" - state: present - - - name: Check and clean any previous backup validation files - ansible.builtin.file: - path: "{{ _ce_provision_build_dir }}/{{ item }}_validation.py" - state: absent - loop: "{{ aws_backup_validation.resources }}" - - - name: Check and clean any previous validation report files - ansible.builtin.file: - path: "{{ _ce_provision_build_dir }}/validation_report.py" - state: absent - - - name: Write Lambda functions - ansible.builtin.template: - src: "{{ item }}_validation.py.j2" - dest: "{{ _ce_provision_build_dir }}/{{ item }}_validation.py" - loop: "{{ aws_backup_validation.resources }}" - - - name: Get info about newly created restore testing plan. - ansible.builtin.command: > - aws backup list-restore-testing-plans --region {{ _aws_region }} - register: _testing_plans - - - name: Print return information from the previous task - ansible.builtin.debug: - var: _testing_plans - - - name: Write validation report functions - ansible.builtin.template: - src: "validation_report.j2" - dest: "{{ _ce_provision_build_dir }}/validation_report.py" - - - name: Create a zip archive of Lambda functions - community.general.archive: - path: "{{ _ce_provision_build_dir }}/{{ item }}_validation.py" - dest: "{{ _ce_provision_build_dir }}/{{ item }}_validation.zip" - format: zip - loop: "{{ aws_backup_validation.resources }}" - - - name: Create a zip archive of validation report - community.general.archive: - path: "{{ _ce_provision_build_dir }}/validation_report.py" - dest: "{{ _ce_provision_build_dir }}/validation_report.zip" - format: zip - - - name: Place backup validation functions in S3 bucket - amazon.aws.s3_object: - bucket: "{{ aws_backup_validation.s3_bucket }}" - object: "lambda-functions/{{ item }}_validation.zip" - src: "{{ _ce_provision_build_dir }}/{{ item }}_validation.zip" - mode: put - loop: "{{ aws_backup_validation.resources }}" - - - name: Place report function in S3 bucket - amazon.aws.s3_object: - bucket: "{{ aws_backup_validation.s3_bucket }}" - object: "lambda-functions/validation_report.zip" - src: "{{ _ce_provision_build_dir }}/validation_report.zip" - mode: put - loop: "{{ aws_backup_validation.resources }}" - -- name: Create Lambda functions - amazon.aws.lambda: - name: "{{ aws_backup_validation.name }}_{{ item }}" - description: "{{ aws_backup_validation.description }}" - region: "{{ _aws_region }}" - timeout: "{{ aws_backup_validation.timeout }}" - s3_bucket: "{{ aws_backup_validation.s3_bucket }}" - s3_key: "lambda-functions/{{ item }}_validation.zip" - state: present - runtime: "{{ aws_backup_validation.runtime }}" - role: "{{ _created_iam_lambda_role.iam_role.arn }}" - handler: "{{ item }}_validation.{{ aws_backup_validation.handler }}" - tags: - Name: "{{ item }}_backup_validation" - register: _lambda_functions - loop: "{{ aws_backup_validation.resources }}" - -- name: Create validation report functions - amazon.aws.lambda: - name: "validation_report" - description: "{{ aws_backup_validation.description }}" - region: "{{ _aws_region }}" - timeout: 30 - s3_bucket: "{{ aws_backup_validation.s3_bucket }}" - s3_key: "lambda-functions/validation_report.zip" - state: present - runtime: "{{ aws_backup_validation.runtime }}" - role: "{{ _created_iam_lambda_role.iam_role.arn }}" - handler: "validation_report.{{ aws_backup_validation.handler }}" - register: _validation_report - -- name: Remove non UTF-8 item +- name: Get info about newly created restore testing plan. + ansible.builtin.command: > + aws backup list-restore-testing-plans --region {{ _aws_region }} + register: _testing_plans + +- name: Create validation report function. + ansible.builtin.include_role: + name: aws/aws_lambda + vars: + aws_lambda: + name: "validation_report" + description: "{{ aws_backup_validation.description }}" + timeout: "30" + role: "{{ aws_iam_role._result['LambdaBackupRestoreRole'] }}" + runtime: "{{ aws_backup_validation.runtime }}" + function_file: "{{ lookup('template', 'validation_report.py.j2') }}" + s3_bucket: "ce-{{ _aws_profile }}-lambda-functions" + tags: + Name: "validation_report" + +- name: Get account ID for ARN. + ansible.builtin.command: >- + aws sts get-caller-identity + --query Account + --output text + register: _acc_id + +- name: Setting previous command output into variable. ansible.builtin.set_fact: - _lambda_functions: "{{ _lambda_functions | ansible.utils.remove_keys(target=['ZipFile', 'location', 'item.invocation']) }}" - _validation_report: "{{ _validation_report | ansible.utils.remove_keys(target=['ZipFile', 'location', 'item.invocation']) }}" + _acc_id: "{{ _acc_id.stdout | from_json }}" -- name: Create EventBridge for validations +- name: Create EventBridge for validation functions. amazon.aws.cloudwatchevent_rule: - name: "{{ item.configuration.function_name }}" - description: "{{ item.configuration.description }}" + name: "RestoreValidation_{{ item }}" + description: "{{ aws_backup_validation.description }}" state: present region: "{{ _aws_region }}" - event_pattern: '{ "source": ["aws.backup"], "detail-type": ["Restore Job State Change"], "detail": { "resourceType": ["{{ item.item }}"], "status": ["COMPLETED"] } }' + event_pattern: '{ "source": ["aws.backup"], "detail-type": ["Restore Job State Change"], "detail": { "resourceType": ["{{ item }}"], "status": ["COMPLETED"] } }' targets: - - id: "{{ item.configuration.function_name }}" - arn: "{{ (item.configuration.function_arn.split(':') | map('trim'))[:-1] | join(':') }}" # Remove the version number from ARN + - id: "RestoreValidation_{{ item }}" + arn: "arn:aws:lambda:{{ _aws_region }}:{{ _acc_id }}:function:RestoreValidation_{{ item }}" + loop: "{{ aws_backup_validation.resources }}" register: _event_bridges - loop: "{{ _lambda_functions.results }}" - name: Create schedule for validation reports amazon.aws.cloudwatchevent_rule: @@ -166,7 +106,7 @@ region: "{{ _aws_region }}" targets: - id: validation_report - arn: "{{ (_validation_report.configuration.function_arn.split(':') | map('trim'))[:-1] | join(':') }}" # Remove the version number from ARN + arn: "{{ (aws_lambda._result['validation_report'].configuration.function_arn.split(':') | map('trim'))[:-1] | join(':') }}" # Remove the version number from ARN register: _validation_event - name: Generate unique string @@ -176,8 +116,8 @@ - name: Update Lambda policy amazon.aws.lambda_policy: state: present - function_name: "{{ item.item.configuration.function_name }}" - statement_id: "{{ item.item.configuration.function_name }}_{{ _rand_str }}" + function_name: "{{ item.rule.name }}" + statement_id: "{{ item.rule.name }}_{{ _rand_str }}" action: lambda:InvokeFunction principal: events.amazonaws.com source_arn: "{{ item.rule.arn }}" @@ -188,7 +128,7 @@ amazon.aws.lambda_policy: state: present function_name: "validation_report" - statement_id: "{{ _validation_report.configuration.function_name }}_{{ _rand_str }}" + statement_id: "validation_report_{{ _rand_str }}" action: lambda:InvokeFunction principal: events.amazonaws.com source_arn: "{{ _validation_event.rule.arn }}" diff --git a/roles/aws/aws_backup_validation/templates/validation_report.j2 b/roles/aws/aws_backup_validation/templates/validation_report.py.j2 similarity index 98% rename from roles/aws/aws_backup_validation/templates/validation_report.j2 rename to roles/aws/aws_backup_validation/templates/validation_report.py.j2 index 9c29baea4..bc80f32a7 100644 --- a/roles/aws/aws_backup_validation/templates/validation_report.j2 +++ b/roles/aws/aws_backup_validation/templates/validation_report.py.j2 @@ -98,9 +98,9 @@ failed_job = backup_cli.list_restore_jobs( {% endfor %} if len(failed_jobs) > 0: - mail_title = "Failed!" + mail_title = "🔴 Failed!" else: - mail_title = "Success!" + mail_title = "🟢 Success!" print("Successful restore jobs:") print(completed_jobs) diff --git a/roles/aws/aws_iam_role/defaults/main.yml b/roles/aws/aws_iam_role/defaults/main.yml index 585728d80..c4bc28ad6 100644 --- a/roles/aws/aws_iam_role/defaults/main.yml +++ b/roles/aws/aws_iam_role/defaults/main.yml @@ -3,6 +3,10 @@ aws_iam_role: aws_profile: "{{ _aws_profile }}" # Pass either names or ARNs for the role. managed_policies: [] + inline_policies: + name: "example_inline_polcy" # Name of inline policy + resource: "*" + action: [] # Which document policy to apply. # Current options are 'ec2', 'ecs' or 'backup' policy_document: ec2 diff --git a/roles/aws/aws_iam_role/tasks/main.yml b/roles/aws/aws_iam_role/tasks/main.yml index daf1ad759..27afb6e02 100644 --- a/roles/aws/aws_iam_role/tasks/main.yml +++ b/roles/aws/aws_iam_role/tasks/main.yml @@ -1,9 +1,42 @@ +- name: Create an IAM Managed Policy if defined. + amazon.aws.iam_managed_policy: + policy_name: "inline_{{ aws_iam_role.name }}_policy" + policy: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Action: "{{ aws_iam_role.inline_policies.action }}" + Resource: "{{ aws_iam_role.inline_policies.resource }}" + state: present + register: _inline_iam_policy + when: inline_policies.action is defined and inline_policies.action > 0 + +- name: Join managed and inline policy. + ansible.builtin.set_fact: + _combined_policies: "{{ aws_iam_role.managed_policies + [_inline_iam_policy.arn] }}" + when: inline_policies.action is defined and inline_policies.action > 0 + +- name: Create combined var if inline policy is not defined or empty. + ansible.builtin.set_fact: + _combined_policies: "{{ aws_iam_role.managed_policies }}" + when: inline_policies.action is not defined or inline_policies.action == 0 + +- name: Create assume role policy document if predefined string is passed. + ansible.builtin.set_fact: + _assume_role_policy: "{{ lookup('file', aws_iam_role.policy_document + '_document_policy.json') }}" + when: aws_iam_role.policy_document | type_debug == 'string' + +- name: Create assume role policy document if template is provided. + ansible.builtin.set_fact: + _assume_role_policy: "{{ aws_iam_role.policy_document }}" + when: aws_iam_role.policy_document | type_debug != 'string' + - name: Create an IAM role. amazon.aws.iam_role: profile: "{{ aws_iam_role.aws_profile }}" name: "{{ aws_iam_role.name }}" - assume_role_policy_document: "{{ lookup('file', aws_iam_role.policy_document + '_document_policy.json') }}" - managed_policies: "{{ aws_iam_role.managed_policies }}" + assume_role_policy_document: "{{ _assume_role_policy }}" + managed_policies: "{{ _combined_policies }}" purge_policies: "{{ aws_iam_role.purge_policies }}" tags: "{{ aws_iam_role.tags }}" create_instance_profile: "{% if aws_iam_role.policy_document == 'ec2' %}true{% else %}false{% endif %}" @@ -12,4 +45,4 @@ - name: Register aws_iam_role results. ansible.builtin.set_fact: - aws_iam_role: "{{ aws_iam_role | combine({'_result': {aws_iam_role.name: _aws_iam_role_result}}) }}" + aws_iam_role: "{{ aws_iam_role | combine({'_result': {aws_iam_role.name: _aws_iam_role_result}}, recursive=True) }}" diff --git a/roles/aws/aws_lambda/defaults/main.yml b/roles/aws/aws_lambda/defaults/main.yml new file mode 100644 index 000000000..6bcf10abf --- /dev/null +++ b/roles/aws/aws_lambda/defaults/main.yml @@ -0,0 +1,10 @@ +aws_lambda: + name: "lambda_function_name" + description: "Description for AWS Lambda function" + timeout: "20" # Maximum number of seconds before function times out + handler: "lambda_handler" # Name of main function + s3_bucket: "ce-{{ _aws_profile }}-lambda-functions" + function_file: "" # template to pass in S3 bucket + runtime: "python3.12" + role: "" + tags: [] diff --git a/roles/aws/aws_lambda/tasks/main.yml b/roles/aws/aws_lambda/tasks/main.yml new file mode 100644 index 000000000..27b692c50 --- /dev/null +++ b/roles/aws/aws_lambda/tasks/main.yml @@ -0,0 +1,52 @@ +- name: Create S3 bucket for Lambda functions. + ansible.builtin.include_role: + name: aws/aws_s3_bucket + vars: + aws_s3_bucket: + profile: "{{ _aws_profile }}" + region: "{{ _aws_region }}" + name: "{{ aws_lambda.s3_bucket }}" + tags: [] + state: "present" + +- name: Check and clean previous Lambda function. + ansible.builtin.file: + path: "{{ _ce_provision_build_dir }}/{{ aws_lambda.name }}.py" + state: absent + +- name: Write Lambda function. + ansible.builtin.copy: + content: "{{ aws_lambda.function_file }}" + dest: "{{ _ce_provision_build_dir }}/{{ aws_lambda.name }}.py" + +- name: Create a zip archive of Lambda function. + community.general.archive: + path: "{{ _ce_provision_build_dir }}/{{ aws_lambda.name }}.py" + dest: "{{ _ce_provision_build_dir }}/{{ aws_lambda.name }}.zip" + format: zip + +- name: Place Lambda function in S3 bucket. + amazon.aws.s3_object: + bucket: "{{ aws_lambda.s3_bucket }}" + object: "{{ aws_lambda.name }}.zip" + src: "{{ _ce_provision_build_dir }}/{{ aws_lambda.name }}.zip" + mode: put + +- name: Create Lambda function. + amazon.aws.lambda: + name: "{{ aws_lambda.name }}" + description: "{{ aws_lambda.description }}" + region: "{{ _aws_region }}" + timeout: "{{ aws_lambda.timeout }}" + s3_bucket: "{{ aws_lambda.s3_bucket }}" + s3_key: "{{ aws_lambda.name }}.zip" + state: present + runtime: "{{ aws_lambda.runtime }}" + role: "{{ aws_lambda.role.iam_role.arn }}" + handler: "{{ aws_lambda.name }}.{{ aws_lambda.handler }}" + tags: "{{ aws_lambda.tags }}" + register: _aws_lambda_result + +- name: Register aws_lambda results. + ansible.builtin.set_fact: + aws_lambda: "{{ aws_lambda | combine({'_result': {aws_lambda.name: _aws_lambda_result}}, recursive=True) }}" diff --git a/roles/aws/aws_s3_bucket/tasks/main.yml b/roles/aws/aws_s3_bucket/tasks/main.yml index 8abcffa76..915b9c09e 100644 --- a/roles/aws/aws_s3_bucket/tasks/main.yml +++ b/roles/aws/aws_s3_bucket/tasks/main.yml @@ -1,15 +1,15 @@ - name: Create an S3 bucket. amazon.aws.s3_bucket: - profile: "{{ aws_s3_bucket.aws_profile }}" + profile: "{{ _aws_profile }}" region: "{{ aws_s3_bucket.region }}" name: "{{ aws_s3_bucket.name }}" tags: "{{ aws_s3_bucket.tags | combine({'Name': aws_s3_bucket.name}) }}" state: present - register: _aws_s3_bucket_bucket + register: _aws_s3_bucket - name: Create a matching policy. amazon.aws.iam_managed_policy: - profile: "{{ aws_s3_bucket.aws_profile }}" + profile: "{{ _aws_profile }}" region: "{{ aws_s3_bucket.region }}" policy_name: "{{ aws_s3_bucket.name }}" policy: @@ -20,8 +20,8 @@ Resource: "arn:aws:s3:::{{ aws_s3_bucket.name }}" make_default: true state: present - register: _aws_s3_bucket_bucket_policy + register: _aws_s3_bucket_policy - name: Register aws_s3_bucket results. ansible.builtin.set_fact: - aws_s3_bucket: "{{ aws_s3_bucket | combine({'_result': {aws_s3_bucket.name: {'bucket': _aws_s3_bucket_bucket, 'policy': _aws_s3_bucket_bucket_policy}}}) }}" + aws_s3_bucket: "{{ aws_s3_bucket | combine({'_result': {aws_s3_bucket.name | replace('-','_'): {'bucket': _aws_s3_bucket, 'policy': _aws_s3_bucket_policy}}}, recursive=True) }}"