diff --git a/ansible/roles/ansible-script/README.md b/ansible/roles/ansible-script/README.md new file mode 100644 index 000000000..f6cc73cc2 --- /dev/null +++ b/ansible/roles/ansible-script/README.md @@ -0,0 +1 @@ +Copy a script onto target host which will run ansible locally diff --git a/ansible/roles/ansible-script/files/ansible.sh b/ansible/roles/ansible-script/files/ansible.sh new file mode 100755 index 000000000..2500f758e --- /dev/null +++ b/ansible/roles/ansible-script/files/ansible.sh @@ -0,0 +1,132 @@ +#!/bin/bash +# Don't set set -u as ansible activate script fails with it on RHEL6 +set -eo pipefail + +branch="main" +ansible_repo="modernisation-platform-configuration-management" +ansible_repo_basedir="ansible" + +run_ansible() { + export PATH=/usr/local/bin:$PATH + + echo "ansible_repo: ${ansible_repo}" + echo "ansible_repo_basedir: ${ansible_repo_basedir}" + echo "ansible_args: $@" + echo "branch: $branch" + + if [[ -z ${ansible_repo} ]]; then + echo "ansible_repo not defined, not installing any ansible" >&2 + exit 0 + fi + + if ! command -v aws > /dev/null; then + echo "aws cli must be installed, not installing any ansible" >&2 + exit 0 + fi + + if ! command -v git > /dev/null; then + echo "git must be installed, not installing any ansible" >&2 + exit 0 + fi + + echo "# Retrieving API Token" + token=$(curl -sS -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") + + echo "# Retrieving Instance ID" + instance_id=$(curl -sS -H "X-aws-ec2-metadata-token: $token" -v http://169.254.169.254/latest/meta-data/instance-id) + + echo "# Retrieving tags using aws cli" + IFS=$'\n' + tags=($(aws ec2 describe-tags --filters "Name=resource-id,Values=$instance_id" "Name=key,Values=Name,os-type,ami,server-type,environment-name" --output=text)) + unset IFS + + # clone ansible roles and playbook + ansible_dir=/root/ansible + cd $ansible_dir + if [[ ! -d $ansible_dir/${ansible_repo} ]]; then + echo "# Cloning ${ansible_repo} into $ansible_dir using branch=$branch" + git clone "https://github.com/ministryofjustice/${ansible_repo}.git" + cd $ansible_dir/${ansible_repo} + git checkout "$branch" + else + cd $ansible_dir/${ansible_repo} + git pull + fi + cd $ansible_dir + + # find the group_var yaml files + ansible_group_vars= + for ((i=0; i<${#tags[@]}; i++)); do + tag=(${tags[i]}) + group=$(echo "${tag[1]}_${tag[4]}" | tr [:upper:] [:lower:] | sed "s/-/_/g") + if [[ "${tag[1]}" == "Name" ]]; then + ansible_group_vars="$ansible_group_vars --extra-vars ec2_name=${tag[4]}" + elif [[ -e $ansible_dir/${ansible_repo}/${ansible_repo_basedir}/group_vars/$group.yml ]]; then + ansible_group_vars="$ansible_group_vars --extra-vars @group_vars/$group.yml" + elif [[ -e $ansible_dir/${ansible_repo}/${ansible_repo_basedir}/group_vars/$group/ansible.yml ]]; then + ansible_group_vars="$ansible_group_vars --extra-vars @group_vars/$group/ansible.yml" + else + echo "Could not find group_vars $group yml" + exit 1 + fi + if [[ "${tag[1]}" == "environment-name" ]]; then + aws_environment=$(echo ${tag[4]} | rev | cut -d- -f1 | rev) + application=$(echo ${tag[4]} | rev | cut -d- -f2- | rev) + ansible_group_vars="$ansible_group_vars --extra-vars aws_environment=$aws_environment --extra-vars application=$application" + fi + done + + # set python version + if [[ $(which python3.9 2> /dev/null) ]]; then + python=$(which python3.9) + elif [[ $(which python3.6 2> /dev/null) ]]; then + python=$(which python3.6) + else + echo "Python3.9/3.6 not found" + exit 1 + fi + echo "# Using python: $python" + + # activate virtual environment + update=0 + if [[ ! -d $ansible_dir/python-venv ]]; then + mkdir $ansible_dir/python-venv + update=1 + fi + cd $ansible_dir/python-venv + $python -m venv ansible + source ansible/bin/activate + if [[ $update == 1 ]]; then + $python -m pip install --upgrade pip + if [[ "$python" =~ 3.6 ]]; then + $python -m pip install wheel + $python -m pip install cryptography==2.3 + export LC_ALL=en_US.UTF-8 + $python -m pip install ansible-core==2.11.12 + else + $python -m pip install ansible==6.0.0 + fi + + # install requirements in virtual env + echo "# Installing ansible requirements" + cd $ansible_dir/${ansible_repo}/${ansible_repo_basedir} + $python -m pip install -r requirements.txt + ansible-galaxy role install -r requirements.yml + ansible-galaxy collection install -r requirements.yml + fi + + # run ansible (comma after localhost deliberate) + cd $ansible_dir/${ansible_repo}/${ansible_repo_basedir} + echo "# Execute ansible $@ $ansible_group_vars ..." + ansible-playbook $@ $ansible_group_vars \ + --connection=local \ + --inventory localhost, \ + --extra-vars "ansible_python_interpreter=$python" \ + --extra-vars "target=localhost" \ + --become + + echo "# Deactivate: ansible_dir/${ansible_repo}/${ansible_repo_basedir}" + deactivate +} + +run_ansible $@ diff --git a/ansible/roles/ansible-script/tasks/ansible-script.yml b/ansible/roles/ansible-script/tasks/ansible-script.yml new file mode 100644 index 000000000..7e41c6c23 --- /dev/null +++ b/ansible/roles/ansible-script/tasks/ansible-script.yml @@ -0,0 +1,11 @@ +--- +- name: Create test ansible directory + ansible.builtin.file: + path: "~/ansible" + state: directory + +- name: Copy test ansible script + ansible.builtin.copy: + src: ansible.sh + dest: "~/ansible.sh" + mode: "0755" diff --git a/ansible/roles/ansible-script/tasks/main.yml b/ansible/roles/ansible-script/tasks/main.yml new file mode 100644 index 000000000..51ccf933f --- /dev/null +++ b/ansible/roles/ansible-script/tasks/main.yml @@ -0,0 +1,5 @@ +--- +- import_tasks: ansible-script.yml + tags: + - ec2provision + - ec2patch diff --git a/ansible/roles/oracle-11g/README.md b/ansible/roles/oracle-11g/README.md index a54231990..df55f724e 100644 --- a/ansible/roles/oracle-11g/README.md +++ b/ansible/roles/oracle-11g/README.md @@ -11,3 +11,12 @@ and either use: - ec2provision tag if the EC2 name has changed. This will reconfigure oracle - ec2patch tag if the EC2 name hasn't change. This is less disruptive. + +### Pre-requisites + +An `asm-passwords` placeholder SSM Parameter is created in terraform prior to +running role. The parameter name should be +/ec2/{{ hostname }}/asm-passwords. +The initial value should contain the word "placeholder". Terraform should +ignore subsequent changes to the parameter value since this role will auto +generate a password and store it there. diff --git a/ansible/roles/oracle-11g/defaults/main.yml b/ansible/roles/oracle-11g/defaults/main.yml index 42850a600..e6183f44b 100644 --- a/ansible/roles/oracle-11g/defaults/main.yml +++ b/ansible/roles/oracle-11g/defaults/main.yml @@ -1,7 +1,6 @@ --- artefacts_s3_bucket_name: mod-platform-image-artefact-bucket20230203091453221500000001 artefacts_s3_bucket_path: hmpps/oracle-11g-software -ssm_parameters_prefix: database oracle_home: /u01/app/oracle/product/11.2.0.4 oracle_inventory: /u01/app/oraInventory @@ -26,3 +25,10 @@ database_home: "{{ oracle_home }}/db_1" database_env: ORACLE_HOME: "{{ database_home }}" PATH: "{{ database_home }}/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/oracle/.local/bin:/home/oracle/bin" + +db_ssm_passwords: + - key: "asm" + parameter: "/ec2/{{ ec2_name }}/asm-passwords" + users: + - ASMSNMP: + - SYS: diff --git a/ansible/roles/oracle-11g/tasks/get-facts.yml b/ansible/roles/oracle-11g/tasks/get-facts.yml index b737bd8fd..6100a0fb9 100644 --- a/ansible/roles/oracle-11g/tasks/get-facts.yml +++ b/ansible/roles/oracle-11g/tasks/get-facts.yml @@ -1,17 +1,14 @@ --- -- name: Set SSM parameters path fact from ec2 ssm-parameters-prefix and Name tag - set_fact: - ssm_parameters_path: '/{{ ssm_parameters_prefix }}/{{ ec2.tags["Name"] }}' - -- name: Set SSM parameters weblogic path facts - set_fact: - ssm_parameters_path_database_asmsys_password: "{{ ssm_parameters_path }}/ASMSYS" - ssm_parameters_path_database_asmsnmp_password: "{{ ssm_parameters_path }}/ASMSNMP" +- name: Get SSM parameters + import_role: + name: ssm-passwords + vars: + ssm_passwords: "{{ db_ssm_passwords }}" - name: Get SSM parameters set_fact: - database_asmsys_password: "{{ lookup('aws_ssm', ssm_parameters_path_database_asmsys_password, region=ansible_ec2_placement_region) }}" - database_asmsnmp_password: "{{ lookup('aws_ssm', ssm_parameters_path_database_asmsnmp_password, region=ansible_ec2_placement_region) }}" + database_asmsys_password: "{{ ssm_passwords_dict['asm'].passwords['SYS'] }}" + database_asmsnmp_password: "{{ ssm_passwords_dict['asm'].passwords['ASMSNMP'] }}" - block: - name: Get DB instances diff --git a/ansible/roles/oracle-19c/README.md b/ansible/roles/oracle-19c/README.md index 228ec4890..6d3a2e2f7 100644 --- a/ansible/roles/oracle-19c/README.md +++ b/ansible/roles/oracle-19c/README.md @@ -1,5 +1,14 @@ This role installs Oracle 19c. It assumes the installation disks `\u01` and `\u02` have already been setup (with `disks` role for example). +### Pre-requisites + +An `asm-passwords` placeholder SSM Parameter is created in terraform prior to +running role. The parameter name should be +/ec2/{{ hostname }}/asm-passwords. +The initial value should contain the word "placeholder". Terraform should +ignore subsequent changes to the parameter value since this role will auto +generate a password and store it there. + ### Ansible Tags Some tasks are optional and can be included in the play by adding the appropriate tag at the command line. Currently these are: diff --git a/ansible/roles/oracle-19c/defaults/main.yml b/ansible/roles/oracle-19c/defaults/main.yml index a6c422815..a99860c96 100644 --- a/ansible/roles/oracle-19c/defaults/main.yml +++ b/ansible/roles/oracle-19c/defaults/main.yml @@ -35,6 +35,13 @@ grid_install_script: grid_install.sh password_response_file: grid_pw.rsp +db_ssm_passwords: + - key: "asm" + parameter: "/ec2/{{ ec2_name }}/asm-passwords" + users: + - monitor: + - SYS: + asmpassword: "{{ lookup('ansible.builtin.password', '/dev/null chars=ascii_letters length=2') }}{{ lookup('ansible.builtin.password', '/dev/null chars=ascii_letters,digits length=12') }}" asmmonitorpassword: "{{ lookup('ansible.builtin.password', '/dev/null chars=ascii_letters length=2') }}{{ lookup('ansible.builtin.password', '/dev/null chars=ascii_letters,digits length=12') }}" diff --git a/ansible/roles/oracle-19c/tasks/get_facts.yml b/ansible/roles/oracle-19c/tasks/get_facts.yml new file mode 100644 index 000000000..0955c397f --- /dev/null +++ b/ansible/roles/oracle-19c/tasks/get_facts.yml @@ -0,0 +1,11 @@ +--- +- name: Get SSM parameters + import_role: + name: ssm-passwords + vars: + ssm_passwords: "{{ db_ssm_passwords }}" + +- name: Get SSM parameters + set_fact: + asmpassword: "{{ ssm_passwords_dict['asm'].passwords['SYS'] }}" + asmmonitorpassword: "{{ ssm_passwords_dict['asm'].passwords['monitor'] }}" diff --git a/ansible/roles/oracle-19c/tasks/main.yml b/ansible/roles/oracle-19c/tasks/main.yml index 4fbfa1356..9a21ecf7c 100644 --- a/ansible/roles/oracle-19c/tasks/main.yml +++ b/ansible/roles/oracle-19c/tasks/main.yml @@ -10,6 +10,12 @@ - oracle_19c_download - oracle_19c_download_software +- import_tasks: get_facts.yml + tags: + - ec2provision + - oracle_db_get_facts + - oracle_19c_install_grid + - import_tasks: pre_install_tasks.yml tags: - ec2provision diff --git a/ansible/roles/oracle-db-backup/defaults/main.yml b/ansible/roles/oracle-db-backup/defaults/main.yml index 797a73cd1..803a79403 100644 --- a/ansible/roles/oracle-db-backup/defaults/main.yml +++ b/ansible/roles/oracle-db-backup/defaults/main.yml @@ -1,7 +1,11 @@ --- # scheduled backups go directly to S3 -# rman_backup_script: "" # define elsewhere, e.g. in group vars, e.g. rman_backup.sh +rman_backup_script: "rman_backup.sh" # override this as necessary in group_vars rman_backup_monitoring_script: "rman_backup_monitoring.sh" +rman_backup_cron: + backup_level_0: [] + backup_level_1: [] + monitoring: [] catalog_parameter: "" recovery_catalog_defined_check: 0 diff --git a/ansible/roles/oracle-oem-agent-setup/defaults/main.yml b/ansible/roles/oracle-oem-agent-setup/defaults/main.yml index 2347a8786..8ad8e422e 100644 --- a/ansible/roles/oracle-oem-agent-setup/defaults/main.yml +++ b/ansible/roles/oracle-oem-agent-setup/defaults/main.yml @@ -15,15 +15,20 @@ agentpatcher_patch: p33355570_135000_Generic.zip agentpatcher_version: 13.9.5.5.0 agent_home: "{{ oem_agent_base }}/agent_13.5.0.0.0" oem_secretsmanager_passwords: - - key: "oem_passwords" + - key: "oem" account_name: "hmpps-oem-{{ aws_environment }}" assume_role_name: "EC2OracleEnterpriseManagementSecretsRole" secret: "/oracle/oem/passwords" users: - agentreg: - - key: "emrep_passwords" + - key: "emrep" account_name: "hmpps-oem-{{ aws_environment }}" assume_role_name: "EC2OracleEnterpriseManagementSecretsRole" secret: "/oracle/database/EMREP/passwords" users: - sysman: +oem_ssm_passwords: + - key: "asm" + parameter: "/ec2/{{ ec2_name }}/asm-passwords" + users: + - ASMSNMP: diff --git a/ansible/roles/oracle-oem-agent-setup/tasks/get_facts.yml b/ansible/roles/oracle-oem-agent-setup/tasks/get_facts.yml index 5af35a06c..332602793 100644 --- a/ansible/roles/oracle-oem-agent-setup/tasks/get_facts.yml +++ b/ansible/roles/oracle-oem-agent-setup/tasks/get_facts.yml @@ -1,23 +1,21 @@ --- +- name: Get SSM parameters + import_role: + name: ssm-passwords + vars: + ssm_passwords: "{{ oem_ssm_passwords }}" + - name: Get OEM secrets import_role: name: secretsmanager-passwords vars: secretsmanager_passwords: "{{ oem_secretsmanager_passwords }}" -- name: Set SSM parameters path fact from ec2 ssm-parameters-prefix and Name tag - set_fact: - db_ssm_parameters_path: '/database/{{ ec2.tags["Name"] }}' - -- name: Set SSM parameters database path facts - set_fact: - ssm_parameter_path_asmsnmp_password: "{{ db_ssm_parameters_path }}/ASMSNMP" - -- name: Get SSM parameters +- name: Set password facts set_fact: - oem_sysman_password: "{{ secretsmanager_passwords_dict['emrep_passwords'].passwords['sysman'] }}" - oem_agent_password: "{{ secretsmanager_passwords_dict['oem_passwords'].passwords['agentreg'] }}" - asmsnmp_password: "{{ lookup('aws_ssm', ssm_parameter_path_asmsnmp_password, region=ansible_ec2_placement_region) }}" + oem_sysman_password: "{{ secretsmanager_passwords_dict['emrep'].passwords['sysman'] }}" + oem_agent_password: "{{ secretsmanager_passwords_dict['oem'].passwords['agentreg'] }}" + asmsnmp_password: "{{ ssm_passwords_dict['asm'].passwords['ASMSNMP'] }}" - name: Get private ip address of the ec2 instance set_fact: @@ -29,6 +27,7 @@ when: - oem_sysman_password|length > 0 - oem_agent_password|length > 0 + - asmsnmp_password|length > 0 - name: Fail if missing parameters fail: diff --git a/ansible/roles/oracle-oms-setup/defaults/main.yml b/ansible/roles/oracle-oms-setup/defaults/main.yml index e86b1fe93..f6b451276 100644 --- a/ansible/roles/oracle-oms-setup/defaults/main.yml +++ b/ansible/roles/oracle-oms-setup/defaults/main.yml @@ -1,7 +1,6 @@ --- artefacts_s3_bucket_name: mod-platform-image-artefact-bucket20230203091453221500000001 artefacts_s3_bucket_path: hmpps/oracle-oem-135 -ssm_parameters_prefix: oem artefact_dir: /u02 app_dir: /u01/app/oracle/product oracle_inventory: /u01/app/oraInventory @@ -45,3 +44,30 @@ oms_env: db_env: ORACLE_HOME: "{{ database_home }}" PATH: "{{ database_home }}/bin:{{ oracle_path }}" + +emrepo_db_name: "{{ db_configs[emrepo] }}" +oms_ssm_passwords: + - key: "emrep" + parameter: "/oracle/database/{{ emrepo_db_name.emrepo_db_name }}/passwords" + users: + - sys: + - system: + - key: "oem" + parameter: "/oracle/oem/passwords" + users: + - weblogic_admin: + - nodemanager: + +oms_secretsmanager_passwords: + - key: "emrep" + assume_role_name: "" + account_name: "{{ application }}-{{ aws_environment }}" + secret: "/oracle/database/{{ emrepo_db_name.emrepo_db_name }}/passwords" + users: + - sysman: + - key: "oem" + assume_role_name: "" + account_name: "{{ application }}-{{ aws_environment }}" + secret: "/oracle/oem/passwords" + users: + - agentreg: diff --git a/ansible/roles/oracle-oms-setup/tasks/get_facts.yml b/ansible/roles/oracle-oms-setup/tasks/get_facts.yml index eafa5fb6d..1194d0ec5 100644 --- a/ansible/roles/oracle-oms-setup/tasks/get_facts.yml +++ b/ansible/roles/oracle-oms-setup/tasks/get_facts.yml @@ -1,38 +1,28 @@ --- -- name: Fail if missing parameters - fail: - msg: "Ensure {{ emrepo }} variable is defined in db_configs fact" - when: db_configs[emrepo] is not defined +- name: Get SSM parameters + import_role: + name: ssm-passwords + vars: + ssm_passwords: "{{ oms_ssm_passwords }}" -- name: Set database facts - set_fact: - emrepo_db_name: "{{ db_configs[ emrepo ] }}" +- name: Get OEM secrets + import_role: + name: secretsmanager-passwords + vars: + secretsmanager_passwords: "{{ oms_secretsmanager_passwords }}" - name: Debug oem repository database name debug: var: emrepo_db_name -- name: Set SSM parameters path fact from ec2 ssm-parameters-prefix and Name tag - set_fact: - ssm_parameters_path: '/{{ ssm_parameters_prefix }}/{{ ec2.tags["Name"] }}' - -- name: Set SSM parameters database path facts - set_fact: - ssm_parameters_path_db_sys_password: "{{ ssm_parameters_path }}/{{ emrepo_db_name.emrepo_db_name }}/syspassword" - ssm_parameters_path_db_system_password: "{{ ssm_parameters_path }}/{{ emrepo_db_name.emrepo_db_name }}/systempassword" - ssm_parameters_path_db_sysman_password: "{{ ssm_parameters_path }}/{{ emrepo_db_name.emrepo_db_name }}/sysmanpassword" - ssm_parameters_path_oem_weblogic_password: "{{ ssm_parameters_path }}/OEM/weblogicpassword" - ssm_parameters_path_oem_nodemanager_password: "{{ ssm_parameters_path }}/OEM/nodemanagerpassword" - ssm_parameters_path_oem_agent_registration_password: "{{ ssm_parameters_path }}/OEM/agentregpassword" - - name: Get SSM parameters set_fact: - db_sys_password: "{{ lookup('aws_ssm', ssm_parameters_path_db_sys_password, region=ansible_ec2_placement_region) }}" - db_system_password: "{{ lookup('aws_ssm', ssm_parameters_path_db_system_password, region=ansible_ec2_placement_region) }}" - db_sysman_password: "{{ lookup('aws_ssm', ssm_parameters_path_db_sysman_password, region=ansible_ec2_placement_region) }}" - weblogic_admin_password: "{{ lookup('aws_ssm', ssm_parameters_path_oem_weblogic_password, region=ansible_ec2_placement_region) }}" - nodemanager_password: "{{ lookup('aws_ssm', ssm_parameters_path_oem_nodemanager_password, region=ansible_ec2_placement_region) }}" - oem_agent_password: "{{ lookup('aws_ssm', ssm_parameters_path_oem_agent_registration_password, region=ansible_ec2_placement_region) }}" + db_sys_password: "{{ ssm_passwords_dict['emrep'].passwords['sys'] }}" + db_system_password: "{{ ssm_passwords_dict['emrep'].passwords['system'] }}" + db_sysman_password: "{{ secretsmanager_passwords_dict['emrep'].passwords['sysman'] }}" + weblogic_admin_password: "{{ ssm_passwords_dict['oem'].passwords['weblogic_admin'] }}" + nodemanager_password: "{{ ssm_passwords_dict['oem'].passwords['nodemanager'] }}" + oem_agent_password: "{{ secretsmanager_passwords_dict['oem'].passwords['agentreg'] }}" - name: Check parameters set_fact: diff --git a/ansible/roles/oracle-recovery-catalog/defaults/main.yml b/ansible/roles/oracle-recovery-catalog/defaults/main.yml index d66a4ada5..1e2d7d0fc 100644 --- a/ansible/roles/oracle-recovery-catalog/defaults/main.yml +++ b/ansible/roles/oracle-recovery-catalog/defaults/main.yml @@ -1,3 +1,10 @@ -ssm_parameters_prefix: database stage: /u02/stage temp_dir: /u02/stage/temp +rcvcat_db_name: "{{ db_configs[rcvcat] }}" +rc_ssm_passwords: + - key: "rc" + parameter: "/oracle/database/{{ rcvcat_db_name.rcvcat_db_name }}/passwords" + users: + - rcvcatowner: + - sys: + - system: diff --git a/ansible/roles/oracle-recovery-catalog/tasks/get_facts.yml b/ansible/roles/oracle-recovery-catalog/tasks/get_facts.yml index c10b881bf..841448293 100644 --- a/ansible/roles/oracle-recovery-catalog/tasks/get_facts.yml +++ b/ansible/roles/oracle-recovery-catalog/tasks/get_facts.yml @@ -1,42 +1,19 @@ --- -- name: Fail if missing parameters - fail: - msg: "Ensure {{ rcvcat }} variable is defined in db_configs fact" - when: db_configs[rcvcat] is not defined - -- name: Set database facts - set_fact: - rcvcat_db_name: "{{ db_configs[ rcvcat ] }}" - - name: Debug recovery catalog database name debug: var: rcvcat_db_name -- name: Set SSM parameters path fact from ec2 ssm-parameters-prefix and Name tag - set_fact: - ssm_parameters_path: '/{{ ssm_parameters_prefix }}/{{ ec2.tags["Name"] }}' - -- name: Set SSM parameters database path facts - set_fact: - ssm_parameters_path_db_sys_password: "{{ ssm_parameters_path }}/{{ rcvcat_db_name.rcvcat_db_name }}/syspassword" - ssm_parameters_path_db_system_password: "{{ ssm_parameters_path }}/{{ rcvcat_db_name.rcvcat_db_name }}/systempassword" - ssm_parameters_path_db_rcatowner_password: "{{ ssm_parameters_path }}/{{ rcvcat_db_name.rcvcat_db_name }}/rcvcatownerpassword" - -# Upload password manually, e.g. aws ssm put-parameter --name "/database/t1-nomis-db-1-b/TRDATT1/syspassword" --type "SecureString" --data-type "text" --value "Password123" --profile nomis-test -- debug: - msg: "Sys password must be uploaded to {{ ssm_parameters_path_db_sys_password }}" - -- debug: - msg: "System password must be uploaded to {{ ssm_parameters_path_db_system_password }}" - -- debug: - msg: "rcvcatowner password must be uploaded to {{ ssm_parameters_path_db_rcatowner_password }}" +- name: Get SSM parameters + import_role: + name: ssm-passwords + vars: + ssm_passwords: "{{ rc_ssm_passwords }}" - name: Get SSM parameters set_fact: - db_sys_password: "{{ lookup('aws_ssm', ssm_parameters_path_db_sys_password, region=ansible_ec2_placement_region) }}" - db_system_password: "{{ lookup('aws_ssm', ssm_parameters_path_db_system_password, region=ansible_ec2_placement_region) }}" - db_rcatowner_password: "{{ lookup('aws_ssm', ssm_parameters_path_db_rcatowner_password, region=ansible_ec2_placement_region) }}" + db_sys_password: "{{ ssm_passwords_dict['rc'].passwords['sys'] }}" + db_system_password: "{{ ssm_passwords_dict['rc'].passwords['system'] }}" + db_rcatowner_password: "{{ ssm_passwords_dict['rc'].passwords['rcvcatowner'] }}" - name: Check parameters set_fact: diff --git a/ansible/roles/secretsmanager-passwords/README.md b/ansible/roles/secretsmanager-passwords/README.md index c299d1b43..b8b847a4c 100644 --- a/ansible/roles/secretsmanager-passwords/README.md +++ b/ansible/roles/secretsmanager-passwords/README.md @@ -80,8 +80,8 @@ See OEM secrets for an example of how this is used in practice. 3. Force re-generation of password You can force a re-generation of password by including the secret key and username -in the `secretmanager_passwords_force_rotate` variable. For example: +in the `secretsmanager_passwords_force_rotate` variable. For example: ``` -ansible-playbook site.yml -e role=oracle-oem-agent-setup -e secretmanager_passwords_force_rotate=emrep_passwords:sysman --limit test-oem-a +ansible-playbook site.yml -e role=oracle-oem-agent-setup -e secretsmanager_passwords_force_rotate=emrep_passwords:sysman --limit test-oem-a ``` diff --git a/ansible/roles/secretsmanager-passwords/defaults/main.yml b/ansible/roles/secretsmanager-passwords/defaults/main.yml index bfe390895..7d3ed048c 100644 --- a/ansible/roles/secretsmanager-passwords/defaults/main.yml +++ b/ansible/roles/secretsmanager-passwords/defaults/main.yml @@ -1,5 +1,5 @@ --- -secretmanager_passwords: +secretsmanager_passwords: # - key: "unique_key_for_ansible_dictionary1_cross_account" e.g. my_password # assume_role_name: EC2OracleEnterpriseManagementSecretsRole # account_name: "my-aws-account-name1" e.g. hmpps-oem-test @@ -9,10 +9,10 @@ secretmanager_passwords: # - myuser2 # - key: "unique_key_for_ansible_dictionary2_same_account" # assume_role_name: "" -# account_name: "{{ application }}-{{ aws_environment }}"" +# account_name: "{{ application }}-{{ aws_environment }}" # secret: "my-secretsmanager-secret-name2" # users: # - myuser3 -secretmanager_passwords_force_rotate: "" +secretsmanager_passwords_force_rotate: "" # include the secret key and username to force rotation, e.g. "unique_key_for_ansible_dictionary1:myuser1" diff --git a/ansible/roles/secretsmanager-passwords/tasks/main.yml b/ansible/roles/secretsmanager-passwords/tasks/main.yml index 611290df4..81828408e 100644 --- a/ansible/roles/secretsmanager-passwords/tasks/main.yml +++ b/ansible/roles/secretsmanager-passwords/tasks/main.yml @@ -10,7 +10,7 @@ # Using the cli instead of native ansible as we need to assume a role # to access secrets in other accounts -- name: Get SecretManager Secrets +- name: Get SecretsManager Secrets ansible.builtin.shell: | PATH=$PATH:/usr/local/bin set -e @@ -74,7 +74,7 @@ if item[1].values()|first != None else secretsmanager_passwords_dict[item[0].key].passwords[item[1].keys()|first] if item[1].keys()|first in secretsmanager_passwords_dict[item[0].key].passwords - and [item[0].key, item[1].keys()|first]|join(':') not in secretmanager_passwords_force_rotate + and [item[0].key, item[1].keys()|first]|join(':') not in secretsmanager_passwords_force_rotate else lookup('ansible.builtin.password', '/dev/null chars=ascii_letters length=1') + lookup('ansible.builtin.password', '/dev/null chars=ascii_letters,digits length=15') } @@ -108,7 +108,7 @@ # community.aws.secretsmanager_secret doesn't work brilliantly. It requires # resource policy to be explicitly set and needs more permissions than # expected. Using cli instead -- name: Update SecretManager Secrets from named account +- name: Update SecretsManager Secrets from named account ansible.builtin.shell: | PATH=$PATH:/usr/local/bin set -e diff --git a/ansible/roles/ssm-passwords/README.md b/ansible/roles/ssm-passwords/README.md new file mode 100644 index 000000000..0b9df0bdf --- /dev/null +++ b/ansible/roles/ssm-passwords/README.md @@ -0,0 +1,60 @@ +# SSM Passwords + +Use this role to retrieve and/or automatically generate passwords stored in SSM Parameter SecretString + +The secret itself is a JSON with following structure: + +``` +{ + "username1": "password1", + "username2": "password2" +} +``` + +## Recommended Usage + +1. Set placeholder value in terraform + +Create a "placeholder" SSM Parameter SecretString in terraform. +Include the word "placeholder" in the value set by terraform. +Set lifecycle to ignore value changes. +Any of these modules will do this for you: +- `ec2-instance` +- `ec2-autoscaling-group` +- `baseline` + +2. Configure ansible to use this role + +Either use `import_role` in the relevant place, or include the +role in the `role_list` for the server. Define the `ssm_passwords` +variable accordingly + +``` +- name: Get OEM SSM passwords + import_role: + name: secretsmanager-passwords + vars: + ssm_passwords: + - key: "oem_passwords" + parameter: "/ec2/oracle/oem/passwords" + users: + - agentreg: + - key: "emrep_passwords" + parameter: "/ec2/oracle/database/EMREP/passwords" + users: + - sysman: +``` + +The role will automatically generate passwords and update the +secret value. + +See OEM secrets for an example of how this is used in practice. + +3. Force re-generation of password + +You can force a re-generation of password by including the secret key and username +in the `ssm_passwords_force_rotate` variable. For example: + +``` +ansible-playbook site.yml -e role=oracle-oem-agent-setup -e ssm_passwords_force_rotate=emrep_passwords:sysman --limit test-oem-a +``` diff --git a/ansible/roles/ssm-passwords/defaults/main.yml b/ansible/roles/ssm-passwords/defaults/main.yml new file mode 100644 index 000000000..fa1f283bf --- /dev/null +++ b/ansible/roles/ssm-passwords/defaults/main.yml @@ -0,0 +1,15 @@ +--- +ssm_passwords: +# - key: "unique_key_for_ansible_dictionary1" e.g. my_password +# parameter: "my-ssm-password-name1" e.g. /ec2/oracle/oem/passwords" +# users: # list of users and optional passwords (don't commit) +# - myuser1: myfixedvalue +# - myuser2 +# - key: "unique_key_for_ansible_dictionary2" +# account_name: "my-aws-account-name2" +# parameter: "my-ssm-password-name2" +# users: +# - myuser3 + +ssm_passwords_force_rotate: "" +# include the key and username to force rotation, e.g. "unique_key_for_ansible_dictionary1:myuser1" diff --git a/ansible/roles/ssm-passwords/tasks/main.yml b/ansible/roles/ssm-passwords/tasks/main.yml new file mode 100644 index 000000000..d3ebcc1f7 --- /dev/null +++ b/ansible/roles/ssm-passwords/tasks/main.yml @@ -0,0 +1,98 @@ +--- +- name: Prepare ssm_passwords_dict fact + set_fact: + ssm_passwords_dict: "{{ ssm_passwords_dict|default({}) }}" + +- name: Get SSM Parameters + set_fact: + ssm_passwords_dict: | + {{ ssm_passwords_dict | combine({ + item.key: { + 'parameter': item.parameter, + 'value': lookup('amazon.aws.aws_ssm', item.parameter, region='eu-west-2') + } + }, recursive=true) }} + loop_control: + label: "{{ item.key }}" + loop: "{{ ssm_passwords }}" + +# If this fails, the SSM parameter doesn't exist or isn't valid json +- name: Prepare any placeholder parameters + set_fact: + ssm_passwords_dict: | + {{ ssm_passwords_dict | combine({ + item.key: { + 'passwords': {} if 'placeholder' in ssm_passwords_dict[item.key].value else ssm_passwords_dict[item.key].value|from_json + } + }, recursive=true) }} + loop_control: + label: "{{ item.key }}" + loop: "{{ ssm_passwords }}" + +# The if statement: +# - use the password defined in the ssm_passwords variable if there is one +# - else use existing password defined in the SecretString and force_rotate not set +# - else generate random password +- name: Generate any missing passwords + set_fact: + ssm_passwords_dict: | + {{ ssm_passwords_dict | combine({ + item[0].key: { + 'passwords': { + item[1].keys()|first: + item[1].values()|first + if item[1].values()|first != None + else ssm_passwords_dict[item[0].key].passwords[item[1].keys()|first] + if item[1].keys()|first in ssm_passwords_dict[item[0].key].passwords + and [item[0].key, item[1].keys()|first]|join(':') not in ssm_passwords_force_rotate + else lookup('ansible.builtin.password', '/dev/null chars=ascii_letters length=1') + + lookup('ansible.builtin.password', '/dev/null chars=ascii_letters,digits length=15') + } + } + }, recursive=true) }} + loop_control: + label: "{{ item[0].key }}:{{ item[1].keys()|first }}" + with_subelements: + - "{{ ssm_passwords }}" + - users + +- name: Check parameters which require updating + set_fact: + ssm_passwords_dict: | + {{ ssm_passwords_dict | combine({ + item.key: { + 'upload': ssm_passwords_dict[item.key].passwords|to_json != ssm_passwords_dict[item.key].value + } + }, recursive=true) }} + loop_control: + label: "{{ item.key }}" + loop: "{{ ssm_passwords }}" + +- name: Create fact with updated parameters + set_fact: + ssm_passwords_to_update: "{{ ssm_passwords_dict | dict2items | selectattr('value.upload', 'equalto', true) }}" + +# - debug: +# var: ssm_passwords_to_update + +# Not using community.aws.ssm_parameter as this requires some additional +# parameters such as ssm:DescribeParameters at root level +- name: Upload updated parameters + ansible.builtin.shell: | + PATH=$PATH:/usr/local/bin + aws ssm put-parameter --name '{{ item.value.parameter }}' --type 'SecureString' --data-type 'text' --value '{{ item.value.passwords|to_json }}' --overwrite + loop_control: + label: "{{ item.key }}" + loop: "{{ ssm_passwords_to_update }}" + +- name: Update upload fact + set_fact: + ssm_passwords_dict: | + {{ ssm_passwords_dict | combine({ + item.key: { + 'upload': False + } + }, recursive=true) }} + loop_control: + label: "{{ item.key }}" + loop: "{{ ssm_passwords }}"