From 1c665128d52d2ca1966587a94692ef406d232e1a Mon Sep 17 00:00:00 2001 From: Jim Enright Date: Thu, 29 Aug 2024 20:34:06 +0100 Subject: [PATCH] Add roles and example playbook for PVC cert renewal (#189) * Add roles and example playbook for PVC cert renewal * Add documentation for TLS roles * Fix lint issues * Remove tasks and variables used to create openssl.cnf Signed-off-by: Jim Enright --- playbooks/pvc_renew_certs.yml | 128 ++++++++++++++++++ roles/tls_fetch_ca_certs/README.md | 17 +++ roles/tls_fetch_ca_certs/defaults/main.yml | 22 +++ .../meta/argument_specs.yml | 44 ++++++ roles/tls_fetch_ca_certs/tasks/main.yml | 26 ++++ roles/tls_generate_csr/README.md | 17 +++ roles/tls_generate_csr/defaults/main.yml | 33 +++++ .../tls_generate_csr/meta/argument_specs.yml | 57 ++++++++ roles/tls_generate_csr/tasks/main.yml | 60 ++++++++ .../templates/certificate_dn.j2 | 20 +++ roles/tls_install_certs/README.md | 17 +++ roles/tls_install_certs/defaults/main.yml | 31 +++++ .../tls_install_certs/meta/argument_specs.yml | 65 +++++++++ roles/tls_install_certs/tasks/main.yml | 90 ++++++++++++ roles/tls_signing/README.md | 17 +++ roles/tls_signing/defaults/main.yml | 29 ++++ roles/tls_signing/meta/argument_specs.yml | 56 ++++++++ roles/tls_signing/tasks/main.yml | 55 ++++++++ 18 files changed, 784 insertions(+) create mode 100644 playbooks/pvc_renew_certs.yml create mode 100644 roles/tls_fetch_ca_certs/README.md create mode 100644 roles/tls_fetch_ca_certs/defaults/main.yml create mode 100644 roles/tls_fetch_ca_certs/meta/argument_specs.yml create mode 100644 roles/tls_fetch_ca_certs/tasks/main.yml create mode 100644 roles/tls_generate_csr/README.md create mode 100644 roles/tls_generate_csr/defaults/main.yml create mode 100644 roles/tls_generate_csr/meta/argument_specs.yml create mode 100644 roles/tls_generate_csr/tasks/main.yml create mode 100644 roles/tls_generate_csr/templates/certificate_dn.j2 create mode 100644 roles/tls_install_certs/README.md create mode 100644 roles/tls_install_certs/defaults/main.yml create mode 100644 roles/tls_install_certs/meta/argument_specs.yml create mode 100644 roles/tls_install_certs/tasks/main.yml create mode 100644 roles/tls_signing/README.md create mode 100644 roles/tls_signing/defaults/main.yml create mode 100644 roles/tls_signing/meta/argument_specs.yml create mode 100644 roles/tls_signing/tasks/main.yml diff --git a/playbooks/pvc_renew_certs.yml b/playbooks/pvc_renew_certs.yml new file mode 100644 index 00000000..fe1476d6 --- /dev/null +++ b/playbooks/pvc_renew_certs.yml @@ -0,0 +1,128 @@ +--- + +# Copyright 2024 Cloudera, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- name: Pre - Initialization of local working directories + hosts: localhost + connection: local + tasks: + - name: Create temporary build directory + ansible.builtin.tempfile: + state: directory + prefix: pvc_tls_ + register: __pvc_tls_tempdir + + - name: Create a directory for csrs and signed certs + ansible.builtin.file: + path: "{{ [__pvc_tls_tempdir.path, item] | path_join }}" + state: directory + mode: '0755' + loop: + - csrs + - certs + - ca_certs + +- name: Play 1 - Generate CSR on each host + hosts: "{{ target | default('cluster') }}" + become: yes + gather_facts: yes + tasks: + + - name: Call tls_generate_csr role + ansible.builtin.import_role: + name: cloudera.exe.tls_generate_csr + vars: + local_csrs_dir: "{{ (hostvars['localhost']['__pvc_tls_tempdir']['path'], 'csrs') | path_join }}" + +- name: Get the list of CSRs to sign + hosts: localhost + connection: local + tasks: + - name: "Set fact for all CSRs in {{ local_csrs_dir }}" + ansible.builtin.find: + paths: "{{ local_csrs_dir }}" + file_type: file + register: __csrs_to_sign + vars: + local_csrs_dir: "{{ (hostvars['localhost']['__pvc_tls_tempdir']['path'], 'csrs') | path_join }}" + + - name: Set fact for csrs to sign + ansible.builtin.set_fact: + local_csrs_to_sign: "{{ __csrs_to_sign.files | json_query('[*].path') | flatten }}" + +- name: Play 2 - Sign the CSR + hosts: ca_server + become: yes + gather_facts: yes + tasks: + + - name: Call tls_signing role + ansible.builtin.import_role: + name: cloudera.exe.tls_signing + vars: + csrs_to_sign: "{{ hostvars['localhost']['local_csrs_to_sign'] }}" + copy_from_controller: true + local_certs_dir: "{{ (hostvars['localhost']['__pvc_tls_tempdir']['path'], 'certs') | path_join }}" + +- name: Play 3 - Install the sign certs on each host + hosts: "{{ target | default('cluster') }}" + become: yes + gather_facts: yes + tasks: + + - name: Call tls_install_certs role + ansible.builtin.import_role: + name: cloudera.exe.tls_install_certs + vars: + local_tls_signed_certs_dir: "{{ (hostvars['localhost']['__pvc_tls_tempdir']['path'], 'certs') | path_join }}" + +- name: Post 1 - Restart CM Server service + hosts: cloudera_manager + become: yes + gather_facts: yes + tasks: + + - name: Restart CM Server service + when: + - restart_services | default(False) + ansible.builtin.service: + name: cloudera-scm-server + state: restarted + +- name: Post 2 - Restart DB Server service + hosts: db_server + become: yes + gather_facts: yes + tasks: + + - name: Restart DB Server service + when: + - restart_services | default(False) + ansible.builtin.service: + name: "{{ db_service_name }}" + state: reloaded + +- name: Post 3 - Restart CM Agent service + hosts: cluster + become: yes + gather_facts: yes + tasks: + + - name: Restart CM Agent service + when: + - restart_services | default(False) + ansible.builtin.service: + name: cloudera-scm-agent + state: restarted diff --git a/roles/tls_fetch_ca_certs/README.md b/roles/tls_fetch_ca_certs/README.md new file mode 100644 index 00000000..103e7c06 --- /dev/null +++ b/roles/tls_fetch_ca_certs/README.md @@ -0,0 +1,17 @@ + + +# cloudera.exe.tls_fetch_ca_certs diff --git a/roles/tls_fetch_ca_certs/defaults/main.yml b/roles/tls_fetch_ca_certs/defaults/main.yml new file mode 100644 index 00000000..015fbb85 --- /dev/null +++ b/roles/tls_fetch_ca_certs/defaults/main.yml @@ -0,0 +1,22 @@ +# Copyright 2024 Cloudera, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- + +ca_server_intermediate_path: /ca/intermediate/certs +ca_server_intermediate_cert_name: intermediate.cert.pem +ca_server_root_path: /ca/certs +ca_server_root_cert_name: ca.cert.pem + +# local_ca_certs_dir diff --git a/roles/tls_fetch_ca_certs/meta/argument_specs.yml b/roles/tls_fetch_ca_certs/meta/argument_specs.yml new file mode 100644 index 00000000..5bff0f30 --- /dev/null +++ b/roles/tls_fetch_ca_certs/meta/argument_specs.yml @@ -0,0 +1,44 @@ +# Copyright 2024 Cloudera, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- + +argument_specs: + main: + short_description: "Bring CA root and intermediate cert back to controller" + description: + - Fetch the named root and intermediate CA TLS Certificates from the CA Server. + author: + - "Jim Enright " + options: + ca_server_intermediate_path: + description: "Path to intermediate CA cert on the CA server" + default: "/ca/intermediate/certs" + type: "str" + ca_server_intermediate_cert_name: + description: "Name of the intermediate CA cert file" + type: "str" + default: "intermediate.cert.pem" + ca_server_root_path: + description: "Path to root CA cert on the CA server" + default: "/ca/certs" + type: "str" + ca_server_root_cert_name: + description: "Name of the root CA cert file" + type: "str" + default: "ca.cert.pem" + local_ca_certs_dir: + description: "Directory on Ansible controller to store the root and intermediate CA cert files" + type: "str" + required: true diff --git a/roles/tls_fetch_ca_certs/tasks/main.yml b/roles/tls_fetch_ca_certs/tasks/main.yml new file mode 100644 index 00000000..e3c14a29 --- /dev/null +++ b/roles/tls_fetch_ca_certs/tasks/main.yml @@ -0,0 +1,26 @@ +# Copyright 2024 Cloudera, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- + +- name: Bring ca root and intermediate cert back to controller + ansible.builtin.fetch: + src: "{{ item.cert_path }}/{{ item.cert_filename }}" + dest: "{{ local_ca_certs_dir }}/{{ item.cert_filename }}" + flat: yes + loop: + - cert_path: "{{ ca_server_intermediate_path }}" + cert_filename: "{{ ca_server_intermediate_cert_name }}" + - cert_path: "{{ ca_server_root_path }}" + cert_filename: "{{ ca_server_root_cert_name }}" diff --git a/roles/tls_generate_csr/README.md b/roles/tls_generate_csr/README.md new file mode 100644 index 00000000..634be215 --- /dev/null +++ b/roles/tls_generate_csr/README.md @@ -0,0 +1,17 @@ + + +# cloudera.exe.tls_generate_csr diff --git a/roles/tls_generate_csr/defaults/main.yml b/roles/tls_generate_csr/defaults/main.yml new file mode 100644 index 00000000..b7ebffb2 --- /dev/null +++ b/roles/tls_generate_csr/defaults/main.yml @@ -0,0 +1,33 @@ +# Copyright 2023 Cloudera, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +openssl_path: /usr/bin/openssl + +base_dir_security: /opt/cloudera/security +base_dir_security_pki: "{{ base_dir_security }}/pki" +tls_csr_path: "{{ base_dir_security_pki }}/{{ inventory_hostname }}.csr" + +# local_csrs_dir: "/tmp/csrs" + +ca_server_attrs_general: + OU: PS + O: Cloudera, Inc. + ST: CA + C: US + + +tls_key_password: changeme + +tls_key_path: "{{ base_dir_security_pki }}/{{ inventory_hostname }}.key" diff --git a/roles/tls_generate_csr/meta/argument_specs.yml b/roles/tls_generate_csr/meta/argument_specs.yml new file mode 100644 index 00000000..d821614b --- /dev/null +++ b/roles/tls_generate_csr/meta/argument_specs.yml @@ -0,0 +1,57 @@ +# Copyright 2024 Cloudera, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- + +argument_specs: + main: + short_description: "Generates a CSR on each host and copies it back to the Ansible controller" + description: + - Generates a TLS Certificate Signing Request (CSR). + - Once created the CSR file is copied back to the Ansibles controller. + author: + - "Jim Enright " + options: + base_dir_security: + description: "Base directory for Cloudera CDP security related files" + type: "str" + default: "/opt/cloudera/security" + base_dir_security_pki: + description: "Base directory for Cloudera CDP PKI security related files" + type: "str" + default: "{{ base_dir_security }}/pki" + tls_csr_path: + description: "Location of the OpenSSL Certificate Signing Request file that will be created by the role" + type: "str" + default: "{{ base_dir_security_pki }}/{{ inventory_hostname }}.csr" + ca_server_attrs_general: + description: "Attributes to use in the certificate signing request" + type: "dict" + default: + OU: PS + O: "Cloudera, Inc." + ST: "CA" + C: "US" + tls_key_password: + description: "Password for the TLS Key." + type: "str" + default: "changeme" + tls_key_path: + description: "Location of the TLS key." + type: "str" + default: "{{ base_dir_security_pki }}/{{ inventory_hostname }}.key" + local_csrs_dir: + description: "Location on the Ansible Controller where the CSR will be copied." + type: "str" + default: "{{ base_dir_security_pki }}/{{ inventory_hostname }}.key" diff --git a/roles/tls_generate_csr/tasks/main.yml b/roles/tls_generate_csr/tasks/main.yml new file mode 100644 index 00000000..1e45b15d --- /dev/null +++ b/roles/tls_generate_csr/tasks/main.yml @@ -0,0 +1,60 @@ +# Copyright 2024 Cloudera, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- + +- name: Prepare directories for TLS + ansible.builtin.file: + state: directory + path: "{{ dir }}" + mode: 0755 + owner: root + loop: + - "{{ base_dir_security }}" + - "{{ base_dir_security_pki }}" + loop_control: + loop_var: dir + +- name: Read subject attribute from certificate DN template file + ansible.builtin.set_fact: + subject_attr: "{{ lookup('template', 'certificate_dn.j2') | from_yaml }}" + +- name: Derive openssl subjects from subject attribute + ansible.builtin.set_fact: + openssl_subject: "{{ openssl_subject | default({}) | combine( {item.split('=')[0] : item.split('=')[1]}) }}" + loop: "{{ subject_attr }}" + +- name: Generate CSR + community.crypto.openssl_csr: + path: "{{ tls_csr_path }}" + privatekey_path: "{{ tls_key_path }}" + privatekey_passphrase: "{{ tls_key_password }}" + subject: "{{ openssl_subject }}" + backup: true + basic_constraints: + - "CA:FALSE" + keyUsage: + - "nonRepudiation" + - "digitalSignature" + - "keyEncipherment" + extendedKeyUsage: + - "serverAuth" + - "clientAuth" + subject_alt_name: "DNS:{{ inventory_hostname }}" + +- name: Bring CSRs back to controller + ansible.builtin.fetch: + src: "{{ tls_csr_path }}" + dest: "{{ local_csrs_dir }}/" + flat: yes diff --git a/roles/tls_generate_csr/templates/certificate_dn.j2 b/roles/tls_generate_csr/templates/certificate_dn.j2 new file mode 100644 index 00000000..c7d1f595 --- /dev/null +++ b/roles/tls_generate_csr/templates/certificate_dn.j2 @@ -0,0 +1,20 @@ +{% set attr = ca_server_attrs_general | default({}) | combine(ca_server_attrs_host | default({})) %} +- CN={{ cn_override | default(attr.CN | default(inventory_hostname)) }} +{% if attr.OU is defined %} +{% if attr.OU is iterable and attr.OU is not string %} +{% for ou in attr.OU %} +- OU={{ ou }} +{% endfor %} +{% else %} +- OU={{ attr.OU }} +{% endif %} +{% endif %} +{% if attr.O is defined %} +- O={{ attr.O }} +{% endif %} +{% if attr.ST is defined %} +- ST={{ attr.ST }} +{% endif %} +{% if attr.C is defined %} +- C={{ attr.C }} +{% endif %} diff --git a/roles/tls_install_certs/README.md b/roles/tls_install_certs/README.md new file mode 100644 index 00000000..a0b032bb --- /dev/null +++ b/roles/tls_install_certs/README.md @@ -0,0 +1,17 @@ + + +# clouera.exe.tls_install_certs diff --git a/roles/tls_install_certs/defaults/main.yml b/roles/tls_install_certs/defaults/main.yml new file mode 100644 index 00000000..34bbd846 --- /dev/null +++ b/roles/tls_install_certs/defaults/main.yml @@ -0,0 +1,31 @@ +# Copyright 2024 Cloudera, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- + +openssl_path: /usr/bin/openssl + +base_dir_security: /opt/cloudera/security +base_dir_security_pki: "{{ base_dir_security }}/pki" + +tls_cert_path: "{{ base_dir_security_pki }}/{{ inventory_hostname }}.pem" +tls_cert_path_generic: "{{ base_dir_security_pki }}/host.pem" + +tls_key_path_generic: "{{ base_dir_security_pki }}/host.key" +tls_key_path_plaintext_generic: "{{ tls_key_path_generic }}.unenc" + +tls_ca_chain_path: "{{ base_dir_security_pki }}/chain.pem" + +tls_keystore_path: "{{ base_dir_security_pki }}/{{ inventory_hostname }}.jks" +tls_keystore_password: changeme diff --git a/roles/tls_install_certs/meta/argument_specs.yml b/roles/tls_install_certs/meta/argument_specs.yml new file mode 100644 index 00000000..e1e9fa2b --- /dev/null +++ b/roles/tls_install_certs/meta/argument_specs.yml @@ -0,0 +1,65 @@ +# Copyright 2024 Cloudera, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- + +argument_specs: + main: + short_description: "Copy and install the signed TLS certificates to each cluster" + description: + - Copies the signed TLS cert to each cluster host. + - Updates the Java keystore with the renewed certificate + author: + - "Jim Enright " + options: + openssl_path: + description: "Absolute path to the C(openssl) executable" + default: "/usr/bin/openssl" + type: "str" + base_dir_security: + description: "Base directory for Cloudera CDP security related files" + type: "str" + default: "/opt/cloudera/security" + base_dir_security_pki: + description: "Base directory for Cloudera CDP PKI security related files" + type: "str" + default: "{{ base_dir_security }}/pki" + tls_cert_path: + description: "Location where the signed TLS certificate should be copied." + type: "str" + default: "{{ base_dir_security_pki }}/{{ inventory_hostname }}.pem" + tls_cert_path_generic: + description: "Host agnostic file name for the signed TLS certificate" + type: "str" + default: "{{ base_dir_security_pki }}/host.pem" + tls_key_path_generic: + description: "Host agnostic file name for the encrypted TLS key" + type: "str" + default: "{{ base_dir_security_pki }}/host.key" + tls_key_path_plaintext_generic: + description: "Host agnostic file name for the unencrypted TLS key" + type: "str" + default: "{{ tls_key_path_generic }}.unenc" + tls_ca_chain_path: + description: "Full path to the TLS CA chain file" + type: "str" + default: "{{ base_dir_security_pki }}/chain.pem" + tls_keystore_path: + description: "Full path to Java Keystore file" + type: "str" + default: "{{ base_dir_security_pki }}/{{ inventory_hostname }}.jks" + tls_keystore_password: + description: "Password for the Java Keystore" + type: "str" + default: "changeme" diff --git a/roles/tls_install_certs/tasks/main.yml b/roles/tls_install_certs/tasks/main.yml new file mode 100644 index 00000000..91c61e99 --- /dev/null +++ b/roles/tls_install_certs/tasks/main.yml @@ -0,0 +1,90 @@ +# Copyright 2024 Cloudera, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- + +- name: Check if signed cert is available + become: no + delegate_to: localhost + stat: + path: "{{ local_tls_signed_certs_dir }}/{{ inventory_hostname }}.pem" + register: signed_cert + +- name: Fail is the signed certificate is not available for this host + ansible.builtin.fail: + msg: > + "Signed cert for {{ inventory_hostname }} could not be found. If manual signing is + required, do this now and re-run the playbook with 'tls_signed_certs_dir' variable set. + when: + - not signed_cert.stat.exists + +- name: Copy signed certs to hosts + ansible.builtin.copy: + src: "{{ local_tls_signed_certs_dir }}/{{ inventory_hostname }}.pem" + dest: "{{ tls_cert_path }}" + mode: 0644 + +- name: Create host agnostic link for signed certificate + ansible.builtin.file: + src: "{{ tls_cert_path }}" + dest: "{{ tls_cert_path_generic }}" + state: hard + force: true + mode: 0644 + owner: root + group: root + +- name: Validate certificate + ansible.builtin.command: + cmd: "{{ openssl_path }} verify -verbose -CAfile {{ tls_ca_chain_path }} {{ tls_cert_path }}" + +# Update the keystore with the renewed cert +# Reference: https://support.smartbear.com/collaborator/faq/how-to-update-an-expired-certificate-in-the-existi/ +# ....Step 1: Create chain including signed host cert +# ....Step 2: Create PKCS12 keystore with chain and key. Alias name is {{ inventory_hostname }} +# NOTE: Step 1 & 2 are handled by the tasks below +- name: Create temporary keystore file name + ansible.builtin.tempfile: + state: file + suffix: tmp_keystore.p12 + register: __pvc_tls_temp_keystore + +- name: Generate a temporary PKCS12 keystore with renewed cert + community.crypto.openssl_pkcs12: + action: export + path: "{{ __pvc_tls_temp_keystore.path }}" + passphrase: "{{ tls_keystore_password }}" + friendly_name: "{{ keystore_alias | default(inventory_hostname) }}" + privatekey_path: "{{ tls_key_path_plaintext_generic }}" + certificate_path: "{{ tls_cert_path_generic }}" + other_certificates: + - "{{ base_dir_security_pki }}/cluster_intca.pem" + - "{{ base_dir_security_pki }}/cluster_rootca.pem" + +# ....Step 3: Update the {{ tls_keystore_path }} with the above keystore +- name: Import the temporary keystore to {{ tls_keystore_path }} + community.general.java_cert: + pkcs12_path: "{{ __pvc_tls_temp_keystore.path }}" + pkcs12_alias: "{{ keystore_alias | default(inventory_hostname) }}" + pkcs12_password: "{{ tls_keystore_password }}" + cert_alias: "{{ keystore_alias | default(inventory_hostname) }}" + keystore_path: "{{ tls_keystore_path }}" + keystore_pass: "{{ tls_keystore_password }}" + keystore_create: false + state: present + +- name: Remove the temporary keystore + ansible.builtin.file: + path: "{{ __pvc_tls_temp_keystore.path }}" + state: absent diff --git a/roles/tls_signing/README.md b/roles/tls_signing/README.md new file mode 100644 index 00000000..d0ee8105 --- /dev/null +++ b/roles/tls_signing/README.md @@ -0,0 +1,17 @@ + + +# cloudera.exe.tls_signing diff --git a/roles/tls_signing/defaults/main.yml b/roles/tls_signing/defaults/main.yml new file mode 100644 index 00000000..56bda5a9 --- /dev/null +++ b/roles/tls_signing/defaults/main.yml @@ -0,0 +1,29 @@ +# Copyright 2024 Cloudera, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- + +# csrs_to_sign: +copy_from_controller: True + +ca_server_intermediate_path: /ca/intermediate +ca_server_intermediate_private_key: "{{ ca_server_intermediate_path }}/private/intermediate.key.pem" +ca_server_intermediate_private_key_password: password + +ca_server_intermediate_path_certs: "{{ ca_server_intermediate_path }}/certs" +ca_server_intermediate_cert: "{{ ca_server_intermediate_path_certs }}/intermediate.cert.pem" + +ca_server_intermediate_path_csr: "{{ ca_server_intermediate_path }}/csr" + +backup_old_certs: True diff --git a/roles/tls_signing/meta/argument_specs.yml b/roles/tls_signing/meta/argument_specs.yml new file mode 100644 index 00000000..9b151e3f --- /dev/null +++ b/roles/tls_signing/meta/argument_specs.yml @@ -0,0 +1,56 @@ +# Copyright 2024 Cloudera, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- + +argument_specs: + main: + short_description: "Sign of CSRs by a CA Server" + description: + - Generates a signed TLS certificate from a specified list of Certificate Signing Requests (CSRs). + - The role will optionally override and backup existing certs of the same name if required. + - Upon completion the signed certs are copied back to the Ansible controller. + author: + - "Jim Enright " + options: + csrs_to_sign: + description: "List of full path locations of the CSRs to sign." + type: "list" + elements: "str" + required: true + copy_from_controller: + description: "Flag to specify if the CSRs should be copied from the Ansible controller." + type: "bool" + default: true + backup_old_certs: + description: + - Flag to specify if existing signed certs of the same name should be backed up. + type: "bool" + default: true + ca_server_intermediate_path: + description: Common base directory for all intermediate CA resources + type: "str" + default: "/ca/intermediate" + ca_server_intermediate_path_certs: + description: Path to intermediate CA certificates directory + type: "str" + default: "{{ ca_server_intermediate_path }}/certs" + ca_server_intermediate_path_csr: + description: Path to intermediate CA CSR directory + type: "str" + default: "{{ ca_server_intermediate_path }}/csr" + ca_server_intermediate_key_password: + description: Password for the intermediate CA TLS key + type: "str" + default: "password" diff --git a/roles/tls_signing/tasks/main.yml b/roles/tls_signing/tasks/main.yml new file mode 100644 index 00000000..66887ec4 --- /dev/null +++ b/roles/tls_signing/tasks/main.yml @@ -0,0 +1,55 @@ +# Copyright 2024 Cloudera, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- + +# TODO: Check index.txt.attr and see if unique_subject = no for intermediate ca + +- name: Set fact for all CSRs to sign + ansible.builtin.set_fact: + __csrs_to_sign: "{{ (__csrs_to_sign | default([])) + ([csr_item]) }}" + vars: + csr_item: + local_file: "{{ item }}" + csr_filename: "{{ item | basename }}" + file_suffix: "{{ item | basename | splitext | first }}" + loop: "{{ csrs_to_sign }}" + +- name: Copy all CSRs from controller to CA server + when: copy_from_controller + ansible.builtin.copy: + src: "{{ item.local_file }}" + dest: "{{ ca_server_intermediate_path_csr }}/{{ item.csr_filename }}" + mode: 0644 + loop: "{{ __csrs_to_sign }}" + +- name: Sign CSRs + community.crypto.x509_certificate: + path: "{{ ca_server_intermediate_path_certs }}/{{ item.file_suffix }}.pem" + csr_path: "{{ ca_server_intermediate_path_csr }}/{{ item.csr_filename }}" + backup: "{{ backup_old_certs }}" + provider: ownca + ownca_path: "{{ ca_server_intermediate_cert }}" + ownca_privatekey_path: "{{ ca_server_intermediate_private_key }}" + ownca_privatekey_passphrase: "{{ ca_server_intermediate_private_key_password }}" + selfsigned_not_after: "+730d" + loop: "{{ __csrs_to_sign }}" + register: __signed_certs + +- name: Bring signed certs back to controller + ansible.builtin.fetch: + src: "{{ ca_server_intermediate_path_certs }}/{{ item.file_suffix }}.pem" + dest: "{{ local_certs_dir }}/{{ item.file_suffix }}.pem" + flat: yes + loop: "{{ __csrs_to_sign }}"