diff --git a/ansible/roles/get-modernisation-platform-facts/README.md b/ansible/roles/get-modernisation-platform-facts/README.md index 46f75134f..f2aca287b 100644 --- a/ansible/roles/get-modernisation-platform-facts/README.md +++ b/ansible/roles/get-modernisation-platform-facts/README.md @@ -1,9 +1,14 @@ Role for retrieving common modernisation platform secrets and SSM parameters. -Variables are set as follows: -- `modernisation_platform_account_id` is the account ID for `modernisation_platform` account -- `environment_management_secret` is the `environment_management` secret -- `account_ids` is a map of account IDs where account name is the key. Part of the `environment_management` secret. +Note that the `environment_management` secret stored in `modernisation_platform` +is not shared with EC2 instances. So this role relies on a copy being stored +as a SSM parameter `account_ids`. + +See nomis for an example of how this parameter is created using the +`baseline` and `baseline_presets` module. + +Facts are set as follows: +- `account_ids` is a map of account IDs where account name is the key Use this if you need to reference resources from other accounts. For example: diff --git a/ansible/roles/get-modernisation-platform-facts/tasks/get-facts.yml b/ansible/roles/get-modernisation-platform-facts/tasks/get-facts.yml index b99393b4c..2363b9085 100644 --- a/ansible/roles/get-modernisation-platform-facts/tasks/get-facts.yml +++ b/ansible/roles/get-modernisation-platform-facts/tasks/get-facts.yml @@ -1,20 +1,10 @@ --- -# Ensure EC2 includes arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore or a policy with ssm:GetParameter -- name: Get modernisation_platform_account_id SSM parameter - set_fact: - modernisation_platform_account_id: "{{ lookup('aws_ssm', 'modernisation_platform_account_id', region='eu-west-2') }}" - failed_when: modernisation_platform_account_id|length < 12 - -- name: Get environment_management SecretsManager Secret Id - set_fact: - environment_management_secret_id: "arn:aws:secretsmanager:eu-west-2:{{ modernisation_platform_account_id }}:secret:environment_management" - -# The secret is shared with all accounts, the EC2 should have access without any extra policies. -- name: Get environment_maangement SecretsManager Secret - set_fact: - environment_management_secret: "{{ lookup('amazon.aws.aws_secret', environment_management_secret_id, region='eu-west-2') }}" - failed_when: environment_management_secret|length < 2 +# Ensure EC2 role has arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore, +# or a policy including ssm:GetParameter +# The environment_management secret in modernisation platform account is not +# accessible from an EC2. Store the accounts you need in an SSM parameter +# called `account_ids` instead via terraform. - name: Get account_ids JSON set_fact: - account_ids: "{{ environment_management_secret.account_ids }}" + account_ids: "{{ lookup('aws_ssm', 'account_ids', region='eu-west-2') }}" diff --git a/ansible/roles/oracle-oem-agent-setup/defaults/main.yml b/ansible/roles/oracle-oem-agent-setup/defaults/main.yml index 48c8db255..2347a8786 100644 --- a/ansible/roles/oracle-oem-agent-setup/defaults/main.yml +++ b/ansible/roles/oracle-oem-agent-setup/defaults/main.yml @@ -14,14 +14,16 @@ agent_ru_patch_number: 35437910 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_secrets: +oem_secretsmanager_passwords: - key: "oem_passwords" account_name: "hmpps-oem-{{ aws_environment }}" + assume_role_name: "EC2OracleEnterpriseManagementSecretsRole" secret: "/oracle/oem/passwords" users: - agentreg: - key: "emrep_passwords" account_name: "hmpps-oem-{{ aws_environment }}" + assume_role_name: "EC2OracleEnterpriseManagementSecretsRole" secret: "/oracle/database/EMREP/passwords" users: - sysman: 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 3fa6f8711..5af35a06c 100644 --- a/ansible/roles/oracle-oem-agent-setup/tasks/get_facts.yml +++ b/ansible/roles/oracle-oem-agent-setup/tasks/get_facts.yml @@ -3,7 +3,7 @@ import_role: name: secretsmanager-passwords vars: - secretsmanager_passwords: "{{ oem_secretsmanager_secrets }}" + secretsmanager_passwords: "{{ oem_secretsmanager_passwords }}" - name: Set SSM parameters path fact from ec2 ssm-parameters-prefix and Name tag set_fact: diff --git a/ansible/roles/secretsmanager-passwords/README.md b/ansible/roles/secretsmanager-passwords/README.md index 6a982a10b..c299d1b43 100644 --- a/ansible/roles/secretsmanager-passwords/README.md +++ b/ansible/roles/secretsmanager-passwords/README.md @@ -11,6 +11,31 @@ The secret itself is a JSON with following structure: } ``` +## Sharing SecretsManager Secrets across accounts + +If you are not doing this, you may as well use SSM parameters. +This is tricky to setup. + +The EC2 needs to assume an IAM role with the relevant permissions to retrieve +the SecretsManager secret and KMS key. There's example of how to do this +in baseline terraform code: + +Account core-shared-services-production +- Holds the KMS key which encrypts the secret + +Account A e.g. hmpps-oem-development +- Creates secrets and shares the resources with each relevant IAM role in the + target accounts B,C,D etc. See `locals_oem.tf` in hmpps-oem terraform + +Account B,C,D, e.g. nomis-development, oasys-development +- Creates an IAM role which can be assumed from an EC2. See + EC2OracleEnterpriseManagementSecretsRole in `baseline_presets` terraform module. +- Ensure this role can access KMS keys and Secrets + +The ansible will assume the role (if necessary) to retrieve the secret from +another account. + + ## Recommended Usage 1. Set placeholder value in terraform @@ -35,11 +60,13 @@ Ensure the secret has been shared with EC2 instances in this account. secretsmanager_passwords: - key: "oem_passwords" account_name: "hmpps-oem-{{ environment }}" + assume_role_name: EC2OracleEnterpriseManagementSecretsRole secret: "/ec2/oracle/oem/passwords" users: - agentreg: - key: "emrep_passwords" account_name: "hmpps-oem-{{ environment }}" + assume_role_name: EC2OracleEnterpriseManagementSecretsRole secret: "/ec2/oracle/database/EMREP/passwords" users: - sysman: diff --git a/ansible/roles/secretsmanager-passwords/defaults/main.yml b/ansible/roles/secretsmanager-passwords/defaults/main.yml index 421fe34f9..bfe390895 100644 --- a/ansible/roles/secretsmanager-passwords/defaults/main.yml +++ b/ansible/roles/secretsmanager-passwords/defaults/main.yml @@ -1,13 +1,15 @@ --- secretmanager_passwords: -# - key: "unique_key_for_ansible_dictionary1" e.g. my_password +# - 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 # secret: "my-secretsmanager-secret-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" +# - key: "unique_key_for_ansible_dictionary2_same_account" +# assume_role_name: "" +# account_name: "{{ application }}-{{ aws_environment }}"" # secret: "my-secretsmanager-secret-name2" # users: # - myuser3 diff --git a/ansible/roles/secretsmanager-passwords/tasks/main.yml b/ansible/roles/secretsmanager-passwords/tasks/main.yml index 17d9fca58..611290df4 100644 --- a/ansible/roles/secretsmanager-passwords/tasks/main.yml +++ b/ansible/roles/secretsmanager-passwords/tasks/main.yml @@ -7,53 +7,51 @@ - name: Setup facts set_fact: secretsmanager_passwords_dict: "{{ secretsmanager_passwords_dict|default({}) }}" - secretsmanager_passwords_self: "{{ secretsmanager_passwords | rejectattr('account_name', 'defined') }}" - secretsmanager_passwords_other: "{{ secretsmanager_passwords | selectattr('account_name', 'defined') }}" -# If this fails, ensure secret has been created via terraform first -- name: Form Secret Ids - Self - set_fact: - secretsmanager_passwords_dict: | - {{ secretsmanager_passwords_dict | combine({ - item.key: { - 'id': item.secret - } - }, recursive=true) }} - loop_control: - label: "{{ item.key }}" - loop: "{{ secretsmanager_passwords_self }}" - -# If this fails, ensure secret has been created via terraform first and has been -# shared into this account -- name: Form Secret Ids - Other accounts - set_fact: - secretsmanager_passwords_dict: | - {{ secretsmanager_passwords_dict | combine({ - item.key: { - 'id': 'arn:aws:secretsmanager:eu-west-2:' + account_ids[item.account_name] + ':secret:' + item.secret - } - }, recursive=true) }} +# Using the cli instead of native ansible as we need to assume a role +# to access secrets in other accounts +- name: Get SecretManager Secrets + ansible.builtin.shell: | + PATH=$PATH:/usr/local/bin + set -e + account_id=$(aws sts get-caller-identity --query Account --output text) + secret_account_id="{{ account_ids[item.account_name] }}" + if [[ $account_id != $secret_account_id ]]; then + role_arn="arn:aws:iam::${account_id}:role/{{ item.assume_role_name }}" + session="{{ item.key }}-ansible" + creds=$(aws sts assume-role --role-arn "${role_arn}" --role-session-name "${session}" --output text --query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken]") + export AWS_ACCESS_KEY_ID=$(echo "${creds}" | tail -1 | cut -f1) + export AWS_SECRET_ACCESS_KEY=$(echo "${creds}" | tail -1 | cut -f2) + export AWS_SESSION_TOKEN=$(echo "${creds}" | tail -1 | cut -f3) + fi + secret_arn="arn:aws:secretsmanager:eu-west-2:${secret_account_id}:secret:{{ item.secret }}" + aws secretsmanager get-secret-value --secret-id "${secret_arn}" --query SecretString --output text loop_control: label: "{{ item.key }}" - loop: "{{ secretsmanager_passwords_other }}" + loop: "{{ secretsmanager_passwords }}" + check_mode: false + changed_when: false + register: get_secrets_shell -- name: Get SecretManager Secrets +- name: Add Secrets to fact set_fact: secretsmanager_passwords_dict: | {{ secretsmanager_passwords_dict | combine({ - item.key: { - 'value': lookup('amazon.aws.aws_secret', secretsmanager_passwords_dict[item.key].id, region='eu-west-2') + item.item.key: { + 'value': item.stdout } }, recursive=true) }} loop_control: - label: "{{ item.key }}" - loop: "{{ secretsmanager_passwords }}" + label: "{{ item.item.key }}" + loop: "{{ get_secrets_shell.results }}" +# If this fails, check the secret value is valid json - name: Prepare any placeholder secrets set_fact: secretsmanager_passwords_dict: | {{ secretsmanager_passwords_dict | combine({ item.key: { + 'config': item, 'passwords': {} if 'placeholder' in secretsmanager_passwords_dict[item.key].value else secretsmanager_passwords_dict[item.key].value|from_json } }, recursive=true) }} @@ -100,52 +98,27 @@ label: "{{ item.key }}" loop: "{{ secretsmanager_passwords }}" -# NOTE: if this fails, check a password isn't being updated in an account -# that is different to where the secret is stored. If this is intentional, -# the relevant IAM policies for updating secrets across accounts need -# to be configured -- name: Retrieve resource policy for any secrets requiring update - ansible.builtin.shell: | - if [[ {{ secretsmanager_passwords_dict[item.key].upload }} == True ]]; then - PATH=$PATH:/usr/local/bin - aws secretsmanager get-resource-policy --secret-id "{{ secretsmanager_passwords_dict[item.key].id }}" --query ResourcePolicy --output text - fi - loop_control: - label: "{{ item.key }}" - loop: "{{ secretsmanager_passwords }}" - check_mode: false - changed_when: false - register: secret_resource_policy - -- name: Add resource policy to fact - set_fact: - secretsmanager_passwords_dict: | - {{ secretsmanager_passwords_dict | combine({ - item.item.key: { - 'policy': item.stdout - } - }, recursive=true) }} - loop_control: - label: "{{ item.item.key }}" - loop: "{{ secret_resource_policy.results }}" - - name: Check secrets which require updating set_fact: secretsmanager_passwords_to_update: "{{ secretsmanager_passwords_dict | dict2items | selectattr('value.upload', 'equalto', true) }}" # - debug: -# var: secretsmanager_passwords_to_update +# var: secretsmanager_passwords_to_update -# When I tested, if resource_policy wasn't set, the policy was cleared -# and would need to be reset in terraform. So attempting to set here. -- name: Upload updated secrets - community.aws.secretsmanager_secret: - region: "eu-west-2" - name: "{{ item.value.id }}" - secret: "{{ item.value.passwords|to_json }}" - resource_policy: "{{ item.value.policy }}" - loop_control: - label: "{{ item.key }}" +# 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 + ansible.builtin.shell: | + PATH=$PATH:/usr/local/bin + set -e + account_id=$(aws sts get-caller-identity --query Account --output text) + secret_account_id="{{ account_ids[item.value.config.account_name] }}" + if [[ $account_id != $secret_account_id ]]; then + echo "ERROR: cannot update secret in other account" >&2 + exit 1 + fi + aws secretsmanager put-secret-value --secret-id "{{ item.value.config.secret }}" --secret-string '{{ item.value.passwords|to_json }}' loop: "{{ secretsmanager_passwords_to_update }}" - name: Update upload fact