From 0e10763eb71963b77c0481e7eba05621590bd071 Mon Sep 17 00:00:00 2001 From: Marcelo Loebens Date: Fri, 10 Nov 2023 11:59:24 -0400 Subject: [PATCH] Improved system-local-ca check and install Included support to check multiple ICAs bundled together (verifying requirements for each one individually) and have override and default variables for ICA or RCA expiration checks (default is 3 years for both). Also, include check to verify if the ICA TLS cert/key match each other. Changed the behavior to only install the RCA as trusted (before we installed both the ICA and the RCA). Also made the install optional so the role can be used to verify the certificates in the first steps of the bootstrap. With this, the user can fix the provided certificates if a problem is detected without waiting until the end of the bootstrap (where the installation of the RCA will be made). Changed the naming to better reflect the intention of the role. Test plan: PASS: Perform cert-manager migration using: - RCA / single ICA; - RCA / multiple ICAs in files; - RCA / multiple ICAs with one with short expiration date (should fail with a message regarding the short expiration); - RCA / multiple ICAs with one with short expiration date but use override flag to allow short expiration date; - RCA with short expiration date / single ICA; (should fail with a message regarding the short expiration); - RCA with short expiration date but use override flag to allow short expiration date/ single ICA; PASS: Deploy AIO-SX providing the CA certs for 'system-local-ca' and enable feature flag. ICA file should contain multiple bundled ICAs.The CAs provided must be within the expected expiration date. Verify: - HTTPS is enabled and openstack public endpoints change into it after unlocking the controller. - The target certificates are issued by 'system-local-ca', and are managed by cert-manager; - The 'system-local-ca' has the provided certs data. - The certificates in /etc/ssl/private are correct. - It's possible to log into the local Docker Registry. - Horizon is working as expected. PASS: Deploy AIO-SX w/o providing the CA certs for 'system-local-ca' and enable feature flag. Verify: - HTTPS is enabled and openstack public endpoints change into it after unlocking the controller. - The target certificates are issued by 'system-local-ca', and are managed by cert-manager; - 'system-local-ca' has the Kubernetes Root CA data. - The certificates in /etc/ssl/private are correct. - It's possible to log into the local Docker Registry. - Horizon is working as expected. PASS: Deploy AIO-SX providing the CA certs for 'system-local-ca' and enable feature flag. ICA file should contain multiple bundled ICAs. One of the ICAs provided must have a short expiration date. Verify installation fail at the beginning of bootstrap, under 'bootstrap/validate-config'. PASS: Deploy AIO-SX providing the CA certs for 'system-local-ca' and enable feature flag. ICA file should contain multiple bundled ICAs. One of the ICAs provided must have a short expiration date. Provide 'ica_duration' override as to make the expiration after the fail limit. Verify that the bootstrap is successful. PASS: Deploy AIO-SX providing the CA certs for 'system-local-ca' and enable feature flag. ICA file should contain the same RCA cert as in the RCA file. Verify bootstrap fail with a message that the ICA must have an ICA. PASS: Deploy AIO-SX providing the CA certs for 'system-local-ca' and enable feature flag. ICA key file should contain a key that doesn't match the ICA cert provided. Verify bootstrap fail with a message that the ICA TLS cert/key pair must match. Story: 2009811 Task: 48907 Change-Id: I6a835990a6e3606f5d716dd8ed7225cf60d4bbb9 Signed-off-by: Marcelo Loebens --- ...cates-to-certmanager-inventory-EXAMPLE.yml | 22 +- ...e_platform_certificates_to_certmanager.yml | 9 +- .../tasks/retrieve-system-local-ca-data.yml | 8 +- .../vars/main.yml | 1 - .../bootstrap/validate-config/tasks/main.yml | 11 +- .../common/install-trusted-ca/tasks/main.yml | 212 ------------------ .../send-ca-cert-to-subcloud/tasks/main.yml | 8 +- ...cert-content-requirements-verification.yml | 57 +++++ .../tasks/ica-individual-verification.yml | 68 ++++++ .../tasks/main.yml | 209 +++++++++++++++++ .../verify-system-local-ca-certificates.yml | 114 ++++++++++ .../vars/main.yml | 13 ++ .../migrate-certificates/tasks/main.yml | 9 +- 13 files changed, 495 insertions(+), 246 deletions(-) delete mode 100644 playbookconfig/src/playbooks/roles/common/install-trusted-ca/tasks/main.yml create mode 100644 playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/cert-content-requirements-verification.yml create mode 100644 playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/ica-individual-verification.yml create mode 100644 playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/main.yml create mode 100644 playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/verify-system-local-ca-certificates.yml create mode 100644 playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/vars/main.yml diff --git a/examples/migrate/migrate-platform-certificates-to-certmanager-inventory-EXAMPLE.yml b/examples/migrate/migrate-platform-certificates-to-certmanager-inventory-EXAMPLE.yml index 15c4d4782..d732f9043 100644 --- a/examples/migrate/migrate-platform-certificates-to-certmanager-inventory-EXAMPLE.yml +++ b/examples/migrate/migrate-platform-certificates-to-certmanager-inventory-EXAMPLE.yml @@ -1,6 +1,6 @@ --- # -# Copyright (c) 2021-2022 Wind River Systems, Inc. +# Copyright (c) 2021-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -32,13 +32,15 @@ # # Please make sure that you use a system_root_ca_cert and system_local_ca_cert certificate # with a long duration. The playbook will fail if any of these CA certificates expire in -# less than 3 years. +# less than the default expected values in min years: +# - For RCA, is 3 years; +# - For ICA, is 1 year. # # If you wish to use a different value for CA duration you can override -# it by setting a different value to validation parameter ca_duration: +# it by setting a different value to validation parameters rca_duration/ica_duration. # Example: -# ca_duration: 2 -# Notice that ca_duration is applied to both system_root_ca_cert and system_local_ca_cert +# 'rca_duration: 2' will result in the playbook only accepting RCAs expiring 2 or more +# years from now. # # Please also make sure that duration and renewBefore are sensible values # considering the system_local_ca_cert remaining duration. @@ -57,11 +59,13 @@ all: # Note: system_local_ca_cert and system_root_ca_cert must be the same for that configuration. system_local_ca_cert: system_local_ca_key: - # the ca_duration parameter is optional. If not specified, it defaults to 3. - # It represents the number of years for the CA certificates validity check. + # the ica_duration/rca_duration parameters are optional. If not specified, it defaults to 3 + # for RCA and 1 for ICA. + # It represents the number of years for the CA certificates expiration validity check. # It is not recommended to use short values for this parameter. - # It applies to both system_local_ca_cert and the system_local_ca_cert certificates. - ca_duration: 3 + # rca_duration: 3 + # ica_duration: 1 + children: # This will be applied to all online subclouds # Use the below example in hosts to override particulars for a subcloud such as passwords diff --git a/playbookconfig/src/playbooks/migrate_platform_certificates_to_certmanager.yml b/playbookconfig/src/playbooks/migrate_platform_certificates_to_certmanager.yml index 617f56b0e..dbd2cf523 100644 --- a/playbookconfig/src/playbooks/migrate_platform_certificates_to_certmanager.yml +++ b/playbookconfig/src/playbooks/migrate_platform_certificates_to_certmanager.yml @@ -83,12 +83,9 @@ - block: - name: Install certificates as system Trusted CA certificates include_role: - name: common/install-trusted-ca - with_items: - - { name: system_local_ca_cert, content: "{{ system_local_ca_cert }}" } - - { name: system_root_ca_cert, content: "{{ system_root_ca_cert }}" } - loop_control: - label: "{{ item.name }}" + name: common/verify-and-install-system-local-ca-certs + vars: + - install_rca: true - name: Restart kube-apiserver to pick the new certificates include_role: diff --git a/playbookconfig/src/playbooks/roles/bootstrap/install-platform-certificates/tasks/retrieve-system-local-ca-data.yml b/playbookconfig/src/playbooks/roles/bootstrap/install-platform-certificates/tasks/retrieve-system-local-ca-data.yml index 4d2ad2b29..d75a5a384 100644 --- a/playbookconfig/src/playbooks/roles/bootstrap/install-platform-certificates/tasks/retrieve-system-local-ca-data.yml +++ b/playbookconfig/src/playbooks/roles/bootstrap/install-platform-certificates/tasks/retrieve-system-local-ca-data.yml @@ -92,10 +92,8 @@ - name: Install system_root_ca_cert as Trusted CA include_role: - name: common/install-trusted-ca - with_items: - - { name: system_root_ca_cert, content: "{{ system_root_ca_cert }}" } - loop_control: - label: "{{ item.name }}" + name: common/verify-and-install-system-local-ca-certs + vars: + - install_rca: true when: - install_rca_as_trusted diff --git a/playbookconfig/src/playbooks/roles/bootstrap/install-platform-certificates/vars/main.yml b/playbookconfig/src/playbooks/roles/bootstrap/install-platform-certificates/vars/main.yml index 0c1b7dff1..53136a585 100644 --- a/playbookconfig/src/playbooks/roles/bootstrap/install-platform-certificates/vars/main.yml +++ b/playbookconfig/src/playbooks/roles/bootstrap/install-platform-certificates/vars/main.yml @@ -7,7 +7,6 @@ cert_manager_spec_file: /tmp/platform_certificates.yaml -ca_duration: 3 system_platform_certificate: dns_domain: starlingx.local duration: 2160h # 90d diff --git a/playbookconfig/src/playbooks/roles/bootstrap/validate-config/tasks/main.yml b/playbookconfig/src/playbooks/roles/bootstrap/validate-config/tasks/main.yml index 2c689f9a7..a24a52a4d 100644 --- a/playbookconfig/src/playbooks/roles/bootstrap/validate-config/tasks/main.yml +++ b/playbookconfig/src/playbooks/roles/bootstrap/validate-config/tasks/main.yml @@ -906,7 +906,7 @@ msg: "system_local_ca_key file not found. ({{ system_local_ca_key }})" when: (system_local_ca_key | length > 0) and not (system_local_ca_key is file) - - name: Encode the user provided files cert/key files + - name: Encode the user provided cert/key files block: - name: Encode system_root_ca_cert shell: cat "{{ system_root_ca_cert }}" | base64 -w0 @@ -926,12 +926,19 @@ system_local_ca_cert_file: "{{ system_local_ca_cert }}" system_local_ca_key_file: "{{ system_local_ca_key }}" - # TODO (mdecastr): Generalize cert verification for trusted CAs and include in here - set_fact: system_root_ca_cert: "{{ root_ca_cert_output.stdout }}" system_local_ca_cert: "{{ local_ca_cert_output.stdout }}" system_local_ca_key: "{{ local_ca_key_output.stdout }}" system_local_ca_overrides: true + + - name: Verify 'system-local-ca' certs + include_role: + name: common/verify-and-install-system-local-ca-certs + vars: + - install_rca: false + - enforce_ica: true + when: system_local_ca_cert | length > 0 when: mode != 'restore' diff --git a/playbookconfig/src/playbooks/roles/common/install-trusted-ca/tasks/main.yml b/playbookconfig/src/playbooks/roles/common/install-trusted-ca/tasks/main.yml deleted file mode 100644 index db7e0b268..000000000 --- a/playbookconfig/src/playbooks/roles/common/install-trusted-ca/tasks/main.yml +++ /dev/null @@ -1,212 +0,0 @@ ---- -# -# Copyright (c) 2021-2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# -# These tasks provide the functionality to validate ICA duration and -# install it as a platform Trusted CA. -# -- name: Create root pem temporary file - tempfile: - state: file - prefix: root_ - suffix: .pem - path: /tmp/ - register: root_ca_file - -- name: Create ca pem temporary file - tempfile: - state: file - prefix: ca_ - suffix: .pem - path: /tmp/ - register: local_ca_file - -- name: Save {{ item.name }} certificate to a file - copy: - dest: "{{ local_ca_file.path }}" - content: "{{ item.content | b64decode }}" - mode: 0640 - -- block: - - name: Save system_root_ca_cert to a file - copy: - dest: "{{ root_ca_file.path }}" - content: "{{ system_root_ca_cert | b64decode }}" - mode: 0640 - - - name: Check if system_local_ca_cert is signed by system_root_ca_cert or self-signed - command: openssl verify -verbose -CAfile {{ root_ca_file.path }} {{ local_ca_file.path }} - register: ca_verification - # failed_when as false in order to print a better error msg in the task below - failed_when: false - - - name: Fail when system_local_ca_cert is not signed by system_root_ca_cert or self-signed - fail: - msg: | - The system_local_ca_cert provided is not signed by - system_root_ca_cert provided or self-signed. - Please review your inventory parameters. - when: ca_verification.rc | int != 0 - when: item.name == 'system_local_ca_cert' - -- name: Get CA information from certificate - shell: | - openssl x509 -in {{ local_ca_file.path }} -text -noout | grep "CA:" - register: is_ca - -- name: Fail when certificate specified is not an actual CA certificate - fail: - msg: The {{ item.name }} certificate provided is not a CA certificate (CA:FALSE) - when: "'CA:TRUE' not in is_ca.stdout" - -- name: Get years for CA duration validation - set_fact: - ca_duration: "{{ ca_duration if ca_duration is defined else 3 }}" - -- name: Check that CA certificate remaining duration is longer than {{ ca_duration }} years - shell: | - expiration_date=$(cat {{ local_ca_file.path }} | openssl x509 -noout -enddate | cut -d'=' -f2) - expiration_date_timestamp=$(date -d "${expiration_date}" +%s) - date_5years_from_now_timestamp=$(date -d "+{{ ca_duration }} years" +%s) - time_left_ica=$(expr $expiration_date_timestamp - $date_5years_from_now_timestamp) - echo $time_left_ica - register: ica_time_left - -- name: Fail when CA certificate remaining duration is shorter than {{ ca_duration }} years - fail: - msg: >- - The remaining duration for the {{ item.name }} certificate specified - is less than {{ ca_duration }} years. - Please use a certificate with a longer validity. - when: ica_time_left.stdout | int < 0 - -# ignore_alarms flag can be set to avoid waiting. Defaults to false. -- name: Initialize flag ignore_alarms - set_fact: - ignore_alarms: "{{ false if ignore_alarms is not defined else ignore_alarms | bool }}" - -- name: Verify if there are 250.001 (config out-of-date) alarms before installing certificate - block: - - name: Check if an 250.001 alarm exists and wait it to be cleared - shell: | - source /etc/platform/openrc; - fm alarm-list --query alarm_id=250.001 - register: alarm_subcloud - retries: 10 - delay: 20 - until: alarm_subcloud.stdout == "" - failed_when: false - - - name: Fail when the alarm remains - fail: - msg: >- - Timed out waiting 250.001 alarm to clear out. - when: alarm_subcloud.stdout != "" - - - name: Register stat of .config_applied file - stat: - path: /etc/platform/.config_applied - register: prev_config_applied_stat - when: not ignore_alarms - -- name: Install {{ item.name }} certificate as a Trusted CA certificate - shell: >- - source /etc/platform/openrc && - system certificate-install -m ssl_ca {{ local_ca_file.path }} - register: install_cert_output - until: install_cert_output is not failed - retries: 3 - delay: 60 - -- name: Register if a new certificate was installed - set_fact: - new_cert_installed: "{{ true if (install_cert_output is search('uuid') and - install_cert_output is search('certtype') and - install_cert_output is search('signature') and - install_cert_output is search('start_date') and - install_cert_output is search('expiry_date') and - install_cert_output is search('subject')) - else false }}" - -- name: Delete temporary .pem files - file: - path: "{{ file_item }}" - state: absent - with_items: - - "{{ local_ca_file.path }}" - - "{{ root_ca_file.path }}" - loop_control: - loop_var: file_item - become: yes - -# If a new trusted CA is installed, sysinv conductor will apply -# platform::config::runtime puppet manifest. This will cause the -# current config to change. We will monitor it through the file -# .config_applied, which is created/changed when a manifest is -# applied. -- name: Wait while the new config is applied by puppet - block: - - name: Initialize fail control variable - set_fact: - puppet_config_apply_failed: false - - # If the .config_applied file doesn't exist, we wait for it to be created. - - block: - - name: Wait for .config_applied file to be created - stat: - path: /etc/platform/.config_applied - register: current_config_applied_stat - until: current_config_applied_stat.stat.exists - retries: 10 - delay: 20 - failed_when: false - - - name: Set fail control variable - set_fact: - puppet_config_apply_failed: true - when: not current_config_applied_stat.stat.exists - when: not prev_config_applied_stat.stat.exists - - # If the .config_applied file exists, we wait for it to change. - - block: - - name: Wait for .config_applied file stat to change - stat: - path: /etc/platform/.config_applied - register: current_config_applied_stat - until: current_config_applied_stat.stat.checksum != prev_config_applied_stat.stat.checksum - retries: 10 - delay: 20 - failed_when: false - - - name: Set fail control variable - set_fact: - puppet_config_apply_failed: true - when: current_config_applied_stat.stat.checksum == prev_config_applied_stat.stat.checksum - when: prev_config_applied_stat.stat.exists - - - name: Fail when the manifest apply times out - fail: - msg: >- - Timed out applying puppet runtime manifest. Check sysinv and puppet logs - for more information and solve any 250.001 alarms before retrying. - when: puppet_config_apply_failed - - - name: Check if an 250.001 alarm was raised and wait it to be cleared - shell: | - source /etc/platform/openrc; - fm alarm-list --query alarm_id=250.001 - register: alarm_subcloud - retries: 5 - delay: 20 - until: alarm_subcloud.stdout == "" - failed_when: false - - - name: Fail when the alarm remains - fail: - msg: >- - Timed out waiting 250.001 alarm to clear out. Check sysinv and puppet logs - for more information and solve any 250.001 alarms before retrying. - when: alarm_subcloud.stdout != "" - when: new_cert_installed and not ignore_alarms diff --git a/playbookconfig/src/playbooks/roles/common/send-ca-cert-to-subcloud/tasks/main.yml b/playbookconfig/src/playbooks/roles/common/send-ca-cert-to-subcloud/tasks/main.yml index 7f10d69eb..f4d76fab8 100644 --- a/playbookconfig/src/playbooks/roles/common/send-ca-cert-to-subcloud/tasks/main.yml +++ b/playbookconfig/src/playbooks/roles/common/send-ca-cert-to-subcloud/tasks/main.yml @@ -152,9 +152,7 @@ - block: - name: Install new CA certificate (Subcloud) include_role: - name: common/install-trusted-ca - with_items: - - { name: system_root_ca_cert, content: "{{ system_root_ca_cert }}" } - loop_control: - label: "{{ item.name }}" + name: common/verify-and-install-system-local-ca-certs + vars: + - install_rca: true when: not ca_send_from_bootstrap diff --git a/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/cert-content-requirements-verification.yml b/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/cert-content-requirements-verification.yml new file mode 100644 index 000000000..5322d9793 --- /dev/null +++ b/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/cert-content-requirements-verification.yml @@ -0,0 +1,57 @@ +--- +# +# Copyright (c) 2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Task that receives an RCA/ICA certs and verifies the requirements +# for it to be used as local issuer for the platform certificates. +# + +- name: Fail if pem stream is undefined + fail: + msg: Variable cert_req_pem_stream must be defined for this task. + when: cert_req_pem_stream is not defined + +- name: Get CA information from certificate + shell: | + echo "{{ cert_req_pem_stream }}" | openssl x509 -text -noout | grep "CA:" + register: is_ca + +- name: Fail when certificate is not a CA certificate + fail: + msg: One of the certificates provided for system-local-ca is not a CA certificate. + when: "'CA:TRUE' not in is_ca.stdout" + +- name: Verify if certificate is a root certificate + shell: >- + openssl verify -verbose -no-CApath -CAfile + <(echo "{{ cert_req_pem_stream }}") <(echo "{{ cert_req_pem_stream }}") + register: openssl_return + failed_when: false + +- name: Assume RCA if verification succeeds + set_fact: + ca_duration: "{{ rca_duration if rca_duration is defined else def_rca_duration }}" + when: openssl_return.rc == 0 + +- name: Assume ICA if verification fails + set_fact: + ca_duration: "{{ ica_duration if ica_duration is defined else def_ica_duration }}" + when: openssl_return.rc != 0 + +- name: Check that CA certificate remaining duration is longer than {{ ca_duration }} years + shell: | + expiration_date=$(echo "{{ cert_req_pem_stream }}" | openssl x509 -noout -enddate | cut -d'=' -f2) + expiration_date_timestamp=$(date -d "${expiration_date}" +%s) + min_date_from_now_timestamp=$(date -d "+{{ ca_duration }} years" +%s) + time_left_ca=$(expr $expiration_date_timestamp - $min_date_from_now_timestamp) + echo $time_left_ca + register: ca_time_left + +- name: Fail when CA certificate remaining duration is shorter than {{ ca_duration }} years + fail: + msg: >- + The remaining duration for one of the certificates specified for system-local-ca + is less than {{ ca_duration }} years. Please use a certificate with a longer validity. + when: ca_time_left.stdout | int < 0 diff --git a/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/ica-individual-verification.yml b/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/ica-individual-verification.yml new file mode 100644 index 000000000..172c89ce0 --- /dev/null +++ b/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/ica-individual-verification.yml @@ -0,0 +1,68 @@ +--- +# +# Copyright (c) 2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Subtask designed to be executed in a loop for verifying an ICA +# bundle looking at each individual certificate. +# Assumes that used variable are set in the calling task. +# + +- name: Retrieve the certificate in the end of the chain (first read from the file) + command: openssl x509 -in "{{ aux_ca_file.path }}" + register: ica_pem_cert + +- name: Get a stream from the auxiliar file without the certificate being read + shell: >- + { openssl x509 >/dev/null && cat; } < "{{ aux_ca_file.path }}" + register: cert_stream + become: yes + +- name: Overwrite auxiliar file with remaining ICAs (minus cert currently being checked) + copy: + content: "{{ cert_stream.stdout }}" + dest: "{{ aux_ca_file.path }}" + mode: 0640 + owner: root + group: root + become: yes + +- name: Assign ICA stream to variable + set_fact: + cert_req_pem_stream: "{{ ica_pem_cert.stdout }}" + +- name: Verify ICA certificate content requirements + include_tasks: cert-content-requirements-verification.yml + +- block: + - name: Verify if the ICA is signed by the next CA in the bundle + shell: >- + openssl verify -verbose -no-CApath -no-CAfile -partial_chain -trusted + "{{ aux_ca_file.path }}" <(echo "{{ cert_req_pem_stream }}") + register: openssl_return + failed_when: false + + - name: Fail the ICA certificate isn't signed by the next CA in the bundle + fail: + msg: >- + Failure while verifying system_local_ca ICA chain. Verify the certificates in the + files provided. Error "{{ openssl_return.rc }}": "{{ openssl_return.stdout }}". + when: openssl_return.rc !=0 + when: ica_bundle_cert_remaining | int > 0 + +- block: + - name: Verify if the last certificate is signed by the RCA + shell: >- + openssl verify -verbose -no-CApath -CAfile + "{{ system_local_ca_rca.path }}" <(echo "{{ cert_req_pem_stream }}") + register: openssl_return + failed_when: false + + - name: Fail the certificate isn't signed by the RCA + fail: + msg: >- + Failure while verifying system_local_ca RCA/ICA chain. Verify the certificates + in the files provided. Error "{{ openssl_return.rc }}": "{{ openssl_return.stdout }}". + when: openssl_return.rc !=0 + when: ica_bundle_cert_remaining | int == 0 diff --git a/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/main.yml b/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/main.yml new file mode 100644 index 000000000..cfabdf55c --- /dev/null +++ b/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/main.yml @@ -0,0 +1,209 @@ +--- +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +# These tasks provide the functionality to validate CA certificates and +# to install Root CA certificates for the local platform issuer: +# system_root_ca_cert - RCA +# system_local_ca_cert - ICA issued by 'system_root_ca_cert' +# install_rca - If true, RCA is installed as trusted +# enforce_ica - If true, a RCA in the top of the ICA file is not allowed + +- name: Verify if system_local_ca_cert and system_local_ca_key match + shell: >- + diff <(echo "{{ system_local_ca_cert | b64decode }}" | openssl x509 -noout -modulus) + <(echo "{{ system_local_ca_key | b64decode }}" | openssl rsa -noout -modulus) + register: diff_return + failed_when: false + +- name: Fail if system_local_ca_cert and system_local_ca_key doesn't match + fail: + msg: system_local_ca_cert and system_local_ca_key should be a TLS cert/key pair. + when: diff_return.stdout != "" + +- name: Create local CA pem temporary file + tempfile: + state: file + prefix: ca_ + suffix: .pem + path: /tmp/ + register: local_ca_file + +- name: Create root CA pem temporary file + tempfile: + state: file + prefix: root_ + suffix: .pem + path: /tmp/ + register: root_ca_file + +- name: Save system_local_ca_cert certificate to a file + copy: + dest: "{{ local_ca_file.path }}" + content: "{{ system_local_ca_cert | b64decode }}" + mode: 0640 + owner: root + group: root + become: yes + +- name: Save system_root_ca_cert to a file + copy: + dest: "{{ root_ca_file.path }}" + content: "{{ system_root_ca_cert | b64decode }}" + mode: 0640 + owner: root + group: root + become: yes + +- name: Set temporary files to required variables + set_fact: + system_local_ca_rca: "{{ root_ca_file }}" + system_local_ca_ica: "{{ local_ca_file }}" + +- name: Verify RCA/ICA certificates + include_tasks: verify-system-local-ca-certificates.yml + +- name: Install RCA if required + block: + # ignore_alarms flag can be set to avoid waiting. Defaults to false. + - name: Initialize flag ignore_alarms + set_fact: + ignore_alarms: "{{ false if ignore_alarms is not defined else ignore_alarms | bool }}" + + - name: Verify if there are 250.001 (config out-of-date) alarms before installing certificate + block: + - name: Check if an 250.001 alarm exists and wait it to be cleared + shell: | + source /etc/platform/openrc; + fm alarm-list --query alarm_id=250.001 + register: alarm_subcloud + retries: 10 + delay: 20 + until: alarm_subcloud.stdout == "" + failed_when: false + + - name: Fail when the alarm remains + fail: + msg: >- + Timed out waiting 250.001 alarm to clear out. + when: alarm_subcloud.stdout != "" + + - name: Register stat of .config_applied file + stat: + path: /etc/platform/.config_applied + register: prev_config_applied_stat + when: not ignore_alarms + + - name: Install RCA as Trusted CA + block: + - name: Install system_root_ca_cert certificate as a Trusted CA certificate + shell: >- + source /etc/platform/openrc && + system certificate-install -m ssl_ca "{{ root_ca_file.path }}" + register: install_cert_output + until: install_cert_output is not failed + retries: 3 + delay: 60 + + - name: Register if a new certificate was installed + set_fact: + new_cert_installed: "{{ true if (install_cert_output is search('uuid') and + install_cert_output is search('certtype') and + install_cert_output is search('signature') and + install_cert_output is search('start_date') and + install_cert_output is search('expiry_date') and + install_cert_output is search('subject')) + else false }}" + error_certs_not_installed: "{{ true if install_cert_output is + search('WARNING: Some certificates were not installed.') + else false }}" + + - name: Fail if some certificates aren't installed + fail: + msg: "{{ install_cert_output.stdout }}" + when: error_certs_not_installed + + when: install_rca + +- name: Delete temporary .pem files + file: + path: "{{ file_item }}" + state: absent + with_items: + - "{{ local_ca_file.path }}" + - "{{ root_ca_file.path }}" + loop_control: + loop_var: file_item + become: yes + +# If a new trusted CA is installed, sysinv conductor will apply +# platform::config::runtime puppet manifest. This will cause the +# current config to change. We will monitor it through the file +# .config_applied, which is created/changed when a manifest is +# applied. +- name: Wait while the new config is applied by puppet + block: + - name: Initialize fail control variable + set_fact: + puppet_config_apply_failed: false + + # If the .config_applied file doesn't exist, we wait for it to be created. + - block: + - name: Wait for .config_applied file to be created + stat: + path: /etc/platform/.config_applied + register: current_config_applied_stat + until: current_config_applied_stat.stat.exists + retries: 10 + delay: 20 + failed_when: false + + - name: Set fail control variable + set_fact: + puppet_config_apply_failed: true + when: not current_config_applied_stat.stat.exists + when: not prev_config_applied_stat.stat.exists + + # If the .config_applied file exists, we wait for it to change. + - block: + - name: Wait for .config_applied file stat to change + stat: + path: /etc/platform/.config_applied + register: current_config_applied_stat + until: current_config_applied_stat.stat.checksum != prev_config_applied_stat.stat.checksum + retries: 10 + delay: 20 + failed_when: false + + - name: Set fail control variable + set_fact: + puppet_config_apply_failed: true + when: current_config_applied_stat.stat.checksum == prev_config_applied_stat.stat.checksum + when: prev_config_applied_stat.stat.exists + + - name: Fail when the manifest apply times out + fail: + msg: >- + Timed out applying puppet runtime manifest. Check sysinv and puppet logs + for more information and solve any 250.001 alarms before retrying. + when: puppet_config_apply_failed + + - name: Check if an 250.001 alarm was raised and wait it to be cleared + shell: | + source /etc/platform/openrc; + fm alarm-list --query alarm_id=250.001 + register: alarm_subcloud + retries: 5 + delay: 20 + until: alarm_subcloud.stdout == "" + failed_when: false + + - name: Fail when the alarm remains + fail: + msg: >- + Timed out waiting 250.001 alarm to clear out. Check sysinv and puppet logs + for more information and solve any 250.001 alarms before retrying. + when: alarm_subcloud.stdout != "" + when: new_cert_installed and not ignore_alarms diff --git a/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/verify-system-local-ca-certificates.yml b/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/verify-system-local-ca-certificates.yml new file mode 100644 index 000000000..89da981f5 --- /dev/null +++ b/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/tasks/verify-system-local-ca-certificates.yml @@ -0,0 +1,114 @@ +--- +# +# Copyright (c) 2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Task that receives an ICA bundle file and RCA file and verifies the +# chain consistency and other requirements for it to be used as local +# issuer for the platform certificates, such as certificate duration. +# Optionally, the RCA can be used as RCA and ICA bundle (for test lab +# scenarios). +# + +- name: Retrieve the number of certificates in RCA file + shell: expr $(openssl storeutl -noout -certs "{{ system_local_ca_rca.path }}" | wc -l) - 1 + register: rca_certs_number + +- name: Fail if there isn't exactly one certificate in the RCA file + fail: + msg: >- + RCA file for system_local_ca should contain only the CA certificate at the + top of the trusted chain (Root CA). + when: rca_certs_number.stdout | int != 1 + +- name: Get RCA pem contents + command: openssl x509 -in "{{ system_local_ca_rca.path }}" + register: rca_pem_cert + +- name: Set variable with RCA content + set_fact: + cert_req_pem_stream: "{{ rca_pem_cert.stdout }}" + +- name: Verify RCA certificate content requirements + include_tasks: cert-content-requirements-verification.yml + +- name: Verify if certificate is RCA + shell: >- + openssl verify -verbose -no-CApath -CAfile + <(echo "{{ cert_req_pem_stream }}") <(echo "{{ cert_req_pem_stream }}") + register: openssl_return + failed_when: false + +- name: Fail if certificate is not a RCA + fail: + msg: >- + RCA file should contain a Root CA certificate. + when: openssl_return.rc != 0 + +- name: Retrieve the number of certificates in ICA file + shell: expr $(openssl storeutl -noout -certs "{{ system_local_ca_ica.path }}" | wc -l) - 1 + register: ica_certs_number + +- name: Fail if there isn't one or more certificates in the ICA file + fail: + msg: >- + ICA file for system_local_ca should have at least one CA certificate. + when: ica_certs_number.stdout | int == 0 + +- name: Create pem temporary file for manipulating the certificates + tempfile: + state: file + prefix: bundle_ + suffix: .pem + path: /tmp/ + register: aux_ca_file + +- name: Copy ICA file contents to auxiliar file + copy: + src: "{{ system_local_ca_ica.path }}" + dest: "{{ aux_ca_file.path }}" + mode: 0640 + owner: root + group: root + become: yes + +- name: Verification for ICA enforced mode + block: + - name: Retrieve the certificate in the end of the ICA chain (first read from the file) + command: openssl x509 -in "{{ aux_ca_file.path }}" + register: ica_pem_cert + + - name: Assign ICA stream to variable + set_fact: + cert_pem_stream: "{{ ica_pem_cert.stdout }}" + + - name: Verify if certificate is a RCA + shell: >- + openssl verify -verbose -no-CApath -CAfile + <(echo "{{ cert_pem_stream }}") <(echo "{{ cert_pem_stream }}") + register: openssl_return + failed_when: false + + - name: Fail if certificate is a RCA + fail: + msg: >- + ICA file must contain a proper Intermediate CA certificate (not a Root CA). + when: openssl_return.rc == 0 + when: enforce_ica + +- name: Set counter for ICA bundle verification + set_fact: + ica_cert_loop_executions: "{{ ica_certs_number.stdout | int }}" + +- name: Loop over the ICA bundle to verify the certificates + include_tasks: ica-individual-verification.yml + with_sequence: start="{{ ica_cert_loop_executions | int - 1 }}" end=0 stride=-1 + loop_control: + loop_var: ica_bundle_cert_remaining + +- name: Remove the auxiliar file + file: + path: "{{ aux_ca_file.path }}" + state: absent + become: yes diff --git a/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/vars/main.yml b/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/vars/main.yml new file mode 100644 index 000000000..b116fb8fa --- /dev/null +++ b/playbookconfig/src/playbooks/roles/common/verify-and-install-system-local-ca-certs/vars/main.yml @@ -0,0 +1,13 @@ +--- +# +# Copyright (c) 2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +install_rca: false +enforce_ica: false +new_cert_installed: false +error_certs_not_installed: false +def_rca_duration: 3 +def_ica_duration: 3 diff --git a/playbookconfig/src/playbooks/roles/migrate-platform-certificates-to-certmanager/migrate-certificates/tasks/main.yml b/playbookconfig/src/playbooks/roles/migrate-platform-certificates-to-certmanager/migrate-certificates/tasks/main.yml index 5de7f0780..22664ddd3 100644 --- a/playbookconfig/src/playbooks/roles/migrate-platform-certificates-to-certmanager/migrate-certificates/tasks/main.yml +++ b/playbookconfig/src/playbooks/roles/migrate-platform-certificates-to-certmanager/migrate-certificates/tasks/main.yml @@ -142,12 +142,9 @@ - name: Install certificates as system Trusted CA certificates include_role: - name: common/install-trusted-ca - with_items: - - { name: system_local_ca_cert, content: "{{ system_local_ca_cert }}" } - - { name: system_root_ca_cert, content: "{{ system_root_ca_cert }}" } - loop_control: - label: "{{ item.name }}" + name: common/verify-and-install-system-local-ca-certs + vars: + - install_rca: true - name: Restart kube-apiserver to pick the new certificates include_role: