diff --git a/roles/aws_open_environment/defaults/main.yml b/roles/aws_open_environment/defaults/main.yml new file mode 100644 index 0000000..6ebcf69 --- /dev/null +++ b/roles/aws_open_environment/defaults/main.yml @@ -0,0 +1,18 @@ +--- +# IAM policy for the admin user created via CloudFormation. +# Default grants full access to all AWS services. +sandbox_aws_admin_user_policy: + Statement: + - Effect: Allow + Action: "*" + Resource: "*" + +# Enable AWS web console access +sandbox_enable_ui: true + +# Control reporting of sandbox credentials +sandbox_user_info_data_enable: true +sandbox_user_info_messages_enable: true + +# Clean up Route53 hosted zones on destroy +aws_open_environment_cleanup_route53: true diff --git a/roles/aws_open_environment/meta/main.yml b/roles/aws_open_environment/meta/main.yml new file mode 100644 index 0000000..626df9b --- /dev/null +++ b/roles/aws_open_environment/meta/main.yml @@ -0,0 +1,15 @@ +--- +galaxy_info: + role_name: aws_open_environment + author: Red Hat, Patrick Rutledge (prutledg@redhat.com) + description: | + Creates an IAM admin user with configurable policy in an AWS sandbox + account and reports credentials (access key, console URL) to the user. + license: MIT + min_ansible_version: "2.14" + platforms: [] + galaxy_tags: + - aws + - sandbox + - openenvironment +dependencies: [] diff --git a/roles/aws_open_environment/tasks/delete_route53_zone.yml b/roles/aws_open_environment/tasks/delete_route53_zone.yml new file mode 100644 index 0000000..6a9272e --- /dev/null +++ b/roles/aws_open_environment/tasks/delete_route53_zone.yml @@ -0,0 +1,57 @@ +--- +- name: Get all records in zone {{ _hosted_zone_id }} + ansible.builtin.command: >- + aws route53 list-resource-record-sets --hosted-zone-id {{ _hosted_zone_id }} + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key_id }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_access_key }}" + AWS_DEFAULT_REGION: "{{ aws_region | default('us-east-1') }}" + register: r_zone_records + changed_when: false + +- name: Set records to delete + ansible.builtin.set_fact: + _records_to_delete: >- + {{ r_zone_records.stdout | from_json + | json_query('ResourceRecordSets') + | rejectattr('Name', 'equalto', _aws_public_zone) + | list }} + +- name: Delete records and zone + when: _records_to_delete | length > 0 + block: + - name: Create change batch file + ansible.builtin.copy: + dest: "{{ output_dir | default('/tmp/output_dir') }}/{{ guid }}_{{ _hosted_zone_id }}_delete_records.json" + content: | + { + "Comment": "Delete all records for {{ _hosted_zone_id }}", + "Changes": [ + {% for record in _records_to_delete %} + { "Action": "DELETE", + "ResourceRecordSet": {{ record | to_json }} + }{{ "," if not loop.last else "" }} + {% endfor %} + ] + } + mode: "0644" + + - name: Delete all records from the zone + ansible.builtin.command: >- + aws route53 change-resource-record-sets + --hosted-zone-id={{ _hosted_zone_id }} + --change-batch=file://{{ output_dir | default('/tmp/output_dir') }}/{{ guid }}_{{ _hosted_zone_id }}_delete_records.json + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key_id }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_access_key }}" + AWS_DEFAULT_REGION: "{{ aws_region | default('us-east-1') }}" + changed_when: true + + - name: Remove hosted zone + ansible.builtin.command: >- + aws route53 delete-hosted-zone --id {{ _hosted_zone_id }} + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key_id }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_access_key }}" + AWS_DEFAULT_REGION: "{{ aws_region | default('us-east-1') }}" + changed_when: true diff --git a/roles/aws_open_environment/tasks/main.yml b/roles/aws_open_environment/tasks/main.yml new file mode 100644 index 0000000..4999343 --- /dev/null +++ b/roles/aws_open_environment/tasks/main.yml @@ -0,0 +1,8 @@ +--- +- name: Running workload provision tasks + when: ACTION == "provision" + ansible.builtin.include_tasks: workload.yml + +- name: Running workload removal tasks + when: ACTION == "destroy" + ansible.builtin.include_tasks: remove_workload.yml diff --git a/roles/aws_open_environment/tasks/remove_workload.yml b/roles/aws_open_environment/tasks/remove_workload.yml new file mode 100644 index 0000000..413bdd6 --- /dev/null +++ b/roles/aws_open_environment/tasks/remove_workload.yml @@ -0,0 +1,50 @@ +--- +- name: Destroy open environment CloudFormation stack + amazon.aws.cloudformation: + aws_access_key: "{{ aws_access_key_id }}" + aws_secret_key: "{{ aws_secret_access_key }}" + stack_name: "{{ project_tag }}-open-environment" + state: absent + region: "{{ aws_region_loop | default(aws_region) | default(region) | default('us-east-1') }}" + register: r_cfn_destroy + ignore_errors: true + +- name: Warn if CloudFormation stack deletion failed + when: r_cfn_destroy is failed + ansible.builtin.debug: + msg: >- + WARNING: CloudFormation stack {{ project_tag }}-open-environment deletion failed. + AWS Nuke will clean up remaining resources when the sandbox is released. + Error: {{ r_cfn_destroy.msg | default('unknown') }} + +- name: Clean up Route53 hosted zone + when: aws_open_environment_cleanup_route53 | default(true) | bool + block: + - name: Set public zone name + ansible.builtin.set_fact: + _aws_public_zone: "{{ guid }}.{{ subdomain_base_suffix | default(base_domain) | regex_replace('^\\.', '') }}." + + - name: List hosted zones matching this environment + ansible.builtin.command: >- + aws route53 list-hosted-zones-by-name + --dns-name {{ _aws_public_zone }} + --max-items 4 + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key_id }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_access_key }}" + AWS_DEFAULT_REGION: "{{ aws_region | default('us-east-1') }}" + register: r_route53_zones + changed_when: false + ignore_errors: true + + - name: Delete matching hosted zones + when: + - r_route53_zones is success + - _zone.Name == _aws_public_zone + ansible.builtin.include_tasks: delete_route53_zone.yml + vars: + _hosted_zone_id: "{{ _zone.Id | regex_replace('/hostedzone/', '') }}" + loop: "{{ (r_route53_zones.stdout | from_json).HostedZones | default([]) }}" + loop_control: + loop_var: _zone + label: "{{ _zone.Name | default('unknown') }}" diff --git a/roles/aws_open_environment/tasks/workload.yml b/roles/aws_open_environment/tasks/workload.yml new file mode 100644 index 0000000..05b5eee --- /dev/null +++ b/roles/aws_open_environment/tasks/workload.yml @@ -0,0 +1,67 @@ +--- +- name: Generate console password + ansible.builtin.set_fact: + aws_open_environment_console_password: >- + {{ lookup('community.general.random_string', + length=12, min_lower=1, min_upper=1, special=false, min_numeric=1) }} + +- name: Deploy CloudFormation stack for open environment + amazon.aws.cloudformation: + aws_access_key: "{{ aws_access_key_id }}" + aws_secret_key: "{{ aws_secret_access_key }}" + stack_name: "{{ project_tag }}-open-environment" + state: present + region: "{{ aws_region_loop | default(aws_region) | default(region) | default('us-east-1') }}" + tags: "{{ cloud_tags_final | default(omit) }}" + template_body: "{{ lookup('template', 'open-environment.yaml.j2') }}" + register: r_cloudformation_open_env + +- name: Set AWS credential facts from stack outputs + ansible.builtin.set_fact: + aws_open_environment_access_key_id: >- + {{ r_cloudformation_open_env.stack_outputs.AdminUserAccessKey }} + aws_open_environment_secret_access_key: >- + {{ r_cloudformation_open_env.stack_outputs.AdminUserSecretAccessKey }} + aws_open_environment_console_user_name: >- + {{ r_cloudformation_open_env.stack_outputs.AdminUser }} + +- name: Report sandbox credentials as user info message + when: sandbox_user_info_messages_enable | default(true) | bool + agnosticd.core.agnosticd_user_info: + msg: | + + Your AWS credentials are: + + `AWS_ACCESS_KEY_ID: {{ aws_open_environment_access_key_id }}` + + `AWS_SECRET_ACCESS_KEY: {{ aws_open_environment_secret_access_key }}` + + *Please DO NOT expose these AWS credentials in GIT repos or anywhere else that could be public!* + + Top level route53 domain: {{ subdomain_base_suffix | default(base_domain) }} + + {% if sandbox_enable_ui | default(true) | bool %} + Web Console Access: https://{{ sandbox_account_id }}.signin.aws.amazon.com/console + + Web Console Credentials: {{ aws_open_environment_console_user_name }} / {{ aws_open_environment_console_password }} + {% endif %} + +- name: Report sandbox credentials as user data + when: sandbox_user_info_data_enable | default(true) | bool + agnosticd.core.agnosticd_user_info: + data: + aws_access_key_id: "{{ aws_open_environment_access_key_id }}" + aws_secret_access_key: "{{ aws_open_environment_secret_access_key }}" + aws_default_region: "{{ aws_region | default('us-east-1') }}" + aws_route53_domain: "{{ subdomain_base_suffix | default(base_domain) }}" + aws_sandbox_account_id: "{{ sandbox_account_id }}" + +- name: Report web console access as user data + when: + - sandbox_user_info_data_enable | default(true) | bool + - sandbox_enable_ui | default(true) | bool + agnosticd.core.agnosticd_user_info: + data: + aws_web_console_url: "https://{{ sandbox_account_id }}.signin.aws.amazon.com/console" + aws_web_console_user_name: "{{ aws_open_environment_console_user_name }}" + aws_web_console_password: "{{ aws_open_environment_console_password }}" diff --git a/roles/aws_open_environment/templates/open-environment.yaml.j2 b/roles/aws_open_environment/templates/open-environment.yaml.j2 new file mode 100644 index 0000000..ad563b4 --- /dev/null +++ b/roles/aws_open_environment/templates/open-environment.yaml.j2 @@ -0,0 +1,36 @@ +--- +AWSTemplateFormatVersion: "2010-09-09" +Resources: + AdminUser: + Type: AWS::IAM::User + Properties: + UserName: "{{ email | default(owner) | default('open-environment') }}-{{ guid }}-admin" + LoginProfile: + Password: {{ aws_open_environment_console_password | to_json }} + PasswordResetRequired: False + Policies: + - PolicyName: AdminAccessPolicy + PolicyDocument: + {{ sandbox_aws_admin_user_policy | to_nice_yaml | indent(12) }} + + AdminUserAccessKey: + DependsOn: AdminUser + Type: AWS::IAM::AccessKey + Properties: + UserName: + Ref: AdminUser +Outputs: + AdminUser: + Value: + Ref: AdminUser + Description: IAM Admin User + AdminUserAccessKey: + Value: + Ref: AdminUserAccessKey + Description: IAM access key for Admin + AdminUserSecretAccessKey: + Value: + Fn::GetAtt: + - AdminUserAccessKey + - SecretAccessKey + Description: IAM User access key for admin