From da4eaca6880b1129e82562096dd6b51810978018 Mon Sep 17 00:00:00 2001 From: rut31337 Date: Wed, 22 Jul 2026 12:03:16 -0500 Subject: [PATCH 1/2] feat: add aws_open_environment workload role Ported from infra-aws-open-environment in legacy agnosticd. Creates an IAM admin user via CloudFormation with configurable policy, reports AWS credentials (access key, console URL) to the user, and cleans up the CFN stack and Route53 zones on destroy. For use with the sandbox config in agnosticd-v2. Ref: GPTEINFRA-17461 --- roles/aws_open_environment/defaults/main.yml | 18 +++++ roles/aws_open_environment/meta/main.yml | 15 +++++ .../tasks/delete_route53_zone.yml | 55 +++++++++++++++ roles/aws_open_environment/tasks/main.yml | 8 +++ .../tasks/remove_workload.yml | 50 ++++++++++++++ roles/aws_open_environment/tasks/workload.yml | 67 +++++++++++++++++++ .../templates/open-environment.yaml.j2 | 36 ++++++++++ 7 files changed, 249 insertions(+) create mode 100644 roles/aws_open_environment/defaults/main.yml create mode 100644 roles/aws_open_environment/meta/main.yml create mode 100644 roles/aws_open_environment/tasks/delete_route53_zone.yml create mode 100644 roles/aws_open_environment/tasks/main.yml create mode 100644 roles/aws_open_environment/tasks/remove_workload.yml create mode 100644 roles/aws_open_environment/tasks/workload.yml create mode 100644 roles/aws_open_environment/templates/open-environment.yaml.j2 diff --git a/roles/aws_open_environment/defaults/main.yml b/roles/aws_open_environment/defaults/main.yml new file mode 100644 index 00000000..6ebcf693 --- /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 00000000..cc1df9e5 --- /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 + - open-environment +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 00000000..e6d40d27 --- /dev/null +++ b/roles/aws_open_environment/tasks/delete_route53_zone.yml @@ -0,0 +1,55 @@ +--- +- 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') }}" + + - 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') }}" diff --git a/roles/aws_open_environment/tasks/main.yml b/roles/aws_open_environment/tasks/main.yml new file mode 100644 index 00000000..4999343f --- /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 00000000..413bdd6d --- /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 00000000..05b5eee5 --- /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 00000000..ad563b4a --- /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 From 13f20a0d044751421a72fbc0e5d73bcd17da7e4f Mon Sep 17 00:00:00 2001 From: rut31337 Date: Wed, 22 Jul 2026 12:17:50 -0500 Subject: [PATCH 2/2] fix: resolve ansible-lint violations - meta/main.yml: replace hyphenated tag with lowercase-only - delete_route53_zone.yml: add changed_when to command tasks --- roles/aws_open_environment/meta/main.yml | 2 +- roles/aws_open_environment/tasks/delete_route53_zone.yml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/roles/aws_open_environment/meta/main.yml b/roles/aws_open_environment/meta/main.yml index cc1df9e5..626df9bf 100644 --- a/roles/aws_open_environment/meta/main.yml +++ b/roles/aws_open_environment/meta/main.yml @@ -11,5 +11,5 @@ galaxy_info: galaxy_tags: - aws - sandbox - - open-environment + - openenvironment dependencies: [] diff --git a/roles/aws_open_environment/tasks/delete_route53_zone.yml b/roles/aws_open_environment/tasks/delete_route53_zone.yml index e6d40d27..6a9272ea 100644 --- a/roles/aws_open_environment/tasks/delete_route53_zone.yml +++ b/roles/aws_open_environment/tasks/delete_route53_zone.yml @@ -45,6 +45,7 @@ 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: >- @@ -53,3 +54,4 @@ 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