diff --git a/defaults/main.yaml b/defaults/main.yaml index 8af440d..7a516cf 100644 --- a/defaults/main.yaml +++ b/defaults/main.yaml @@ -35,6 +35,10 @@ consul_self_signed_cert_name: "consul-tls" consul_cacert_multiple_default_gw_workaround: false consul_cacert_force_append_ips: [] consul_cacert_force_append_names: [] +consul_cacert_ca_host_group: "consul_cacert_ca_host" +consul_cacert_clients_group: "consul_cacert_clients" +consul_cacert_ca_trust_anchors_update: true + consul_server_ssl_config: {} consul_server_ssl_default_config: ports: @@ -43,8 +47,8 @@ consul_server_ssl_default_config: verify_outgoing: true verify_server_hostname: false ca_file: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.crt" - cert_file: "{{ consul_ssl_path }}/{{ consul_self_signed_cert_name }}.crt" - key_file: "{{ consul_ssl_path }}/{{ consul_self_signed_cert_name }}.key" + cert_file: "{{ consul_ssl_path }}/{{ inventory_hostname }}.crt" + key_file: "{{ consul_ssl_path }}/{{ inventory_hostname }}.key" auto_encrypt: allow_tls: true @@ -56,7 +60,7 @@ consul_agent_ssl_default_config: verify_outgoing: true verify_server_hostname: false ca_file: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.crt" - cert_file: "{{ consul_ssl_path }}/{{ consul_self_signed_cert_name }}.crt" - key_file: "{{ consul_ssl_path }}/{{ consul_self_signed_cert_name }}.key" + cert_file: "{{ consul_ssl_path }}/{{ inventory_hostname }}.crt" + key_file: "{{ consul_ssl_path }}/{{ inventory_hostname }}.key" is_virtualenv: "{{ lookup('env','VIRTUAL_ENV') | default('') }}" diff --git a/tasks/main.yaml b/tasks/main.yaml index 7f29c6c..80129a2 100644 --- a/tasks/main.yaml +++ b/tasks/main.yaml @@ -24,7 +24,7 @@ include_tasks: "{{ ansible_facts['os_family'] }}/main.yaml" - name: SSL certificates generation - import_tasks: cacert.yaml + import_tasks: ssl/main.yaml when: consul_ssl - name: Bootstrap Consul diff --git a/tasks/ssl/ca.yaml b/tasks/ssl/ca.yaml new file mode 100644 index 0000000..c7ce841 --- /dev/null +++ b/tasks/ssl/ca.yaml @@ -0,0 +1,142 @@ +- name: Create CA + when: inventory_hostname in groups[consul_cacert_ca_host_group] + block: + - name: CA and certs | Install cryptography library + package: + name: python3-cryptography + state: present + + - name: CA and certs | Check if ssl gen dir exist + file: + name: "{{ consul_ssl_path }}" + state: directory + + - name: Generate an OpenSSL private CA key with the default values (4096 bits, RSA) + community.crypto.openssl_privatekey: + path: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.key" + register: consul_cacert_ca_key_gen + + - name: Generate an OpenSSL Certificate Signing Request + community.crypto.openssl_csr: + path: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.csr" + privatekey_path: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.key" + use_common_name_for_san: false + basic_constraints: + - 'CA:TRUE' + basic_constraints_critical: yes + key_usage: + - keyCertSign + key_usage_critical: true + common_name: "CA-{{ consul_self_signed_cert_name }}" + register: consul_cacert_ca_csr + + - name: Generate a Self Signed OpenSSL CA certificate + community.crypto.x509_certificate: + path: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.crt" + csr_path: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.csr" + privatekey_path: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.key" + provider: selfsigned + register: consul_cacert_ca_cert_gen + + - name: Get CA cert content + slurp: + src: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.crt" + register: consul_cacert_ca_cert_b64 + + - name: Get CA csr content + slurp: + src: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.csr" + register: consul_cacert_ca_csr_b64 + + - name: Get CA key content + slurp: + src: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.key" + register: consul_cacert_ca_key_b64 + + - name: Set facts about key and cert + set_fact: + consul_cacert_ca_key: "{{ consul_cacert_ca_key_b64.content | b64decode }}" + consul_cacert_ca_csr: "{{ consul_cacert_ca_csr_b64.content | b64decode }}" + consul_cacert_ca_cert: "{{ consul_cacert_ca_cert_b64.content | b64decode }}" + delegate_to: "{{ fact_item }}" + delegate_facts: true + with_items: + - "{{ groups[consul_cacert_ca_host_group] | default([]) }}" + - "{{ groups[consul_cacert_clients_group] | default([]) }}" + loop_control: + loop_var: fact_item + +- name: Distribute CA certificates + become: true + block: + - name: CA and cert | Check if dest dir exist on remote host + file: + name: "{{ consul_ssl_path }}" + state: directory + loop: + - "{{ groups[consul_cacert_ca_host_group] | default([]) }}" + - "{{ groups[consul_cacert_clients_group] | default([]) }}" + loop_control: + loop_var: host_item + + - name: Put CA key + copy: + content: "{{ consul_cacert_ca_key }}" + dest: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.key" + mode: 0600 + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + loop: + - "{{ groups[consul_cacert_ca_host_group] | default([]) }}" + - "{{ groups[consul_cacert_clients_group] | default([]) }}" + loop_control: + loop_var: host_item + + - name: Put CA csr + copy: + content: "{{ consul_cacert_ca_csr }}" + dest: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.csr" + mode: 0644 + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + loop: + - "{{ groups[consul_cacert_ca_host_group] | default([]) }}" + - "{{ groups[consul_cacert_clients_group] | default([]) }}" + loop_control: + loop_var: host_item + + - name: Put CA cert + copy: + content: "{{ consul_cacert_ca_cert }}" + dest: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.crt" + mode: 0644 + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + loop: + - "{{ groups[consul_cacert_ca_host_group] | default([]) }}" + - "{{ groups[consul_cacert_clients_group] | default([]) }}" + loop_control: + loop_var: host_item + + - name: Put CA OpenSSL cert to PKI + copy: + content: "{{ consul_cacert_ca_cert }}" + dest: "{{ consul_cacert_ca_trust_dir }}/CA-{{ consul_self_signed_cert_name }}.crt" + mode: 0644 + loop: + - "{{ groups[consul_cacert_ca_host_group] | default([]) }}" + - "{{ groups[consul_cacert_clients_group] | default([]) }}" + loop_control: + loop_var: host_item + register: consul_ca_trust_anchors + + - name: Update CA trust + shell: "{{ consul_cacert_update_ca_trust_command }}" + loop: + - "{{ groups[consul_cacert_ca_host_group] | default([]) }}" + - "{{ groups[consul_cacert_clients_group] | default([]) }}" + loop_control: + loop_var: host_item + when: + - consul_ca_trust_anchors.changed + - consul_cacert_ca_trust_anchors_update diff --git a/tasks/ssl/certs.yaml b/tasks/ssl/certs.yaml new file mode 100644 index 0000000..403dbcd --- /dev/null +++ b/tasks/ssl/certs.yaml @@ -0,0 +1,181 @@ +- name: CA and certs | Generate CN certs and keys + when: inventory_hostname in groups[consul_cacert_ca_host_group] + block: + - name: Generate an OpenSSL private client key {{ item }} with the default values (4096 bits, RSA) + community.crypto.openssl_privatekey: + path: "{{ consul_ssl_path }}/{{ item }}.key" + register: consul_cacert_client_key_gen + + - name: Generate Consul subject_alt_ips from ansible_host + set_fact: + consul_server_subject_alt_ips_from_ansible_host: "{{ hostvars[item]['ansible_host'] | regex_replace('^', 'IP:') }}" + when: hostvars[item]['ansible_host'] is defined + + - name: Generate Consul subject_alt_ips from default ipv4 address + set_fact: + consul_server_subject_alt_ips: "{{ hostvars[item]['ansible_default_ipv4']['address'] | regex_replace('^', 'IP:') }}" + when: + - hostvars[item]['ansible_default_ipv4']['address'] is defined + - not consul_cacert_multiple_default_gw_workaround + + - name: Generate Consul subject_alt_names from inventory_hostname + set_fact: + consul_server_subject_alt_names: "{{ item | regex_replace('^', 'DNS:') }}" + + - name: Generate Consul subject_alt_ips from ansible_all_ipv4_addresses + set_fact: + consul_server_subject_alt_ips_all_ipv4: "{{ hostvars[item]['ansible_all_ipv4_addresses'] | map('regex_replace', '^', 'IP:') | join(',') }}" + when: hostvars[item]['ansible_all_ipv4_addresses'] is defined + + - name: Generate Consul subject_alt_ips from consul_cacert_force_append_ips + set_fact: + consul_server_subject_alt_ips_force_append: "{{ consul_cacert_force_append_ips | map('regex_replace', '^', 'IP:') | join(',') }}" + when: + - consul_cacert_force_append_ips is defined + - consul_cacert_force_append_ips | length > 0 + + - name: Generate Consul subject_alt_names from consul_cacert_force_append_names + set_fact: + consul_server_subject_alt_names_force_append: "{{ consul_cacert_force_append_names | map('regex_replace', '^', 'DNS:') | join(',') }}" + when: + - consul_cacert_force_append_names is defined + - consul_cacert_force_append_names | length > 0 + + - name: Construct base subject_alt_name + set_fact: + subject_alt_name: + - "{{ consul_server_subject_alt_names }}" + - "{{ consul_server_subject_alt_ips_from_ansible_host }}" + - "{{ consul_server_subject_alt_ips_all_ipv4 }}" + + - name: Construct subject_alt_name with consul_server_subject_alt_ips + set_fact: + subject_alt_name: + - "{{ subject_alt_name | join(',') }}" + - "{{ consul_server_subject_alt_ips }}" + when: consul_server_subject_alt_ips is defined + + - name: Construct subject_alt_name with consul_server_subject_alt_ips_force_append + set_fact: + subject_alt_name: + - "{{ subject_alt_name | join(',') }}" + - "{{ consul_server_subject_alt_ips_force_append }}" + when: consul_server_subject_alt_ips_force_append is defined + + - name: Construct subject_alt_name with consul_server_subject_alt_names_force_append + set_fact: + subject_alt_name: + - "{{ subject_alt_name | join(',') }}" + - "{{ consul_server_subject_alt_names_force_append }}" + when: consul_server_subject_alt_names_force_append is defined + + - debug: + msg: "{{ subject_alt_name }}" + + - name: Generate an OpenSSL Certificate Signing Request for client + community.crypto.openssl_csr: + path: "{{ consul_ssl_path }}/{{ item }}.csr" + privatekey_path: "{{ consul_ssl_path }}/{{ item }}.key" + common_name: "{{ item }}" + subject_alt_name: "{{ subject_alt_name | join(',') }}" + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + register: consul_csr + + - name: Generate an OpenSSL certificate for client signed with your own CA certificate + community.crypto.x509_certificate: + path: "{{ consul_ssl_path }}/{{ item }}.crt" + csr_path: "{{ consul_ssl_path }}/{{ item }}.csr" + ownca_path: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.crt" + ownca_privatekey_path: "{{ consul_ssl_path }}/CA-{{ consul_self_signed_cert_name }}.key" + provider: ownca + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + register: consul_cert + + - name: Get {{ item }} OpenSSL crt and key content + ansible.builtin.shell: | + cat {{ consul_ssl_path }}/{{ item }}.crt {{ consul_ssl_path }}/{{ item }}.key + register: concated_crt_key + changed_when: false + + - name: Concatenate and save {{ item }} OpenSSL crt and key to single file + copy: + content: "{{ concated_crt_key.stdout }}" + dest: "{{ consul_ssl_path }}/{{ item }}.pem" + + - name: Get CN key {{ item }} content + slurp: + src: "{{ consul_ssl_path }}/{{ item }}.key" + register: consul_cacert_cn_certs_key_b64 + + - name: Get CN csr {{ item }} content + slurp: + src: "{{ consul_ssl_path }}/{{ item }}.csr" + register: consul_cacert_cn_certs_csr_b64 + + - name: Get CN cert {{ item }} content + slurp: + src: "{{ consul_ssl_path }}/{{ item }}.crt" + register: consul_cacert_cn_certs_cert_b64 + + - name: Get CN cert and key concat {{ item }} content + slurp: + src: "{{ consul_ssl_path }}/{{ item }}.pem" + register: consul_cacert_cn_certs_concat_b64 + + - name: Set facts about {{ item }} key and cert and delegate + set_fact: + consul_cacert_cn_certs_key: "{{ consul_cacert_cn_certs_key_b64.content | b64decode }}" + consul_cacert_cn_certs_csr: "{{ consul_cacert_cn_certs_csr_b64.content | b64decode }}" + consul_cacert_cn_certs_cert: "{{ consul_cacert_cn_certs_cert_b64.content | b64decode }}" + consul_cacert_cn_certs_concat: "{{ consul_cacert_cn_certs_concat_b64.content | b64decode }}" + delegate_to: "{{ fact_item }}" + delegate_facts: true + with_items: + - "{{ groups[consul_cacert_ca_host_group] | default([]) }}" + - "{{ groups[consul_cacert_clients_group] | default([]) }}" + loop_control: + loop_var: fact_item + +- name: Distribute CN certificates + become: true + when: + - inventory_hostname in groups[consul_cacert_clients_group] + block: + - name: CA and cert | Check if dest dir exist on remote host + file: + name: "{{ consul_ssl_path }}" + state: directory + + - name: Put CN key + copy: + content: "{{ consul_cacert_cn_certs_key }}" + dest: "{{ consul_ssl_path }}/{{ item }}.key" + mode: 0600 + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + + - name: Put CN csr + copy: + content: "{{ consul_cacert_cn_certs_csr }}" + dest: "{{ consul_ssl_path }}/{{ item }}.csr" + mode: 0644 + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + + - name: Put CN cert + copy: + content: "{{ consul_cacert_cn_certs_cert }}" + dest: "{{ consul_ssl_path }}/{{ item }}.crt" + mode: 0644 + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + + - name: Put CN key and cert concat + copy: + content: "{{ consul_cacert_cn_certs_concat }}" + dest: "{{ consul_ssl_path }}/{{ item }}.pem" + mode: 0644 + owner: "{{ consul_user }}" + group: "{{ consul_group }}" diff --git a/tasks/ssl/main.yaml b/tasks/ssl/main.yaml new file mode 100644 index 0000000..38dc06d --- /dev/null +++ b/tasks/ssl/main.yaml @@ -0,0 +1,26 @@ +- name: Add host to {{ consul_cacert_ca_host_group }} + add_host: + groups: "{{ consul_cacert_ca_host_group }}" + hostname: "{{ hostvars[item]['ansible_hostname'] }}" + ansible_host: "{{ hostvars[item]['ansible_host'] }}" + with_items: + - "{{ ansible_play_hosts[0] }}" + changed_when: false + when: groups[consul_cacert_ca_host_group] is not defined + +- name: Add all hosts to {{ consul_cacert_clients_group }} + add_host: + groups: "{{ consul_cacert_clients_group }}" + hostname: "{{ hostvars[item]['ansible_hostname'] }}" + ansible_host: "{{ hostvars[item]['ansible_host'] }}" + with_items: + - "{{ ansible_play_hosts }}" + changed_when: false + when: groups[consul_cacert_clients_group] is not defined + +- name: CA and certs | Include CA + include_tasks: ca.yaml + +- name: CA and certs | Include Certificates + include_tasks: certs.yaml + loop: "{{ groups[consul_cacert_clients_group] }}" diff --git a/vars/Debian.yaml b/vars/Debian.yaml index 9692cb0..94c9690 100644 --- a/vars/Debian.yaml +++ b/vars/Debian.yaml @@ -12,5 +12,5 @@ consul_deps_packages: consul_version_build: "1" consul_package_name_regex: "{{ consul_package_name }}-{{ consul_version }}-{{ consul_version_build }}" -consul_ssl_update_ca_command: "update-ca-certificates --fresh" -consul_ssl_ca_trust_dir: "/usr/local/share/ca-certificates" +consul_cacert_update_ca_trust_command: "update-ca-certificates --fresh" +consul_cacert_ca_trust_dir: "/usr/local/share/ca-certificates" diff --git a/vars/RedHat.yaml b/vars/RedHat.yaml index d6ff1cc..275eb5d 100644 --- a/vars/RedHat.yaml +++ b/vars/RedHat.yaml @@ -1,4 +1,4 @@ consul_package: "{{ consul_package_name }}-{{ consul_version }}" consul_package_name_regex: "{{ consul_package }}" -consul_ssl_update_ca_command: "update-ca-trust extract" -consul_ssl_ca_trust_dir: "/etc/pki/ca-trust/source/anchors" +consul_cacert_update_ca_trust_command: "update-ca-trust extract" +consul_cacert_ca_trust_dir: "/etc/pki/ca-trust/source/anchors"