diff --git a/roles/security/tls_presigned_certs/defaults/main.yml b/roles/security/tls_presigned_certs/defaults/main.yml new file mode 100644 index 00000000..f53c2f01 --- /dev/null +++ b/roles/security/tls_presigned_certs/defaults/main.yml @@ -0,0 +1,18 @@ +# 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. + +--- + +keytool_path: /usr/bin/keytool +openssl_path: /usr/bin/openssl diff --git a/roles/security/tls_presigned_certs/meta/main.yml b/roles/security/tls_presigned_certs/meta/main.yml new file mode 100644 index 00000000..3b157c54 --- /dev/null +++ b/roles/security/tls_presigned_certs/meta/main.yml @@ -0,0 +1,18 @@ +# 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. + +--- +dependencies: + - role: cloudera.cluster.infrastructure.ca_common + - role: cloudera.cluster.prereqs.local_accounts_common \ No newline at end of file diff --git a/roles/security/tls_presigned_certs/tasks/acls-ecs.yml b/roles/security/tls_presigned_certs/tasks/acls-ecs.yml new file mode 100644 index 00000000..72d7b47f --- /dev/null +++ b/roles/security/tls_presigned_certs/tasks/acls-ecs.yml @@ -0,0 +1,158 @@ +# Copyright 2023 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: Install acls package + ansible.builtin.package: + lock_timeout: "{{ (ansible_os_family == 'RedHat') | ternary(60, omit) }}" + name: acl + state: present + +- name: Change permissions on keystore + file: + state: file + path: "{{ tls_keystore_path }}" + mode: 0640 + owner: root + group: hadoop + +- name: Add ACLs to keystore + acl: + path: "{{ tls_keystore_path }}" + entity: "{{ account.user }}" + etype: group + permissions: r + state: present + loop: "{{ ecs_accounts | json_query('[?@.keystore_acl]') }}" + loop_control: + loop_var: account + label: "{{ account.user }}" + when: account.when | default(True) + +- name: Change permissions on keystore hard link + file: + state: file + path: "{{ tls_keystore_path_generic }}" + mode: 0640 + owner: root + group: hadoop + +- name: Add ACLs to keystore hard link + acl: + path: "{{ tls_keystore_path_generic }}" + entity: "{{ account.user }}" + etype: group + permissions: r + state: present + loop: "{{ ecs_accounts | json_query('[?@.keystore_acl]') }}" + loop_control: + loop_var: account + label: "{{ account.user }}" + when: account.when | default(True) + +- name: Change permissions on private key + file: + state: file + path: "{{ item }}" + mode: 0440 + owner: root + group: root + loop: + - "{{ tls_key_path }}" + - "{{ tls_key_path_generic }}" + +- name: Add ACLs to private key + acl: + path: "{{ tls_key_path }}" + entity: "{{ account.user }}" + etype: group + permissions: r + state: present + loop: "{{ ecs_accounts | json_query('[?@.key_acl]') }}" + loop_control: + loop_var: account + label: "{{ account.user }}" + when: account.when | default(True) + +- name: Add ACLs to private key hard link + acl: + path: "{{ tls_key_path_generic }}" + entity: "{{ account.user }}" + etype: group + permissions: r + state: present + loop: "{{ ecs_accounts | json_query('[?@.key_acl]') }}" + loop_control: + loop_var: account + label: "{{ account.user }}" + when: account.when | default(True) + +- name: Change permissions on key password file + file: + state: file + path: "{{ tls_key_password_file }}" + mode: 0440 + owner: root + group: root + +- name: Add ACLs to key password file + acl: + path: "{{ tls_key_password_file }}" + entity: "{{ account.user }}" + etype: user + permissions: r + state: present + loop: "{{ ecs_accounts | json_query('[?@.key_password_acl]') }}" + loop_control: + loop_var: account + label: "{{ account.user }}" + when: account.when | default(True) + +- name: Change permissions on unencrypted key + file: + state: file + path: "{{ item }}" + mode: 0440 + owner: root + group: root + loop: + - "{{ tls_key_path_plaintext }}" + - "{{ tls_key_path_plaintext_generic }}" + +- name: Add ACLs to unencrypted key + acl: + path: "{{ tls_key_path_plaintext }}" + entity: "{{ account.user }}" + etype: group + permissions: r + state: present + loop: "{{ local_accounts | json_query('[?@.unencrypted_key_acl]') }}" + loop_control: + loop_var: account + label: "{{ account.user }}" + when: account.when | default(True) + +- name: Add ACLs to unencrypted key hard link + acl: + path: "{{ tls_key_path_plaintext_generic }}" + entity: "{{ account.user }}" + etype: group + permissions: r + state: present + loop: "{{ ecs_accounts | json_query('[?@.unencrypted_key_acl]') }}" + loop_control: + loop_var: account + label: "{{ account.user }}" + when: account.when | default(True) diff --git a/roles/security/tls_presigned_certs/tasks/acls.yml b/roles/security/tls_presigned_certs/tasks/acls.yml new file mode 100644 index 00000000..d853471c --- /dev/null +++ b/roles/security/tls_presigned_certs/tasks/acls.yml @@ -0,0 +1,158 @@ +# 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. + +--- + +- name: Install acls package + ansible.builtin.package: + lock_timeout: "{{ (ansible_os_family == 'RedHat') | ternary(60, omit) }}" + name: acl + state: present + +- name: Change permissions on keystore + file: + state: file + path: "{{ tls_keystore_path }}" + mode: 0640 + owner: root + group: hadoop + +- name: Add ACLs to keystore + acl: + path: "{{ tls_keystore_path }}" + entity: "{{ account.user }}" + etype: group + permissions: r + state: present + loop: "{{ local_accounts | json_query('[?@.keystore_acl]') }}" + loop_control: + loop_var: account + label: "{{ account.user }}" + when: account.when | default(True) + +- name: Change permissions on keystore hard link + file: + state: file + path: "{{ tls_keystore_path_generic }}" + mode: 0640 + owner: root + group: hadoop + +- name: Add ACLs to keystore hard link + acl: + path: "{{ tls_keystore_path_generic }}" + entity: "{{ account.user }}" + etype: group + permissions: r + state: present + loop: "{{ local_accounts | json_query('[?@.keystore_acl]') }}" + loop_control: + loop_var: account + label: "{{ account.user }}" + when: account.when | default(True) + +- name: Change permissions on private key + file: + state: file + path: "{{ item }}" + mode: 0440 + owner: root + group: root + loop: + - "{{ tls_key_path }}" + - "{{ tls_key_path_generic }}" + +- name: Add ACLs to private key + acl: + path: "{{ tls_key_path }}" + entity: "{{ account.user }}" + etype: group + permissions: r + state: present + loop: "{{ local_accounts | json_query('[?@.key_acl]') }}" + loop_control: + loop_var: account + label: "{{ account.user }}" + when: account.when | default(True) + +- name: Add ACLs to private key hard link + acl: + path: "{{ tls_key_path_generic }}" + entity: "{{ account.user }}" + etype: group + permissions: r + state: present + loop: "{{ local_accounts | json_query('[?@.key_acl]') }}" + loop_control: + loop_var: account + label: "{{ account.user }}" + when: account.when | default(True) + +- name: Change permissions on key password file + file: + state: file + path: "{{ tls_key_password_file }}" + mode: 0440 + owner: root + group: root + +- name: Add ACLs to key password file + acl: + path: "{{ tls_key_password_file }}" + entity: "{{ account.user }}" + etype: user + permissions: r + state: present + loop: "{{ local_accounts | json_query('[?@.key_password_acl]') }}" + loop_control: + loop_var: account + label: "{{ account.user }}" + when: account.when | default(True) + +- name: Change permissions on unencrypted key + file: + state: file + path: "{{ item }}" + mode: 0440 + owner: root + group: root + loop: + - "{{ tls_key_path_plaintext }}" + - "{{ tls_key_path_plaintext_generic }}" + +- name: Add ACLs to unencrypted key + acl: + path: "{{ tls_key_path_plaintext }}" + entity: "{{ account.user }}" + etype: group + permissions: r + state: present + loop: "{{ local_accounts | json_query('[?@.unencrypted_key_acl]') }}" + loop_control: + loop_var: account + label: "{{ account.user }}" + when: account.when | default(True) + +- name: Add ACLs to unencrypted key hard link + acl: + path: "{{ tls_key_path_plaintext_generic }}" + entity: "{{ account.user }}" + etype: group + permissions: r + state: present + loop: "{{ local_accounts | json_query('[?@.unencrypted_key_acl]') }}" + loop_control: + loop_var: account + label: "{{ account.user }}" + when: account.when | default(True) diff --git a/roles/security/tls_presigned_certs/tasks/main.yml b/roles/security/tls_presigned_certs/tasks/main.yml new file mode 100644 index 00000000..619fc16b --- /dev/null +++ b/roles/security/tls_presigned_certs/tasks/main.yml @@ -0,0 +1,154 @@ +# Copyright 2023 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 + file: + state: directory + path: "{{ dir }}" + mode: 0755 + owner: root + loop: + - "{{ base_dir_security }}" + - "{{ base_dir_security_pki }}" + loop_control: + loop_var: dir + +- set_fact: + wild_card_cert: "{{ inventory_hostname.split('.', 1)[1] }}" + +- name: Check if pre-signed cert is available + become: no + stat: + path: "{{ tls_signed_certs_dir }}/{{ inventory_hostname }}.pem" + register: pre_signed_cert + +- name: Check if pre-existing key is available + become: no + stat: + path: "{{ tls_signed_certs_dir }}/{{ inventory_hostname }}.key" + register: pre_existing_key + +- name: Check if pre-signed wildcard cert is available + become: no + stat: + path: "{{ tls_signed_certs_dir }}/{{ wild_card_cert }}.pem" + register: pre_signed_wildcard_cert + +- name: Check if pre-existing wildcard key is available + become: no + stat: + path: "{{ tls_signed_certs_dir }}/{{ wild_card_cert }}.key" + register: pre_existing_wildcard_key + +- name: Copy pre-signed certs to cloudera pki path + copy: + remote_src: true + src: "{{ tls_signed_certs_dir }}/{{ inventory_hostname }}.pem" + dest: "{{ tls_cert_path }}" + mode: 0644 + when: pre_signed_cert.stat.exists + +- name: Copy pre-existing key to cloudera pki path + copy: + remote_src: true + src: "{{ tls_signed_certs_dir }}/{{ inventory_hostname }}.key" + dest: "{{ tls_key_path }}" + mode: 0400 + when: pre_existing_key.stat.exists + +- name: Copy pre-signed wildcard certs to cloudera pki path + copy: + remote_src: true + src: "{{ tls_signed_certs_dir }}/{{ wild_card_cert }}.pem" + dest: "{{ tls_cert_path }}" + mode: 0644 + when: pre_signed_wildcard_cert.stat.exists + +- name: Copy pre-existing wildcard key to cloudera pki path + copy: + remote_src: true + src: "{{ tls_signed_certs_dir }}/{{ wild_card_cert }}.key" + dest: "{{ tls_key_path }}" + mode: 0400 + when: pre_existing_wildcard_key.stat.exists + +- name: Write key password to file + shell: + cmd: echo -n {{ tls_key_password }} > {{ tls_key_password_file }} + creates: "{{ tls_key_password_file }}" + +- name: Write unencrypted key + shell: + cmd: > + {{ openssl_path }} rsa + -in {{ tls_key_path }} + -passin pass:{{ tls_key_password }} + -out {{ tls_key_path_plaintext }} + creates: "{{ tls_key_path_plaintext }}" + +- name: Create a pkcs12 cert + shell: + cmd: > + {{ openssl_path }} pkcs12 + -export + -in {{ base_dir_security_pki }}/{{ inventory_hostname }}.pem + -inkey {{ tls_key_path_plaintext }} + -certfile {{ base_dir_security_pki }}/{{ inventory_hostname }}.pem + -out {{ base_dir_security_pki }}/{{ inventory_hostname }}.p12 + -password pass:{{ tls_keystore_password }} + -name {{ inventory_hostname }} + creates: "{{ base_dir_security_pki }}/{{ inventory_hostname }}.p12" + when: pre_existing_key.stat.exists or pre_signed_cert.stat.exists or pre_existing_wildcard_key.stat.exists or pre_signed_wildcard_cert.stat.exists + +- name: Create Java keystore from pkcs12 cert + shell: + cmd: > + {{ keytool_path }} + -importkeystore + -alias {{ keystore_alias | default(inventory_hostname) }} + -srckeystore {{ base_dir_security_pki }}/{{ inventory_hostname }}.p12 + -srcstoretype pkcs12 + -srcstorepass {{ tls_keystore_password }} + -destkeystore {{ tls_keystore_path }} + -deststoretype JKS + -deststorepass {{ tls_keystore_password }} + creates: "{{ tls_keystore_path }}" + when: pre_existing_key.stat.exists or pre_signed_cert.stat.exists or pre_existing_wildcard_key.stat.exists or pre_signed_wildcard_cert.stat.exists + +- name: Delete temporary PKCS12 keystore + file: + path: "{{ base_dir_security_pki }}/{{ inventory_hostname }}.p12" + state: absent + +- name: Create host agnostic links + file: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + state: hard + with_items: + - { src: "{{ tls_keystore_path }}", dest: "{{ tls_keystore_path_generic }}" } + - { src: "{{ tls_key_path }}", dest: "{{ tls_key_path_generic }}" } + - { src: "{{ tls_key_path_plaintext }}", dest: "{{ tls_key_path_plaintext_generic }}" } + +- name: Set file permissions + include_tasks: acls.yml + when: + - inventory_hostname not in groups['ecs_nodes'] | default([]) + +- name: Set file permissions for ECS + include_tasks: acls-ecs.yml + when: + - inventory_hostname in groups['ecs_nodes'] | default([])