Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
11c1fe4
add support of different key algorithm
DXist Mar 9, 2024
dbf51aa
don't use passphrase in command arguments which could be logged
DXist Mar 9, 2024
acd9418
document key algorithm vars
DXist Mar 9, 2024
a36219a
ca_dir_owner is customizable
DXist Mar 9, 2024
9878f9a
require user to specify ca_ca_password
DXist Mar 9, 2024
780fd64
do prepare both for RSA and Ed25519 CAs
DXist Mar 9, 2024
db3deab
distinguished name components are omitted by default
DXist Mar 9, 2024
c9e4d69
use variables with defaults for common name and alternative names
DXist Mar 9, 2024
f0226de
add certificate update handlers
DXist Mar 10, 2024
3ed008d
tests for handlers, notAfter validation uses openssl -checkvalid
DXist Mar 10, 2024
58c5d91
fix bugs with wrong ca.crt path for clients and run_once for CA and l…
DXist Mar 10, 2024
2e08580
fix ansible-lint errors and enable ansible-lint in CI
DXist Mar 10, 2024
3d5fbce
add ca_alternative_name variable, nullable ca_altnameX vars
DXist Mar 12, 2024
20c50c0
include x509v3 extensions to CA certificate
DXist Mar 12, 2024
50bf19c
fix typo
DXist Mar 12, 2024
3d77d7d
don't generate client cert on CA host by default
DXist Mar 13, 2024
678003b
Don't generate server certificate by default
DXist Mar 13, 2024
70d3605
Rename: ca_client_ca_* -> ca_certs_*
DXist Mar 13, 2024
30c0611
Rename: ca_client_key_algorithm -> ca_key_algorithm
DXist Mar 13, 2024
9b5e9b0
Rename ca_client_cert -> ca_cert
DXist Mar 13, 2024
ec02e7c
Rename extended_key_usage -> ca_extended_key_usage
DXist Mar 13, 2024
6e33c57
ca_extended_key_usage could be a list
DXist Mar 13, 2024
950bcc5
use ca_dir_owner/group for ca.conf
DXist Mar 13, 2024
1bf4175
extended_key_usage is comma-separated
DXist Mar 13, 2024
65154e3
split san using commas
DXist Mar 13, 2024
1bdfbb2
fix mode for certs and keys
DXist Mar 13, 2024
9293a7e
always fetch CA certificate to the control host
DXist Mar 13, 2024
3c35b7c
remove leftover for openssl version detection
DXist Aug 9, 2025
e15894b
Apply suggestions from code review
DXist Aug 9, 2025
bc8660c
document CA key generation params variable
DXist Aug 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 42 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,41 @@ You need to have the Python library `cryptography` in version `>1.2.3` available

* `ca_manage_openssl`: Install `openssl` package? (default: `true`)
* `ca_ca_dir`: Directory to place CA and certificates (default: `/opt/ca`)
* `ca_ca_dir_owner`: CA directory owner (default: `root`)
* `ca_ca_dir_group`: CA directory group (default: `root`)
* `ca_ca_days`: Runtime of the CA certificate (default: `3650`)
* `ca_ca_password`: Password of CA key (default: `ChangeMe`)
* `ca_ca_password`: Password of CA key (no default, should be defined by user)
* `ca_localdir`: Temporary directory on Ansible management host (default: `/tmp/ca`)
* `ca_local_become`: Use `become` on the Ansible controller. Used for creation of `ca_localdir`. (default: `false`)
* `ca_ca_host`: Hostname of the CA host (default: `localhost`)
* `ca_country`: Setting for certificates (default: `EX`)
* `ca_state`: Setting for certificates (default: `EX`)
* `ca_locality`: Setting for certificates (default: `EX`)
* `ca_postalcode`: Setting for certificates (default: `1234`)
* `ca_organization`: Setting for certificates (default: `example`)
* `ca_organizationalunit`: Setting for certificates (default: `example`)
* `ca_country`: Setting for certificates (omitted by default)
* `ca_state`: Setting for certificates (omitted by default)
* `ca_locality`: Setting for certificates (omitted by default)
* `ca_postalcode`: Setting for certificates (omitted by default)
* `ca_organization`: Setting for certificates (omitted by default)
* `ca_organizationalunit`: Setting for certificates (omitted by default)
* `ca_common_name`: CN for certificates (default: `{{ inventory_hostname }}`)
* `ca_email`: E-Mail address for certificates (default: `root@{{ ansible_fqdn }}`)
* `ca_altname_1`: First alt name (default: `{{ ansible_fqdn }}`)
* `ca_email`: E-Mail address for certificates (omitted by default)
* `ca_subject_alternative_name`: Value for certificate `subjectAltName` field (default: `DNS:{{ ca_altname_1 }},DNS:{{ ca_altname_2 }},DNS:{{ ca_altname_3}}`, omitted if all `ca_altnameX` varaibles are `null`)
* `ca_altname_1`: First default alt name (default: `{{ ansible_hostname }}`). Omitted when set to `null`.
* `ca_altname_2`: Second default alt name (default: `{{ ansible_fqdn }}`). Omitted when set to `null`.
* `ca_altname_3`: Third default alt name (default: `{{ inventory_hostname }}`). Omitted when set to `null`.
* `ca_ca_signing_key_algorithm`: CA key generation algorithm (default: `RSA`)
* `ca_ca_signing_key_params`: CA key generation command options (empty by default)
* `ca_ca_keylength`: CA keylength (default: `2048`)
* `ca_server_cert`: Create server certificate as well (default: `true`)
* `ca_cert`: Create certificate (default skips CA host: `{{ inventory_hostname != ca_ca_host }}`). It's up to an operator to configure the certificate for TLS client or/and TLS server.
* `ca_extended_key_usage`: Configures certificate `extendedKeyUsage` field. For example, to support both client and server authentication pass `['clientAuth', 'serverAuth']` (default: omitted)
* `ca_server_cert`: Create server certificate as well (default: `false`)
* `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_keypassphrase`: Password for the leaf certificate 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`)
* `ca_client_ca_dir_owner`: User to own the certificate directory on the clients (default: `root`)
* `ca_client_ca_dir_group`: Group to own the certificate directory on the clients (default: `root`)
* `ca_client_ca_dir_mode`: Permissions of the certificate directory on the clients (default: `0700`)
* `ca_certs_dir`: Directory to place key, CA and leaf certificates on the hosts (default: `/opt/ca`)
* `ca_certs_dir_owner`: User to own the certificate directory on the hosts (default: `root`)
* `ca_certs_dir_group`: Group to own the certificate directory on the hosts (default: `root`)
* `ca_certs_dir_mode`: Permissions of the certificate directory on the hosts (default: `0700`)
* `ca_key_algorithm`: End-user key generation algorithm (default: `{{ ca_ca_signing_key_algorithm }}`)
* `ca_renew`: Renew certificates if they expire within `ca_check_valid_time` timeframe (default: `false`)
* `ca_valid_time`: Valid time of new created certificates (default: `+365d`)
* `ca_check_valid_time`: Timeframe to check if certificates will expire (default: `+2w`)
Expand All @@ -66,11 +76,28 @@ All of these have the default value `false`.
* `ca_ls7_workaround`: Enable pinning key parameters for a Logstash compatible key. These settings make sure the key works with a certain combination of OpenSSL and Logstash. Symptom: Logstash logs that a valid PKCS8 key is invalid.
* `ca_ls7_workaround_cipher`: The cipher to use for the workaround (default: `PBE-SHA1-RC4-128`)

## Notification handlers

It's possible to register handlers to run actions on certificate change. For example, to reload service and use the updated certificate.

The following handler names are available for registration:

* `Ansible-role-ca : on certificate change`: runs on certificate change
* `Ansible-role-ca : on server certificate change`: runs on server certificate change
* `Ansible-role-ca : on etcd certificate change`: runs on etcd certificate change
* `Ansible-role-ca : on etcd server certificate change`: runs on etcd server certificate change


## Example Playbook ##

- hosts: all
roles:
- ca
handlers:
- name: "Ansible-role-ca : on certificate change"
ansible.builtin.systemd_service:
name: my_tls_service
state: reloaded

## Contributing ##

Expand Down
28 changes: 12 additions & 16 deletions defaults/main.yml
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
---

ca_manage_openssl: true
ca_ca_dir: /opt/ca
ca_client_ca_dir: /opt/ca
ca_client_ca_dir_owner: root
ca_client_ca_dir_group: root
ca_client_ca_dir_mode: 0700
ca_ca_password: ChangeMe
ca_ca_dir_owner: root
ca_ca_dir_group: root
ca_certs_dir: /opt/ca
ca_certs_dir_owner: root
ca_certs_dir_group: root
ca_certs_dir_mode: 0700
ca_key_algorithm: "{{ ca_ca_signing_key_algorithm }}"
ca_localdir: /tmp/ca
ca_local_become: false
ca_ca_host: localhost
ca_ca_signing_key_algorithm: RSA
ca_ca_signing_key_params: ""

ca_server_cert: true
ca_cert: "{{ inventory_hostname != ca_ca_host }}"
ca_server_cert: false
ca_logstash: false
ca_etcd: false

ca_country: EX
ca_state: EX
ca_locality: EX
ca_postalcode: 1234
ca_organization: example
ca_organizationalunit: example
ca_common_name: "{{ inventory_hostname }}"
ca_email: "root@{{ ansible_fqdn }}"
ca_altname_1: "{{ ansible_hostname }}"
ca_altname_2: "{{ ansible_fqdn }}"
ca_altname_3: "{{ inventory_hostname }}"
ca_subject_alternative_name: "{{ [ca_altname_1, ca_altname_2, ca_altname_3] | reject('none') | map('regex_replace', '^(.*)$', 'DNS:\\1') | join(',') }}"
ca_ca_keylength: 2048

ca_ls7_workaround: false
Expand All @@ -35,5 +33,3 @@ ca_renew: false
ca_ca_days: 3650
ca_valid_time: +365d
ca_check_valid_time: +2w

_ca_ca_openssl_version_3: false
11 changes: 11 additions & 0 deletions handlers/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
- name: "Ansible-role-ca : on certificate change"
ansible.builtin.meta: noop

- name: "Ansible-role-ca : on server certificate change"
ansible.builtin.meta: noop

- name: "Ansible-role-ca : on etcd certificate change"
ansible.builtin.meta: noop

- name: "Ansible-role-ca : on etcd server certificate change"
ansible.builtin.meta: noop
25 changes: 21 additions & 4 deletions molecule/ca-renew/converge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,30 @@
hosts: all
vars:
ca_ca_host: ca_default
ca_ca_password: ChangeMe
# for CA server separate CA and cert client directories allow to trigger notify action on client certificate change
ca_certs_dir: /opt/certs
ca_cert: true
ca_server_cert: true
ca_logstash: true
ca_etcd: true
ca_etcd_group: molecule
ca_ca_days: 3650
ca_valid_time: +365d
ca_renew: true
tasks:
- name: "Include CA role"
include_role:
name: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}"
roles:
- role: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}"
- role: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}"
vars:
ca_ca_signing_key_algorithm: Ed25519
ca_ca_dir: /opt/ca-ed25519
ca_certs_dir: /opt/certs-ed25519
ca_localdir: /tmp/ca-ed25519

handlers:
# runs once for both CAs for each notified host
- name: "Ansible-role-ca : on certificate change"
ansible.builtin.file:
path: "{{ ansible_env.HOME }}/{{ inventory_hostname }}.renewed_certificate"
state: touch
mode: 0600
34 changes: 26 additions & 8 deletions molecule/ca-renew/prepare.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,53 @@
hosts: all
vars:
ca_ca_host: ca_default
ca_ca_password: ChangeMe
# for CA server separate CA and cert client directories allow to trigger notify action on client certificate change
ca_certs_dir: /opt/certs
ca_cert: true
ca_server_cert: true
ca_logstash: true
ca_etcd: true
ca_etcd_group: molecule
ca_ca_days: 5
ca_valid_time: "+14d"
ca_renew: true
tasks:

pre_tasks:
- name: Install Python libraries
pip:
ansible.builtin.pip:
name: cryptography>= 1.2.3

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

- name: Start NetworkManager
service:
ansible.builtin.service:
name: NetworkManager
state: started
enabled: yes
when: ansible_os_family == "RedHat"

- name: Gather facts again to define ansible_default_ipv4
setup:
ansible.builtin.setup:

- name: "Include CA role"
include_role:
name: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}"
roles:
- role: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}"
- role: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}"
vars:
ca_ca_signing_key_algorithm: Ed25519
ca_ca_dir: /opt/ca-ed25519
ca_certs_dir: /opt/certs-ed25519
ca_localdir: /tmp/ca-ed25519

handlers:
# runs once for both CAs for each notified host
- name: "Ansible-role-ca : on certificate change"
ansible.builtin.file:
path: "{{ ansible_env.HOME }}/{{ inventory_hostname }}.initial_certificate"
state: touch
mode: 0600
Loading