From bfec61ad8d42d565e8b92e474a899e75c1a0cc79 Mon Sep 17 00:00:00 2001 From: "Christian M. Adams" Date: Fri, 11 Jun 2021 11:54:06 -0400 Subject: [PATCH] Dynamically collect secrets for backup & restore roles - This prevents us from overwriting vars unintentionally at restore time - This will make it easier to add secrets to be backed up in the future - Add generated secret names to awx spec backup - Fail early if secret status doesn't exist - Skip if secret is not in spec for non-generated secrets - Secret values must be b64 decoded before secret is created - Cleanup temp files --- ansible/templates/awxrestore_crd.yml.j2 | 2 +- deploy/awx-operator.yaml | 2 +- deploy/crds/awxrestore_v1beta1_crd.yaml | 2 +- roles/backup/tasks/awx-cro.yml | 9 +++ roles/backup/tasks/dump_generated_secret.yml | 35 +++++++++ roles/backup/tasks/dump_secret.yml | 24 ++++++ roles/backup/tasks/main.yml | 6 +- roles/backup/tasks/secrets.yml | 78 ++++++-------------- roles/backup/templates/secrets.yml.j2 | 14 ---- roles/restore/tasks/cleanup.yml | 20 ++++- roles/restore/tasks/deploy_awx.yml | 9 --- roles/restore/tasks/postgres.yml | 2 +- roles/restore/tasks/secrets.yml | 42 ++++++++--- roles/restore/templates/secrets.yml.j2 | 61 ++------------- roles/restore/vars/main.yml | 8 +- 15 files changed, 153 insertions(+), 161 deletions(-) create mode 100644 roles/backup/tasks/dump_generated_secret.yml create mode 100644 roles/backup/tasks/dump_secret.yml delete mode 100644 roles/backup/templates/secrets.yml.j2 diff --git a/ansible/templates/awxrestore_crd.yml.j2 b/ansible/templates/awxrestore_crd.yml.j2 index 32b2be68..a8f88759 100644 --- a/ansible/templates/awxrestore_crd.yml.j2 +++ b/ansible/templates/awxrestore_crd.yml.j2 @@ -76,4 +76,4 @@ spec: type: array restoreComplete: description: Restore process complete - type: string + type: boolean diff --git a/deploy/awx-operator.yaml b/deploy/awx-operator.yaml index f945098a..7602f648 100644 --- a/deploy/awx-operator.yaml +++ b/deploy/awx-operator.yaml @@ -560,7 +560,7 @@ spec: type: array restoreComplete: description: Restore process complete - type: string + type: boolean --- apiVersion: rbac.authorization.k8s.io/v1 diff --git a/deploy/crds/awxrestore_v1beta1_crd.yaml b/deploy/crds/awxrestore_v1beta1_crd.yaml index 32b2be68..a8f88759 100644 --- a/deploy/crds/awxrestore_v1beta1_crd.yaml +++ b/deploy/crds/awxrestore_v1beta1_crd.yaml @@ -76,4 +76,4 @@ spec: type: array restoreComplete: description: Restore process complete - type: string + type: boolean diff --git a/roles/backup/tasks/awx-cro.yml b/roles/backup/tasks/awx-cro.yml index 12693827..91869bf0 100644 --- a/roles/backup/tasks/awx-cro.yml +++ b/roles/backup/tasks/awx-cro.yml @@ -16,6 +16,15 @@ set_fact: awx_spec: "{{ _awx['spec'] }}" +- name: Set names of backed up secrets in the CR spec + set_fact: + awx_spec: "{{ awx_spec | combine ({ item.key : item.value }) }}" + with_items: + - {"key": "secret_key_secret", "value": "{{ this_awx['resources'][0]['status']['secretKeySecret'] }}"} + - {"key": "admin_password_secret", "value": "{{ this_awx['resources'][0]['status']['adminPasswordSecret'] }}"} + - {"key": "broadcast_websocket_secret", "value": "{{ this_awx['resources'][0]['status']['broadcastWebsocketSecret'] }}"} + - {"key": "postgres_configuration_secret", "value": "{{ this_awx['resources'][0]['status']['postgresConfigurationSecret'] }} "} + - name: Write awx object to pvc k8s_exec: namespace: "{{ backup_pvc_namespace }}" diff --git a/roles/backup/tasks/dump_generated_secret.yml b/roles/backup/tasks/dump_generated_secret.yml new file mode 100644 index 00000000..ee6f5fc4 --- /dev/null +++ b/roles/backup/tasks/dump_generated_secret.yml @@ -0,0 +1,35 @@ +--- + +- name: Get secret name + set_fact: + _name: "{{ this_awx['resources'][0]['status'][item] }}" + +- name: Fail if status is not set on AWX CR + block: + - name: Set error message + set_fact: + error_msg: "{{ item }} status is not set on AWX object yet" + + - name: Handle error + import_tasks: error_handling.yml + + - name: Fail early if secret name status is not set + fail: + msg: "{{ error_msg }}" + when: _name is not defined or _name == '' + +- name: Get secret + k8s_info: + version: v1 + kind: Secret + namespace: '{{ meta.namespace }}' + name: "{{ _name }}" + register: _secret + +- name: Set secret data + set_fact: + _data: "{{ _secret['resources'][0]['data'] }}" + +- name: Create and Add secret names and data to dictionary + set_fact: + secret_dict: "{{ secret_dict | default({}) | combine({ item: {'name': _name, 'data': _data }}) }}" diff --git a/roles/backup/tasks/dump_secret.yml b/roles/backup/tasks/dump_secret.yml new file mode 100644 index 00000000..d062407c --- /dev/null +++ b/roles/backup/tasks/dump_secret.yml @@ -0,0 +1,24 @@ +--- + +- name: Get Secret Name + set_fact: + _name: "{{ awx_spec[item] | default('') }}" + +- name: Skip if secret name not defined + block: + - name: Get secret + k8s_info: + version: v1 + kind: Secret + namespace: '{{ meta.namespace }}' + name: "{{ _name }}" + register: _secret + + - name: Set secret key + set_fact: + _data: "{{ _secret['resources'][0]['data'] }}" + + - name: Create and Add secret names and data to dictionary + set_fact: + secret_dict: "{{ secret_dict | default({}) | combine({item: { 'name': _name, 'data': _data }}) }}" + when: _name != '' diff --git a/roles/backup/tasks/main.yml b/roles/backup/tasks/main.yml index a140eaa2..502450e3 100644 --- a/roles/backup/tasks/main.yml +++ b/roles/backup/tasks/main.yml @@ -30,10 +30,10 @@ - include_tasks: postgres.yml - - include_tasks: secrets.yml - - include_tasks: awx-cro.yml + - include_tasks: secrets.yml + - name: Set flag signifying this backup was successful set_fact: backup_complete: true @@ -45,5 +45,3 @@ - name: Update status variables include_tasks: update_status.yml - -# TODO: backup tower settings or make sure that users only specify settings/config changes via AWX object. See ticket diff --git a/roles/backup/tasks/secrets.yml b/roles/backup/tasks/secrets.yml index 0b839f13..3bf761b4 100644 --- a/roles/backup/tasks/secrets.yml +++ b/roles/backup/tasks/secrets.yml @@ -1,65 +1,33 @@ --- -- name: Get secret_key - k8s_info: - kind: Secret - namespace: '{{ meta.namespace }}' - name: "{{ this_awx['resources'][0]['status']['secretKeySecret'] }}" - register: _secret_key +- name: Create Temporary secrets file + tempfile: + state: file + suffix: .json + register: tmp_secrets -- name: Set secret key +- name: Dump (generated) secret names from statuses and data into file + include_tasks: dump_generated_secret.yml + with_items: + - secretKeySecret + - adminPasswordSecret + - broadcastWebsocketSecret + - postgresConfigurationSecret + +- name: Dump secret names from awx spec and data into file + include_tasks: dump_secret.yml + loop: + - route_tls_secret + - ldap_cacert_secret + - image_pull_secret + +- name: Nest secrets under a single variable set_fact: - secret_key: "{{ _secret_key['resources'][0]['data']['secret_key'] | b64decode }}" - -- name: Get admin_password - k8s_info: - kind: Secret - namespace: '{{ meta.namespace }}' - name: "{{ this_awx['resources'][0]['status']['adminPasswordSecret'] }}" - register: _admin_password - -- name: Set admin_password - set_fact: - admin_password: "{{ _admin_password['resources'][0]['data']['password'] | b64decode }}" - -- name: Get broadcast_websocket - k8s_info: - kind: Secret - namespace: '{{ meta.namespace }}' - name: "{{ this_awx['resources'][0]['status']['broadcastWebsocketSecret'] }}" - register: _broadcast_websocket - -- name: Set broadcast_websocket key - set_fact: - broadcast_websocket: "{{ _broadcast_websocket['resources'][0]['data']['secret'] | b64decode }}" - -- name: Get postgres configuration - k8s_info: - kind: Secret - namespace: '{{ meta.namespace }}' - name: "{{ this_awx['resources'][0]['status']['postgresConfigurationSecret'] }}" - register: _postgres_configuration - -- name: Set postgres type - set_fact: - database_type: "{{ _postgres_configuration['resources'][0]['data']['type'] | b64decode }}" - when: _postgres_configuration['resources'][0]['data']['type'] is defined - -- name: Set postgres configuration - set_fact: - database_password: "{{ _postgres_configuration['resources'][0]['data']['password'] | b64decode }}" - database_username: "{{ _postgres_configuration['resources'][0]['data']['username'] | b64decode }}" - database_name: "{{ _postgres_configuration['resources'][0]['data']['database'] | b64decode }}" - database_port: "{{ _postgres_configuration['resources'][0]['data']['port'] | b64decode }}" - database_host: "{{ _postgres_configuration['resources'][0]['data']['host'] | b64decode }}" - -- name: Template secrets into yaml - set_fact: - secrets_file: "{{ lookup('template', 'secrets.yml.j2') }}" + secrets: {"secrets": '{{ secret_dict }}'} - name: Write postgres configuration to pvc k8s_exec: namespace: "{{ backup_pvc_namespace }}" pod: "{{ meta.name }}-db-management" command: >- - bash -c "echo '{{ secrets_file }}' > {{ backup_dir }}/secrets.yml" + bash -c "echo '{{ secrets | to_yaml }}' > {{ backup_dir }}/secrets.yml" diff --git a/roles/backup/templates/secrets.yml.j2 b/roles/backup/templates/secrets.yml.j2 deleted file mode 100644 index e296a00c..00000000 --- a/roles/backup/templates/secrets.yml.j2 +++ /dev/null @@ -1,14 +0,0 @@ ---- -secret_key_secret_name: "{{ _secret_key['resources'][0]['metadata']['name'] }}" -admin_password_secret_name: "{{ _admin_password['resources'][0]['metadata']['name'] }}" -broadcast_websocket_secret_name: "{{ _broadcast_websocket['resources'][0]['metadata']['name'] }}" -postgres_configuration_secret_name: "{{ _postgres_configuration['resources'][0]['metadata']['name'] }}" -secret_key: {{ secret_key }} -admin_password: {{ admin_password }} -broadcast_websocket: {{ broadcast_websocket }} -database_password: {{ database_password }} -database_username: {{ database_username }} -database_name: {{ database_name }} -database_port: {{ database_port }} -database_host: {{ database_host }} -database_type: {{ database_type }} diff --git a/roles/restore/tasks/cleanup.yml b/roles/restore/tasks/cleanup.yml index 0f3d7208..f80ad691 100644 --- a/roles/restore/tasks/cleanup.yml +++ b/roles/restore/tasks/cleanup.yml @@ -18,7 +18,19 @@ namespace: '{{ meta.namespace }}' ownerReferences: null loop: - - '{{ secret_key_secret_name }}' - - '{{ admin_password_secret_name }}' - - '{{ broadcast_websocket_secret_name }}' - - '{{ postgres_configuration_secret_name }}' + - '{{ secret_key_secret }}' + - '{{ admin_password_secret }}' + - '{{ broadcast_websocket_secret }}' + - '{{ postgres_configuration_secret }}' + +- name: Cleanup temp spec file + file: + path: "{{ tmp_spec.path }}" + state: absent + when: tmp_spec.path is defined + +- name: Cleanup temp secret vars file + file: + path: "{{ secret_vars.path }}" + state: absent + when: secret_vars.path is defined diff --git a/roles/restore/tasks/deploy_awx.yml b/roles/restore/tasks/deploy_awx.yml index 2cd1f81c..d6d3fb67 100644 --- a/roles/restore/tasks/deploy_awx.yml +++ b/roles/restore/tasks/deploy_awx.yml @@ -31,15 +31,6 @@ set_fact: awx_spec: "{{ spec.ansible_facts }}" -- name: Set names of backed up secrets in the CR spec - set_fact: - awx_spec: "{{ awx_spec | combine ({ item.key : item.value }) }}" - with_items: - - {'key': 'secret_key_secret', 'value': '{{ secret_key_secret_name }}'} - - {'key': 'admin_password_secret', 'value': '{{ admin_password_secret_name }}'} - - {'key': 'broadcast_websocket_secret', 'value': '{{ broadcast_websocket_secret_name }}'} - - {'key': 'postgres_configuration_secret', 'value': '{{ postgres_configuration_secret_name }}'} - - name: Restore kind set_fact: kind: "{{ _kind }}" diff --git a/roles/restore/tasks/postgres.yml b/roles/restore/tasks/postgres.yml index 8e6a117f..db494ccc 100644 --- a/roles/restore/tasks/postgres.yml +++ b/roles/restore/tasks/postgres.yml @@ -4,7 +4,7 @@ k8s_info: kind: Secret namespace: '{{ meta.namespace }}' - name: '{{ postgres_configuration_secret_name }}' + name: '{{ postgres_configuration_secret }}' register: pg_config - name: Store Database Configuration diff --git a/roles/restore/tasks/secrets.yml b/roles/restore/tasks/secrets.yml index b543769a..d00aef47 100644 --- a/roles/restore/tasks/secrets.yml +++ b/roles/restore/tasks/secrets.yml @@ -6,27 +6,45 @@ pod: "{{ meta.name }}-db-management" command: >- bash -c "cat '{{ backup_dir }}/secrets.yml'" - register: secrets + register: _secrets -- name: Create temp vars file +- name: Create Temporary secrets file tempfile: - prefix: secret_vars- - register: secret_vars + state: file + suffix: .json + register: tmp_secrets - name: Write vars to file locally copy: - dest: "{{ secret_vars.path }}" - content: "{{ secrets.stdout }}" + dest: "{{ tmp_secrets.path }}" + content: "{{ _secrets.stdout }}" mode: 0640 - name: Include secret vars from backup - include_vars: "{{ secret_vars.path }}" + include_vars: "{{ tmp_secrets.path }}" -- name: Set new database host based on supplied deployment_name - set_fact: - database_host: "{{ deployment_name }}-postgres" - when: - - database_type == 'managed' +- name: If deployment is managed, set the database_host in the pg config secret + block: + - name: Set new database host + set_fact: + database_host: "{{ deployment_name }}-postgres" + + - name: Set tmp postgres secret dict + set_fact: + _pg_secret: "{{ secrets['postgresConfigurationSecret'] }}" + + - name: Change postgres host value + set_fact: + _pg_data: "{{ _pg_secret['data'] | combine({'host': database_host | b64encode }) }}" + + - name: Create a postgres secret with the new host value + set_fact: + _pg_secret: "{{ _pg_secret | combine({'data': _pg_data}) }}" + + - name: Create a new dict of secrets with the new postgres secret + set_fact: + secrets: "{{ secrets | combine({'postgresConfigurationSecret': _pg_secret}) }}" + when: secrets['postgresConfigurationSecret']['data']['type'] | b64decode == 'managed' - name: Apply secret k8s: diff --git a/roles/restore/templates/secrets.yml.j2 b/roles/restore/templates/secrets.yml.j2 index a4803355..beeaf840 100644 --- a/roles/restore/templates/secrets.yml.j2 +++ b/roles/restore/templates/secrets.yml.j2 @@ -1,9 +1,9 @@ -# Postgres Secret +{% for secret in secrets %} --- apiVersion: v1 kind: Secret metadata: - name: '{{ postgres_configuration_secret_name }}' + name: '{{ secrets[secret]['name'] }}' namespace: '{{ meta.namespace }}' labels: app.kubernetes.io/name: '{{ meta.name }}' @@ -12,57 +12,8 @@ metadata: app.kubernetes.io/component: '{{ deployment_type }}' app.kubernetes.io/operator-version: '{{ lookup("env", "OPERATOR_VERSION") }}' stringData: - password: '{{ database_password }}' - username: '{{ database_username }}' - database: '{{ database_name }}' - port: '{{ database_port }}' - host: '{{ database_host }}' - type: '{{ database_type }}' + {% for key, value in secrets[secret]['data'].items() %} + '{{ key }}': '{{ value | b64decode }}' + {% endfor %} -# Secret Key Secret ---- -apiVersion: v1 -kind: Secret -metadata: - name: '{{ secret_key_secret_name }}' - namespace: '{{ meta.namespace }}' - labels: - app.kubernetes.io/name: '{{ meta.name }}' - app.kubernetes.io/part-of: '{{ meta.name }}' - app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' - app.kubernetes.io/component: '{{ deployment_type }}' - app.kubernetes.io/operator-version: '{{ lookup("env", "OPERATOR_VERSION") }}' -stringData: - secret_key: '{{ secret_key }}' - -# Admin Password Secret ---- -apiVersion: v1 -kind: Secret -metadata: - name: '{{ admin_password_secret_name }}' - namespace: '{{ meta.namespace }}' - labels: - app.kubernetes.io/name: '{{ meta.name }}' - app.kubernetes.io/part-of: '{{ meta.name }}' - app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' - app.kubernetes.io/component: '{{ deployment_type }}' - app.kubernetes.io/operator-version: '{{ lookup("env", "OPERATOR_VERSION") }}' -stringData: - password: '{{ admin_password }}' - -# Broadcast Websocket Secret ---- -apiVersion: v1 -kind: Secret -metadata: - name: '{{ broadcast_websocket_secret_name }}' - namespace: '{{ meta.namespace }}' - labels: - app.kubernetes.io/name: '{{ meta.name }}' - app.kubernetes.io/part-of: '{{ meta.name }}' - app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' - app.kubernetes.io/component: '{{ deployment_type }}' - app.kubernetes.io/operator-version: '{{ lookup("env", "OPERATOR_VERSION") }}' -stringData: - secret: '{{ broadcast_websocket }}' +{% endfor %} diff --git a/roles/restore/vars/main.yml b/roles/restore/vars/main.yml index 837c7bf1..ae336b38 100644 --- a/roles/restore/vars/main.yml +++ b/roles/restore/vars/main.yml @@ -8,7 +8,7 @@ backup_api_version: '{{ deployment_type }}.ansible.com/v1beta1' backup_kind: 'AWXBackup' # set default secret names to be used if a backup dir and claim are provided (not a backup_name) -secret_key_secret_name: '{{ deployment_name }}-secret-key' -admin_password_secret_name: '{{ deployment_name }}-admin-password' -broadcast_websocket_secret_name: '{{ deployment_name }}-broadcast-websocket' -postgres_configuration_secret_name: '{{ deployment_name }}-postgres-configuration' +secret_key_secret: '{{ deployment_name }}-secret-key' +admin_password_secret: '{{ deployment_name }}-admin-password' +broadcast_websocket_secret: '{{ deployment_name }}-broadcast-websocket' +postgres_configuration_secret: '{{ deployment_name }}-postgres-configuration'