From 99745a04223f2ed8111b5eb4b49d2bcfec9e678f Mon Sep 17 00:00:00 2001 From: Jan Chaloupka Date: Thu, 14 Sep 2017 12:10:15 +0200 Subject: Consolidate etcd certs roles This is a part of the etcd_ like role consolidationi into an action-based role. As part of the consilidation some roles have been removed and some replaced by include_role module. Resulting in reorder and shift of role dependencies from a role into a play. --- roles/etcd/meta/main.yml | 1 - roles/etcd/tasks/ca.yml | 2 + roles/etcd/tasks/ca/deploy.yml | 78 +++++++ roles/etcd/tasks/client_certificates.yml | 2 + .../tasks/client_certificates/fetch_from_ca.yml | 138 ++++++++++++ roles/etcd/tasks/main.yml | 2 + roles/etcd/tasks/server_certificates.yml | 2 + .../tasks/server_certificates/fetch_from_ca.yml | 238 +++++++++++++++++++++ roles/etcd/templates/openssl_append.j2 | 51 +++++ 9 files changed, 513 insertions(+), 1 deletion(-) create mode 100644 roles/etcd/tasks/ca.yml create mode 100644 roles/etcd/tasks/ca/deploy.yml create mode 100644 roles/etcd/tasks/client_certificates.yml create mode 100644 roles/etcd/tasks/client_certificates/fetch_from_ca.yml create mode 100644 roles/etcd/tasks/server_certificates.yml create mode 100644 roles/etcd/tasks/server_certificates/fetch_from_ca.yml create mode 100644 roles/etcd/templates/openssl_append.j2 (limited to 'roles/etcd') diff --git a/roles/etcd/meta/main.yml b/roles/etcd/meta/main.yml index 9a955c822..d69366a39 100644 --- a/roles/etcd/meta/main.yml +++ b/roles/etcd/meta/main.yml @@ -18,5 +18,4 @@ galaxy_info: dependencies: - role: lib_openshift - role: lib_os_firewall -- role: etcd_server_certificates - role: etcd_common diff --git a/roles/etcd/tasks/ca.yml b/roles/etcd/tasks/ca.yml new file mode 100644 index 000000000..7cda49069 --- /dev/null +++ b/roles/etcd/tasks/ca.yml @@ -0,0 +1,2 @@ +--- +- include: ca/deploy.yml diff --git a/roles/etcd/tasks/ca/deploy.yml b/roles/etcd/tasks/ca/deploy.yml new file mode 100644 index 000000000..3d32290a2 --- /dev/null +++ b/roles/etcd/tasks/ca/deploy.yml @@ -0,0 +1,78 @@ +--- +- name: Install openssl + package: + name: openssl + state: present + when: not etcd_is_atomic | bool + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- file: + path: "{{ item }}" + state: directory + mode: 0700 + owner: root + group: root + with_items: + - "{{ etcd_ca_new_certs_dir }}" + - "{{ etcd_ca_crl_dir }}" + - "{{ etcd_ca_dir }}/fragments" + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- command: cp /etc/pki/tls/openssl.cnf ./ + args: + chdir: "{{ etcd_ca_dir }}/fragments" + creates: "{{ etcd_ca_dir }}/fragments/openssl.cnf" + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- template: + dest: "{{ etcd_ca_dir }}/fragments/openssl_append.cnf" + src: openssl_append.j2 + backup: true + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- assemble: + src: "{{ etcd_ca_dir }}/fragments" + dest: "{{ etcd_openssl_conf }}" + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- name: Check etcd_ca_db exist + stat: path="{{ etcd_ca_db }}" + register: etcd_ca_db_check + changed_when: false + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- name: Touch etcd_ca_db file + file: + path: "{{ etcd_ca_db }}" + state: touch + when: etcd_ca_db_check.stat.isreg is not defined + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- copy: + dest: "{{ etcd_ca_serial }}" + content: "01" + force: no + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- name: Create etcd CA certificate + command: > + openssl req -config {{ etcd_openssl_conf }} -newkey rsa:4096 + -keyout {{ etcd_ca_key }} -new -out {{ etcd_ca_cert }} + -x509 -extensions {{ etcd_ca_exts_self }} -batch -nodes + -days {{ etcd_ca_default_days }} + -subj /CN=etcd-signer@{{ ansible_date_time.epoch }} + args: + chdir: "{{ etcd_ca_dir }}" + creates: "{{ etcd_ca_cert }}" + environment: + SAN: 'etcd-signer' + delegate_to: "{{ etcd_ca_host }}" + run_once: true diff --git a/roles/etcd/tasks/client_certificates.yml b/roles/etcd/tasks/client_certificates.yml new file mode 100644 index 000000000..2e9c078b9 --- /dev/null +++ b/roles/etcd/tasks/client_certificates.yml @@ -0,0 +1,2 @@ +--- +- include: client_certificates/fetch_from_ca.yml diff --git a/roles/etcd/tasks/client_certificates/fetch_from_ca.yml b/roles/etcd/tasks/client_certificates/fetch_from_ca.yml new file mode 100644 index 000000000..119071a72 --- /dev/null +++ b/roles/etcd/tasks/client_certificates/fetch_from_ca.yml @@ -0,0 +1,138 @@ +--- +- name: Ensure CA certificate exists on etcd_ca_host + stat: + path: "{{ etcd_ca_cert }}" + register: g_ca_cert_stat_result + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- fail: + msg: > + CA certificate {{ etcd_ca_cert }} doesn't exist on CA host + {{ etcd_ca_host }}. Apply 'etcd_ca' action from `etcd` role to + {{ etcd_ca_host }}. + when: not g_ca_cert_stat_result.stat.exists | bool + run_once: true + +- name: Check status of external etcd certificatees + stat: + path: "{{ etcd_cert_config_dir }}/{{ item }}" + with_items: + - "{{ etcd_cert_prefix }}client.crt" + - "{{ etcd_cert_prefix }}client.key" + - "{{ etcd_cert_prefix }}ca.crt" + register: g_external_etcd_cert_stat_result + when: not etcd_certificates_redeploy | default(false) | bool + +- set_fact: + etcd_client_certs_missing: "{{ true if etcd_certificates_redeploy | default(false) | bool + else (False in (g_external_etcd_cert_stat_result.results + | default({}) + | oo_collect(attribute='stat.exists') + | list)) }}" + +- name: Ensure generated_certs directory present + file: + path: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + state: directory + mode: 0700 + when: etcd_client_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Create the client csr + command: > + openssl req -new -keyout {{ etcd_cert_prefix }}client.key + -config {{ etcd_openssl_conf }} + -out {{ etcd_cert_prefix }}client.csr + -reqexts {{ etcd_req_ext }} -batch -nodes + -subj /CN={{ etcd_hostname }} + args: + chdir: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + creates: "{{ etcd_generated_certs_dir ~ '/' ~ etcd_cert_subdir ~ '/' + ~ etcd_cert_prefix ~ 'client.csr' }}" + environment: + SAN: "IP:{{ etcd_ip }},DNS:{{ etcd_hostname }}" + when: etcd_client_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +# Certificates must be signed serially in order to avoid competing +# for the serial file. +- name: Sign and create the client crt + delegated_serial_command: + command: > + openssl ca -name {{ etcd_ca_name }} -config {{ etcd_openssl_conf }} + -out {{ etcd_cert_prefix }}client.crt + -in {{ etcd_cert_prefix }}client.csr + -batch + chdir: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + creates: "{{ etcd_generated_certs_dir ~ '/' ~ etcd_cert_subdir ~ '/' + ~ etcd_cert_prefix ~ 'client.crt' }}" + environment: + SAN: "IP:{{ etcd_ip }}" + when: etcd_client_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- file: + src: "{{ etcd_ca_cert }}" + dest: "{{ etcd_generated_certs_dir}}/{{ etcd_cert_subdir }}/{{ etcd_cert_prefix }}ca.crt" + state: hard + when: etcd_client_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Create local temp directory for syncing certs + local_action: command mktemp -d /tmp/etcd_certificates-XXXXXXX + register: g_etcd_client_mktemp + changed_when: False + when: etcd_client_certs_missing | bool + become: no + +- name: Create a tarball of the etcd certs + command: > + tar -czvf {{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}.tgz + -C {{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }} . + args: + creates: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}.tgz" + # Disables the following warning: + # Consider using unarchive module rather than running tar + warn: no + when: etcd_client_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Retrieve the etcd cert tarballs + fetch: + src: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}.tgz" + dest: "{{ g_etcd_client_mktemp.stdout }}/" + flat: yes + fail_on_missing: yes + validate_checksum: yes + when: etcd_client_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Ensure certificate directory exists + file: + path: "{{ etcd_cert_config_dir }}" + state: directory + when: etcd_client_certs_missing | bool + +- name: Unarchive etcd cert tarballs + unarchive: + src: "{{ g_etcd_client_mktemp.stdout }}/{{ etcd_cert_subdir }}.tgz" + dest: "{{ etcd_cert_config_dir }}" + when: etcd_client_certs_missing | bool + +- file: + path: "{{ etcd_cert_config_dir }}/{{ item }}" + owner: root + group: root + mode: 0600 + with_items: + - "{{ etcd_cert_prefix }}client.crt" + - "{{ etcd_cert_prefix }}client.key" + - "{{ etcd_cert_prefix }}ca.crt" + when: etcd_client_certs_missing | bool + +- name: Delete temporary directory + local_action: file path="{{ g_etcd_client_mktemp.stdout }}" state=absent + changed_when: False + when: etcd_client_certs_missing | bool + become: no diff --git a/roles/etcd/tasks/main.yml b/roles/etcd/tasks/main.yml index 78e543ef1..870c11ad4 100644 --- a/roles/etcd/tasks/main.yml +++ b/roles/etcd/tasks/main.yml @@ -1,4 +1,6 @@ --- +- include: server_certificates.yml + - name: Set hostname and ip facts set_fact: # Store etcd_hostname and etcd_ip such that they will be available diff --git a/roles/etcd/tasks/server_certificates.yml b/roles/etcd/tasks/server_certificates.yml new file mode 100644 index 000000000..f0ba58b6e --- /dev/null +++ b/roles/etcd/tasks/server_certificates.yml @@ -0,0 +1,2 @@ +--- +- include: server_certificates/fetch_from_ca.yml diff --git a/roles/etcd/tasks/server_certificates/fetch_from_ca.yml b/roles/etcd/tasks/server_certificates/fetch_from_ca.yml new file mode 100644 index 000000000..064fe1952 --- /dev/null +++ b/roles/etcd/tasks/server_certificates/fetch_from_ca.yml @@ -0,0 +1,238 @@ +--- +- include: ../ca/deploy.yml + when: + - etcd_ca_setup | default(True) | bool + +- name: Install etcd + package: + name: "etcd{{ '-' + etcd_version if etcd_version is defined else '' }}" + state: present + when: not etcd_is_containerized | bool + +- name: Check status of etcd certificates + stat: + path: "{{ item }}" + with_items: + - "{{ etcd_cert_config_dir }}/{{ etcd_cert_prefix }}server.crt" + - "{{ etcd_cert_config_dir }}/{{ etcd_cert_prefix }}peer.crt" + - "{{ etcd_cert_config_dir }}/{{ etcd_cert_prefix }}ca.crt" + - "{{ etcd_system_container_cert_config_dir }}/{{ etcd_cert_prefix }}server.crt" + - "{{ etcd_system_container_cert_config_dir }}/{{ etcd_cert_prefix }}peer.crt" + - "{{ etcd_system_container_cert_config_dir }}/{{ etcd_cert_prefix }}ca.crt" + register: g_etcd_server_cert_stat_result + when: not etcd_certificates_redeploy | default(false) | bool + +- set_fact: + etcd_server_certs_missing: "{{ true if etcd_certificates_redeploy | default(false) | bool + else (False in (g_etcd_server_cert_stat_result.results + | default({}) + | oo_collect(attribute='stat.exists') + | list)) }}" + +- name: Ensure generated_certs directory present + file: + path: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + state: directory + mode: 0700 + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Create the server csr + command: > + openssl req -new -keyout {{ etcd_cert_prefix }}server.key + -config {{ etcd_openssl_conf }} + -out {{ etcd_cert_prefix }}server.csr + -reqexts {{ etcd_req_ext }} -batch -nodes + -subj /CN={{ etcd_hostname }} + args: + chdir: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + creates: "{{ etcd_generated_certs_dir ~ '/' ~ etcd_cert_subdir ~ '/' + ~ etcd_cert_prefix ~ 'server.csr' }}" + environment: + SAN: "IP:{{ etcd_ip }},DNS:{{ etcd_hostname }}" + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +# Certificates must be signed serially in order to avoid competing +# for the serial file. +- name: Sign and create the server crt + delegated_serial_command: + command: > + openssl ca -name {{ etcd_ca_name }} -config {{ etcd_openssl_conf }} + -out {{ etcd_cert_prefix }}server.crt + -in {{ etcd_cert_prefix }}server.csr + -extensions {{ etcd_ca_exts_server }} -batch + chdir: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + creates: "{{ etcd_generated_certs_dir ~ '/' ~ etcd_cert_subdir ~ '/' + ~ etcd_cert_prefix ~ 'server.crt' }}" + environment: + SAN: "IP:{{ etcd_ip }}" + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Create the peer csr + command: > + openssl req -new -keyout {{ etcd_cert_prefix }}peer.key + -config {{ etcd_openssl_conf }} + -out {{ etcd_cert_prefix }}peer.csr + -reqexts {{ etcd_req_ext }} -batch -nodes + -subj /CN={{ etcd_hostname }} + args: + chdir: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + creates: "{{ etcd_generated_certs_dir ~ '/' ~ etcd_cert_subdir ~ '/' + ~ etcd_cert_prefix ~ 'peer.csr' }}" + environment: + SAN: "IP:{{ etcd_ip }},DNS:{{ etcd_hostname }}" + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +# Certificates must be signed serially in order to avoid competing +# for the serial file. +- name: Sign and create the peer crt + delegated_serial_command: + command: > + openssl ca -name {{ etcd_ca_name }} -config {{ etcd_openssl_conf }} + -out {{ etcd_cert_prefix }}peer.crt + -in {{ etcd_cert_prefix }}peer.csr + -extensions {{ etcd_ca_exts_peer }} -batch + chdir: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + creates: "{{ etcd_generated_certs_dir ~ '/' ~ etcd_cert_subdir ~ '/' + ~ etcd_cert_prefix ~ 'peer.crt' }}" + environment: + SAN: "IP:{{ etcd_ip }}" + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- file: + src: "{{ etcd_ca_cert }}" + dest: "{{ etcd_generated_certs_dir}}/{{ etcd_cert_subdir }}/{{ etcd_cert_prefix }}ca.crt" + state: hard + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Create local temp directory for syncing certs + local_action: command mktemp -d /tmp/etcd_certificates-XXXXXXX + become: no + register: g_etcd_server_mktemp + changed_when: False + when: etcd_server_certs_missing | bool + +- name: Create a tarball of the etcd certs + command: > + tar -czvf {{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}.tgz + -C {{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }} . + args: + creates: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}.tgz" + # Disables the following warning: + # Consider using unarchive module rather than running tar + warn: no + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Retrieve etcd cert tarball + fetch: + src: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}.tgz" + dest: "{{ g_etcd_server_mktemp.stdout }}/" + flat: yes + fail_on_missing: yes + validate_checksum: yes + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Ensure certificate directory exists + file: + path: "{{ item }}" + state: directory + with_items: + - "{{ etcd_cert_config_dir }}" + - "{{ etcd_system_container_cert_config_dir }}" + when: etcd_server_certs_missing | bool + +- name: Unarchive cert tarball + unarchive: + src: "{{ g_etcd_server_mktemp.stdout }}/{{ etcd_cert_subdir }}.tgz" + dest: "{{ etcd_cert_config_dir }}" + when: etcd_server_certs_missing | bool + +- name: Create a tarball of the etcd ca certs + command: > + tar -czvf {{ etcd_generated_certs_dir }}/{{ etcd_ca_name }}.tgz + -C {{ etcd_ca_dir }} . + args: + creates: "{{ etcd_generated_certs_dir }}/{{ etcd_ca_name }}.tgz" + warn: no + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Retrieve etcd ca cert tarball + fetch: + src: "{{ etcd_generated_certs_dir }}/{{ etcd_ca_name }}.tgz" + dest: "{{ g_etcd_server_mktemp.stdout }}/" + flat: yes + fail_on_missing: yes + validate_checksum: yes + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Ensure ca directory exists + file: + path: "{{ item }}" + state: directory + with_items: + - "{{ etcd_ca_dir }}" + - "{{ etcd_system_container_cert_config_dir }}/ca" + when: etcd_server_certs_missing | bool + +- name: Unarchive cert tarball for the system container + unarchive: + src: "{{ g_etcd_server_mktemp.stdout }}/{{ etcd_cert_subdir }}.tgz" + dest: "{{ etcd_system_container_cert_config_dir }}" + when: + - etcd_server_certs_missing | bool + - r_etcd_common_etcd_runtime == 'runc' + +- name: Unarchive etcd ca cert tarballs for the system container + unarchive: + src: "{{ g_etcd_server_mktemp.stdout }}/{{ etcd_ca_name }}.tgz" + dest: "{{ etcd_system_container_cert_config_dir }}/ca" + when: + - etcd_server_certs_missing | bool + - r_etcd_common_etcd_runtime == 'runc' + +- name: Delete temporary directory + local_action: file path="{{ g_etcd_server_mktemp.stdout }}" state=absent + become: no + changed_when: False + when: etcd_server_certs_missing | bool + +- name: Validate permissions on certificate files + file: + path: "{{ item }}" + mode: 0600 + owner: "{{ 'etcd' if not etcd_is_containerized | bool else omit }}" + group: "{{ 'etcd' if not etcd_is_containerized | bool else omit }}" + when: etcd_url_scheme == 'https' + with_items: + - "{{ etcd_ca_file }}" + - "{{ etcd_cert_file }}" + - "{{ etcd_key_file }}" + +- name: Validate permissions on peer certificate files + file: + path: "{{ item }}" + mode: 0600 + owner: "{{ 'etcd' if not etcd_is_containerized | bool else omit }}" + group: "{{ 'etcd' if not etcd_is_containerized | bool else omit }}" + when: etcd_peer_url_scheme == 'https' + with_items: + - "{{ etcd_peer_ca_file }}" + - "{{ etcd_peer_cert_file }}" + - "{{ etcd_peer_key_file }}" + +- name: Validate permissions on the config dir + file: + path: "{{ etcd_conf_dir }}" + state: directory + owner: "{{ 'etcd' if not etcd_is_containerized | bool else omit }}" + group: "{{ 'etcd' if not etcd_is_containerized | bool else omit }}" + mode: 0700 diff --git a/roles/etcd/templates/openssl_append.j2 b/roles/etcd/templates/openssl_append.j2 new file mode 100644 index 000000000..f28316fc2 --- /dev/null +++ b/roles/etcd/templates/openssl_append.j2 @@ -0,0 +1,51 @@ + +[ {{ etcd_req_ext }} ] +basicConstraints = critical,CA:FALSE +keyUsage = digitalSignature,keyEncipherment +subjectAltName = ${ENV::SAN} + +[ {{ etcd_ca_name }} ] +dir = {{ etcd_ca_dir }} +crl_dir = {{ etcd_ca_crl_dir }} +database = {{ etcd_ca_db }} +new_certs_dir = {{ etcd_ca_new_certs_dir }} +certificate = {{ etcd_ca_cert }} +serial = {{ etcd_ca_serial }} +private_key = {{ etcd_ca_key }} +crl_number = {{ etcd_ca_crl_number }} +x509_extensions = {{ etcd_ca_exts_client }} +default_days = {{ etcd_ca_default_days }} +default_md = sha256 +preserve = no +name_opt = ca_default +cert_opt = ca_default +policy = policy_anything +unique_subject = no +copy_extensions = copy + +[ {{ etcd_ca_exts_self }} ] +authorityKeyIdentifier = keyid,issuer +basicConstraints = critical,CA:TRUE,pathlen:0 +keyUsage = critical,digitalSignature,keyEncipherment,keyCertSign +subjectKeyIdentifier = hash + +[ {{ etcd_ca_exts_peer }} ] +authorityKeyIdentifier = keyid,issuer:always +basicConstraints = critical,CA:FALSE +extendedKeyUsage = clientAuth,serverAuth +keyUsage = digitalSignature,keyEncipherment +subjectKeyIdentifier = hash + +[ {{ etcd_ca_exts_server }} ] +authorityKeyIdentifier = keyid,issuer:always +basicConstraints = critical,CA:FALSE +extendedKeyUsage = serverAuth +keyUsage = digitalSignature,keyEncipherment +subjectKeyIdentifier = hash + +[ {{ etcd_ca_exts_client }} ] +authorityKeyIdentifier = keyid,issuer:always +basicConstraints = critical,CA:FALSE +extendedKeyUsage = clientAuth +keyUsage = digitalSignature,keyEncipherment +subjectKeyIdentifier = hash -- cgit v1.2.1