Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DSOS-2188: fix OEM secret sharing #339

Merged
merged 2 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions ansible/roles/get-modernisation-platform-facts/README.md
Original file line number Diff line number Diff line change
@@ -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:

Expand Down
Original file line number Diff line number Diff line change
@@ -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') }}"
4 changes: 3 additions & 1 deletion ansible/roles/oracle-oem-agent-setup/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
2 changes: 1 addition & 1 deletion ansible/roles/oracle-oem-agent-setup/tasks/get_facts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
27 changes: 27 additions & 0 deletions ansible/roles/secretsmanager-passwords/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
8 changes: 5 additions & 3 deletions ansible/roles/secretsmanager-passwords/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
115 changes: 44 additions & 71 deletions ansible/roles/secretsmanager-passwords/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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) }}
Expand Down Expand Up @@ -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
Expand Down