Skip to content

Commit

Permalink
Support of additional SAN for etcd (#49)
Browse files Browse the repository at this point in the history
* Support of SAN for etcd

* Removed trailing spaces

* Update README.md

Co-authored-by: Thomas Widhalm <[email protected]>

* Update tasks/main.yml

Co-authored-by: Thomas Widhalm <[email protected]>

* Update tasks/main.yml

Co-authored-by: Thomas Widhalm <[email protected]>

* Update README.md

Co-authored-by: Thomas Widhalm <[email protected]>

* Fixed syntax

* Check for instance key (#52)

* Check for instance key
* Use new ca_client_ca_dir
* Add check for server certificate

fixes #51

* Enhance Readme (#55)

* Enhance Readme

fixes #54

* Added link to "Contributing" chapter

Co-authored-by: DanOPT <[email protected]>

* Added tasks to create additional peer and server certificates for etcd

* Resolved merge conflict of README.md

* Resolved tasks/main.yml

* Removed trailing spaces

* Updated README.md

* Update README.md

* Added first part of molecule verification

* Finished molecule verification tasks

* Get IP addresses of nodes by group name for etcd certificates

* Fixed trailing spaces

* Fixed groups in molecule.yml

* Converted groups to array

* Added packages and start Networkmanager for EL in prepare.yml

* Added empty line EOF to prepare.yml

* Changed volume to rw instead r

* Added cgroupns_mode to molecule.yml

* Changed actions to v3

* Removed CentOS 7 from molecule tests

* Improved default(omit) to default(omit, true)

Co-authored-by: Thomas Widhalm <[email protected]>
  • Loading branch information
danopt and widhalmt authored Jan 11, 2023
1 parent 6df97b8 commit 858b8c4
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 15 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/molecule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ jobs:
strategy:
max-parallel: 4
matrix:
distro: [centos7, rockylinux8]
distro: [rockylinux8]
scenario: [default]
steps:
- name: Check out code
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Set up Python 3.8
uses: actions/setup-python@v2
uses: actions/setup-python@v3
with:
python-version: 3.8

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ You need to have the Python library `cryptography` in version `>1.2.3` available
* `ca_ca_keylength`: CA keylength (default: `2048`)
* `ca_server_cert`: Create server certificate as well (default: `true`)
* `ca_logstash`: Create Logstash compatible certificate as well. Needs `ca_server_cert` to be set. (default: `false`)
* `ca_etcd`: Create additional etcd compatible certificates. Requires `ca_etcd_group` to be defined. (default: `false`)
* `ca_etcd_group`: Needs to be set to the group name of etcd nodes and will add the default IPv4 address of each node to the certificates. 127.0.0.1 will also be added by the role to the SAN for loopback purposes.(default: `undefined`)
* `ca_keypassphrase`: Password for the client key, default not defined
* `ca_openssl_cipher`: Cipher to use for key creation, default not defined
* `ca_client_ca_dir`: Directory to place CA and certificates on the clients (default: `/opt/ca`)
Expand Down
1 change: 1 addition & 0 deletions defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ca_ca_host: localhost

ca_server_cert: true
ca_logstash: false
ca_etcd: false

ca_country: EX
ca_state: EX
Expand Down
2 changes: 2 additions & 0 deletions molecule/default/converge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
vars:
ca_ca_host: ca_default
ca_logstash: true
ca_etcd: true
ca_etcd_group: molecule
tasks:
- name: "Include CA role"
include_role:
Expand Down
8 changes: 6 additions & 2 deletions molecule/default/molecule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@ platforms:
image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos7}-ansible:latest"
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
- /sys/fs/cgroup:/sys/fs/cgroup:rw
privileged: true
pre_build_image: true
cgroupns_mode: host
- name: ca_default_client
image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos7}-ansible:latest"
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
- /sys/fs/cgroup:/sys/fs/cgroup:rw
privileged: true
pre_build_image: true
groups:
- molecule
cgroupns_mode: host
provisioner:
name: ansible
verifier:
Expand Down
14 changes: 14 additions & 0 deletions molecule/default/prepare.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,17 @@
- name: Install Python libraries
pip:
name: cryptography>= 1.2.3

- name: Install packages for RHEL
package:
name:
- iproute
- NetworkManager
when: ansible_os_family == "RedHat"

- name: Start NetworkManager
service:
name: NetworkManager
state: started
enabled: yes
when: ansible_os_family == "RedHat"
47 changes: 47 additions & 0 deletions molecule/default/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,50 @@
msg: "Logstash key is missing"
when:
- not logstash_key_stat.stat.exists | bool

- name: Verify signature on etcd peer certificate
command: >
openssl verify
-verbose
-CAfile {{ ca_ca_dir }}/ca.crt
{{ ca_client_ca_dir }}/{{ inventory_hostname }}-etcd.crt
- name: Verify signature on etcd server certificate
command: >
openssl verify
-verbose
-CAfile {{ ca_ca_dir }}/ca.crt
{{ ca_client_ca_dir }}/{{ inventory_hostname }}-etcd-server.crt
- name: Verify signature on etcd server certificate
command: >
openssl verify
-verbose
-CAfile {{ ca_ca_dir }}/ca.crt
{{ ca_client_ca_dir }}/{{ inventory_hostname }}-etcd-server.crt
- name: Register SAN of etcd peer certificate
command: >
openssl x509 -text -noout -in {{ ca_client_ca_dir }}/{{ inventory_hostname }}-etcd.crt
-certopt no_subject,no_header,no_version,no_serial,no_signame,no_validity,no_issuer,no_pubkey,no_sigdump,no_aux
register: etcd_san_peer_stat

- name: Register SAN of etcd server certificate
command: >
openssl x509 -text -noout -in {{ ca_client_ca_dir }}/{{ inventory_hostname }}-etcd-server.crt
-certopt no_subject,no_header,no_version,no_serial,no_signame,no_validity,no_issuer,no_pubkey,no_sigdump,no_aux
register: etcd_san_server_stat

- name: Fail if SAN of etcd peer certificate is missing loopback or default IP address
fail:
msg: "Default IPv4 address in etcd peer certifcate are missing"
when:
- hostvars['ca_default_client']['ansible_default_ipv4']['address'] not in etcd_san_peer_stat.stdout
- '"127.0.0.1" not in etcd_san_peer_stat.stdout'

- name: Fail if SAN of etcd server certificate is missing loopback or default IP address
fail:
msg: "Default IPv4 address in etcd server certifcate are missing"
when:
- hostvars['ca_default_client']['ansible_default_ipv4']['address'] not in etcd_san_server_stat.stdout
- '"127.0.0.1" not in etcd_san_server_stat.stdout'
153 changes: 143 additions & 10 deletions tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,20 @@
import_tasks: ca.yml
when: ca_ca_host == inventory_hostname

- name: Set ca_san to hostname, FQDN, and inventory hostname
set_fact:
ca_san: >-
DNS:{{ ansible_hostname }},
DNS:{{ ansible_fqdn }},
DNS:{{ inventory_hostname }}
### client certificate ###

- name: Create key
openssl_privatekey:
path: "{{ ca_client_ca_dir }}/{{ inventory_hostname }}.key"
passphrase: "{{ ca_keypassphrase | default(omit) }}"
cipher: "{{ ca_openssl_cipher | default(omit) }}"
passphrase: "{{ ca_keypassphrase | default(omit, true) }}"
cipher: "{{ ca_openssl_cipher | default(omit, true) }}"
owner: "{{ ca_client_ca_dir_owner }}"
group: "{{ ca_client_ca_dir_group }}"
mode: "{{ ca_client_ca_dir_mode }}"
Expand All @@ -50,12 +57,12 @@
openssl_csr:
path: "{{ ca_client_ca_dir }}/{{ inventory_hostname }}.csr"
privatekey_path: "{{ ca_client_ca_dir }}/{{ inventory_hostname }}.key"
privatekey_passphrase: "{{ ca_keypassphrase | default(omit) }}"
privatekey_passphrase: "{{ ca_keypassphrase | default(omit, true) }}"
country_name: "{{ ca_country }}"
organization_name: "{{ ca_organization }}"
common_name: "{{ inventory_hostname }}"
subject_alt_name: "DNS:{{ ansible_hostname }},DNS:{{ ansible_fqdn }},DNS:{{ inventory_hostname }}" # yamllint disable-line rule:line-length
extended_key_usage: "{{ extended_key_usage | default(omit) }}"
subject_alt_name: "{{ ca_san | regex_replace(' ', '') }}"
extended_key_usage: "{{ extended_key_usage | default(omit, true) }}"

- name: Pull CSR
fetch:
Expand Down Expand Up @@ -122,12 +129,12 @@
openssl_csr:
path: "{{ ca_client_ca_dir }}/{{ inventory_hostname }}-server.csr"
privatekey_path: "{{ ca_client_ca_dir }}/{{ inventory_hostname }}.key"
privatekey_passphrase: "{{ ca_keypassphrase | default(omit) }}"
privatekey_passphrase: "{{ ca_keypassphrase | default(omit, true) }}"
country_name: "{{ ca_country }}"
organization_name: "{{ ca_organization }}"
common_name: "{{ inventory_hostname }}"
subject_alt_name: "DNS:{{ ansible_hostname }},DNS:{{ ansible_fqdn }},DNS:{{ inventory_hostname }}" # yamllint disable-line rule:line-length
extended_key_usage: "{{ extended_key_usage | default(omit) }}"
subject_alt_name: "{{ ca_san | regex_replace(' ', '') }}"
extended_key_usage: "{{ extended_key_usage | default(omit, true) }}"

- name: Pull server CSR
fetch:
Expand Down Expand Up @@ -194,9 +201,9 @@
openssl pkcs8
-in {{ ca_client_ca_dir }}/{{ inventory_hostname }}.key
-topk8
-passin pass:{{ ca_keypassphrase | default(omit) }}
-passin pass:{{ ca_keypassphrase | default(omit, true) }}
-out {{ ca_client_ca_dir }}/{{ inventory_hostname }}-pkcs8.key
-passout pass:{{ ca_keypassphrase | default(omit) }}
-passout pass:{{ ca_keypassphrase | default(omit, true) }}
args:
creates: "{{ ca_client_ca_dir }}/{{ inventory_hostname }}-pkcs8.key"

Expand All @@ -206,3 +213,129 @@
owner: root
group: root
mode: 0600

### etcd peer certificate ###

- name: Create etcd peer certificate
when: ca_etcd | bool
block:

- name: Add IP addresses for etcd to ca_san_etcd
set_fact:
ca_san_etcd: >-
{{ ca_san }},
IP:127.0.0.1,
{%- for host in groups[ca_etcd_group] -%}
IP:{{ hostvars[host]['ansible_default_ipv4']['address'] }}
{%- if not loop.last -%}
,
{%- endif -%}
{%- endfor -%}
when:
- ca_etcd | bool

- name: Create CSR
openssl_csr:
path: "{{ ca_client_ca_dir }}/{{ inventory_hostname }}-etcd.csr"
privatekey_path: "{{ ca_client_ca_dir }}/{{ inventory_hostname }}.key"
country_name: "{{ ca_country }}"
organization_name: "{{ ca_organization }}"
common_name: "{{ inventory_hostname }}"
subject_alt_name: "{{ ca_san_etcd | regex_replace(' ', '') }}"
extended_key_usage: "{{ extended_key_usage | default(omit, true) }}"

- name: Pull CSR
fetch:
src: "{{ ca_client_ca_dir }}/{{ inventory_hostname }}-etcd.csr"
dest: "{{ ca_localdir }}/{{ inventory_hostname }}-etcd.csr"
flat: true

- name: Push CSR to CA host
copy:
src: "{{ ca_localdir }}/{{ inventory_hostname }}-etcd.csr"
dest: "{{ ca_ca_dir }}/{{ inventory_hostname }}-etcd.csr"
owner: root
group: root
mode: 0600
delegate_to: "{{ ca_ca_host }}"

- name: Sign CSR with CA key
openssl_certificate:
path: "{{ ca_ca_dir }}/{{ inventory_hostname }}-etcd.crt"
csr_path: "{{ ca_ca_dir }}/{{ inventory_hostname }}-etcd.csr"
ownca_path: "{{ ca_ca_dir }}/ca.crt"
ownca_privatekey_path: "{{ ca_ca_dir }}/ca.key"
ownca_privatekey_passphrase: "{{ ca_ca_password }}"
provider: ownca
delegate_to: "{{ ca_ca_host }}"

- name: Fetch certificate
fetch:
src: "{{ ca_ca_dir }}/{{ inventory_hostname }}-etcd.crt"
dest: "{{ ca_localdir }}/{{ inventory_hostname }}-etcd.crt"
flat: true
delegate_to: "{{ ca_ca_host }}"

- name: Push certificate to client
copy:
src: "{{ ca_localdir }}/{{ inventory_hostname }}-etcd.crt"
dest: "{{ ca_client_ca_dir }}/{{ inventory_hostname }}-etcd.crt"
owner: "{{ ca_client_ca_dir_owner }}"
group: "{{ ca_client_ca_dir_group }}"
mode: "{{ ca_client_ca_dir_mode }}"

### etcd server certificate ###

- name: Create etcd server certificate
when: ca_etcd | bool
block:

- name: Create CSR
openssl_csr:
path: "{{ ca_client_ca_dir }}/{{ inventory_hostname }}-etcd-server.csr"
privatekey_path: "{{ ca_client_ca_dir }}/{{ inventory_hostname }}.key"
country_name: "{{ ca_country }}"
organization_name: "{{ ca_organization }}"
common_name: "{{ inventory_hostname }}"
subject_alt_name: "{{ ca_san_etcd | regex_replace(' ', '') }}"
extended_key_usage: "{{ extended_key_usage | default(omit, true) }}"

- name: Pull CSR
fetch:
src: "{{ ca_client_ca_dir }}/{{ inventory_hostname }}-etcd-server.csr"
dest: "{{ ca_localdir }}/{{ inventory_hostname }}-etcd-server.csr"
flat: true

- name: Push CSR to CA host
copy:
src: "{{ ca_localdir }}/{{ inventory_hostname }}-etcd-server.csr"
dest: "{{ ca_ca_dir }}/{{ inventory_hostname }}-etcd-server.csr"
owner: root
group: root
mode: 0600
delegate_to: "{{ ca_ca_host }}"

- name: Sign CSR with CA key
openssl_certificate:
path: "{{ ca_ca_dir }}/{{ inventory_hostname }}-etcd-server.crt"
csr_path: "{{ ca_ca_dir }}/{{ inventory_hostname }}-etcd-server.csr"
ownca_path: "{{ ca_ca_dir }}/ca.crt"
ownca_privatekey_path: "{{ ca_ca_dir }}/ca.key"
ownca_privatekey_passphrase: "{{ ca_ca_password }}"
provider: ownca
delegate_to: "{{ ca_ca_host }}"

- name: Fetch certificate
fetch:
src: "{{ ca_ca_dir }}/{{ inventory_hostname }}-etcd-server.crt"
dest: "{{ ca_localdir }}/{{ inventory_hostname }}-etcd-server.crt"
flat: true
delegate_to: "{{ ca_ca_host }}"

- name: Push certificate to client
copy:
src: "{{ ca_localdir }}/{{ inventory_hostname }}-etcd-server.crt"
dest: "{{ ca_client_ca_dir }}/{{ inventory_hostname }}-etcd-server.crt"
owner: "{{ ca_client_ca_dir_owner }}"
group: "{{ ca_client_ca_dir_group }}"
mode: "{{ ca_client_ca_dir_mode }}"

0 comments on commit 858b8c4

Please sign in to comment.