Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .ostree/packages-runtime.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
ca-certificates
certmonger
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ ansible-galaxy collection install -vv -r meta/collection-requirements.yml
|-------------------------|----------------------------------------------------------------------------------------------------------------|:----:|:--------:|-------------------|
| certificate_wait | If the task should wait for the certificate to be issued. | bool | no | yes |
| certificate\_requests | A list of dicts representing each certificate to be issued. See [certificate_requests](#certificate_requests). | list | no | - |
| certificate\_trust | A list of dicts representing certificates to install into the system trust store. See [certificate_trust](#certificate_trust). | list | no | - |

### certificate_requests

Expand Down Expand Up @@ -141,6 +142,35 @@ If `extended_key_usage` is not set the role will default to:
* id-kp-serverAuth
* id-kp-clientAuth

### certificate_trust

Each item in the `certificate_trust` list describes a certificate to install
into (or remove from) the system trust store, so that TLS connections to
services using those certificates are trusted system-wide.

| Parameter | Description | Type | Required | Default |
|-------------|-------------------------------------------------------------------------------------------------|:----:|:--------:|---------|
| name | Base file name of the trust anchor. The certificate is installed as `<name>.crt` in the trust anchors directory. | str | yes | - |
| content | Inline PEM content of the certificate. | str | no | - |
| src | Path of a certificate file on the control node to copy to the trust anchors directory. | str | no | - |
| remote\_src | If `true`, `src` is a path on the managed host instead of the control node. | bool | no | no |
| url | URL to download the certificate from. | str | no | - |
| state | `present` to install the certificate, `absent` to remove it from the trust store. | str | no | present |

Exactly one of `content`, `src`, or `url` must be given when `state` is
`present`. Installed files are owned by `root:root` with mode `0644`. When
any trust anchor is added or removed, the role updates the system trust
store by running the platform specific update command.

The trust anchors directory and update command per platform:

* RHEL/CentOS/Fedora: `/etc/pki/ca-trust/source/anchors/`, updated with
`update-ca-trust extract`
* SLES/openSUSE: `/etc/pki/trust/anchors/`, updated with
`update-ca-certificates`
* Debian/Ubuntu: `/usr/local/share/ca-certificates/`, updated with
`update-ca-certificates`

### run hooks

Sometimes you need to execute a command just before a certificate is
Expand All @@ -163,6 +193,10 @@ CA represents the CA certificates that will be used to issue and sign the
requested certificate. Provider represents the method used to send the certificate
request to the CA and then retrieve the signed certificate.

The provider packages (for example `certmonger`) and services are only
installed and configured when `certificate_requests` is not empty. Using
only `certificate_trust` does not install any provider.

If a user chooses `self-sign` CA, with `certmonger` as provider and, later on
decide to change the provider to `openssl`, the CA certificates used in both
cases needs to be the same. *Please note that `openssl` is **not yet a supported**
Expand Down Expand Up @@ -232,6 +266,43 @@ The example below creates a certificate file in
- linux-system-roles.certificate
```

### Adding certificates to the system trust store

Install an internal CA certificate into the system trust store, one from
inline PEM content and one from a file on the control node.

```yaml
---
- hosts: webserver
vars:
certificate_trust:
- name: internal-ca
content: |
-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
...
-----END CERTIFICATE-----
- name: partner-ca
src: files/partner-ca.crt

roles:
- linux-system-roles.certificate
```

To remove a certificate from the trust store, use `state: absent`:

```yaml
---
- hosts: webserver
vars:
certificate_trust:
- name: partner-ca
state: absent

roles:
- linux-system-roles.certificate
```

### Issuing certificates with multiple DNS, IP and Email

```yaml
Expand Down
10 changes: 10 additions & 0 deletions defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,13 @@

certificate_requests: []
certificate_wait: true

# A list of dicts describing certificates to install into the system
# trust store. Each item supports the following keys:
# name (required) - base file name of the trust anchor
# content - inline PEM content of the certificate
# src - path of a certificate file to copy to the trust store
# remote_src - if true, src is a path on the managed host
# url - URL to download the certificate from
# state - present (default) or absent to remove an anchor
certificate_trust: []
74 changes: 74 additions & 0 deletions tasks/certificate_trust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# SPDX-License-Identifier: MIT
---
- name: Validate certificate_trust items
assert:
that:
- item.name is defined
- item.state | d('present') in ['present', 'absent']
- >-
item.state | d('present') == 'absent' or
[item.content is defined, item.src is defined, item.url is defined] |
select | list | length == 1
fail_msg: >-
Each certificate_trust item must have a name, state must be 'present'
or 'absent', and exactly one of content, src, or url must be given
when state is 'present'
loop: "{{ certificate_trust }}"
loop_control:
label: "{{ item.name | d('unnamed') }}"

- name: Ensure trust store packages are installed
package:
name: "{{ __certificate_trust_packages }}"
state: present
use: "{{ (__certificate_is_ostree | d(false)) |
ternary('ansible.posix.rhel_rpm_ostree', omit) }}"

- name: Install trust certificates
copy:
content: "{{ item.content | default(omit) }}"
src: "{{ item.src | default(omit) }}"
remote_src: "{{ item.remote_src | default(omit) }}"
dest: "{{ __certificate_trust_dir }}/{{ item.name }}.crt"
owner: root
group: root
mode: "0644"
loop: "{{ certificate_trust }}"
loop_control:
label: "{{ item.name }}"
when:
- item.state | d('present') == 'present'
- item.content is defined or item.src is defined
register: __certificate_trust_copied
Comment thread
Klaas- marked this conversation as resolved.

- name: Install trust certificates from URL
get_url:
url: "{{ item.url }}"
dest: "{{ __certificate_trust_dir }}/{{ item.name }}.crt"
owner: root
group: root
mode: "0644"
loop: "{{ certificate_trust }}"
loop_control:
label: "{{ item.name }}"
when:
- item.state | d('present') == 'present'
- item.url is defined
register: __certificate_trust_downloaded
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
Klaas- marked this conversation as resolved.

- name: Remove trust certificates
file:
path: "{{ __certificate_trust_dir }}/{{ item.name }}.crt"
state: absent
loop: "{{ certificate_trust }}"
loop_control:
label: "{{ item.name }}"
when: item.state | d('present') == 'absent'
register: __certificate_trust_removed

- name: Update system trust store
command: "{{ __certificate_update_trust_cli }}"
changed_when: true
when: __certificate_trust_copied is changed
or __certificate_trust_downloaded is changed
or __certificate_trust_removed is changed
21 changes: 13 additions & 8 deletions tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@
- name: Set version specific variables
include_tasks: tasks/set_vars.yml

- name: Ensure certificate role dependencies are installed
package:
name: "{{ __certificate_packages }}"
state: present
use: "{{ (__certificate_is_ostree | d(false)) |
ternary('ansible.posix.rhel_rpm_ostree', omit) }}"

- name: Install and configure required providers
- name: Install and configure requirements and providers
when: certificate_requests | length > 0
vars:
__certificate_providers: "{{ (certificate_requests | selectattr('provider', 'defined') | map(attribute='provider') | list) +
[__certificate_provider_default] | unique | list }}"
block:
- name: Ensure certificate role dependencies are installed
package:
name: "{{ __certificate_packages }}"
state: present
use: "{{ (__certificate_is_ostree | d(false)) |
ternary('ansible.posix.rhel_rpm_ostree', omit) }}"

- name: Ensure provider packages are installed
package:
name: "{{ __certificate_provider_vars[__certificate_provider].packages }}"
Expand Down Expand Up @@ -86,6 +87,10 @@
loop_var: __certificate_provider
when: __certificate_provider_vars[__certificate_provider].service is defined

- name: Ensure certificates in the system trust store
include_tasks: tasks/certificate_trust.yml
when: certificate_trust | length > 0

- name: Ensure certificate requests
certificate_request:
name: "{{ item.name | default(omit) }}"
Expand Down
3 changes: 2 additions & 1 deletion tests/setup-snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@

- name: Install test packages
package:
name: "{{ __certificate_packages }}"
name: "{{ __certificate_packages + __certificate_trust_packages +
['openssl'] }}"
state: present
Loading