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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ jobs:
./foremanctl deploy \
--certificate-source=${{ matrix.certificate_source }} \
${{ matrix.database == 'external' && '--database-mode=external --database-host=database.example.com --database-ssl-ca $(pwd)/.var/lib/foremanctl/db-ca.crt --database-ssl-mode verify-full' || '' }} \
${{ matrix.certificate_source == 'custom_server' && '--certificate-server-certificate /root/custom-certificates/certs/quadlet.example.com.crt --certificate-server-key /root/custom-certificates/private/quadlet.example.com.key --certificate-server-ca-certificate /root/custom-certificates/certs/server-ca.crt' || '' }} \
${{ matrix.certificate_source == 'custom_server' && '--certificate-server-certificate /root/custom-certificates/quadlet.example.com/certs/quadlet.example.com.crt --certificate-server-key /root/custom-certificates/quadlet.example.com/private/quadlet.example.com.key --certificate-server-ca-certificate /root/custom-certificates/certs/ca.crt' || '' }} \
--initial-admin-password=changeme \
--initial-organization "Foreman CI" \
--initial-location "Internet" \
Expand Down
23 changes: 18 additions & 5 deletions development/playbooks/custom-certs/custom-certs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,23 @@
- quadlet
become: true
vars:
certificates_ca_directory: /root/custom-certificates
_custom_certs_root: /root/custom-certificates
certificates_ca_password: "CUSTOMCA"
certificates_ca_subject: 'Custom Test CA'
certificates_hostnames:
- "{{ ansible_facts['fqdn'] }}"
roles:
- role: certificates
pre_tasks:
- name: Generate custom CA
ansible.builtin.include_role:
name: certificates
vars:
certificates_ca_directory: "{{ _custom_certs_root }}"
certificates_hostnames: []

- name: Generate host certificates
ansible.builtin.include_role:
name: certificates
vars:
certificates_ca: false
certificates_signing_ca_directory: "{{ _custom_certs_root }}"
certificates_ca_directory: "{{ _custom_certs_root }}/{{ hostname | default(ansible_facts['fqdn']) }}"
certificates_hostnames:
- "{{ hostname | default(ansible_facts['fqdn']) }}"
8 changes: 8 additions & 0 deletions development/playbooks/custom-certs/metadata.obsah.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
help: |
Generate custom certificates for testing

variables:
hostname:
help: Additional hostname to generate certificates for.
parameter: --hostname
47 changes: 47 additions & 0 deletions docs/user/certificates.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,53 @@ foremanctl deploy --certificate-renew

The `--certificate-renew` flag is **not persisted** in foremanctl’s answers file (one-shot).

### Certificate Bundle for Secondary Systems

The `certificate-bundle` command generates a certificate tarball for a secondary system such as a foreman-proxy host. This is the foremanctl equivalent of `foreman-proxy-certs-generate`.

The internal CA must already exist from a prior `foremanctl deploy`.

#### Usage

```bash
# Generate a certificate bundle using the internal CA
foremanctl certificate-bundle proxy.example.com

# Generate a certificate bundle with custom server certificates for the proxy
foremanctl certificate-bundle \
--certificate-server-certificate /path/to/proxy.example.com.crt \
--certificate-server-key /path/to/proxy.example.com.key \
--certificate-server-ca-certificate /path/to/ca.crt \
proxy.example.com
```

The command generates certificates for the given hostname and packages them into a tarball at `/root/<hostname>.tar.gz`.

When no custom certificate flags are provided, all certificates (server and client) are generated by the internal CA. When custom server certificates are provided, the custom cert and key are used for the proxy's server certificates while client certificates are still generated by the internal CA.

If the Foreman server was deployed with custom server certificates, each proxy must also have its own distinct custom server certificate. The three `--certificate-server-*` flags must be provided together.

#### Tarball Contents

The tarball follows the `ssl-build` directory layout:

```
ssl-build/
├── katello-default-ca.crt # Internal CA certificate
├── katello-server-ca.crt # CA that signed the server certificate
└── <hostname>/
├── <hostname>-apache.crt # Server certificate (HTTPS)
├── <hostname>-apache.key # Server private key
├── <hostname>-foreman-proxy.crt # Proxy server certificate
├── <hostname>-foreman-proxy.key # Proxy server private key
├── <hostname>-foreman-proxy-client.crt # Client certificate (proxy-to-Foreman)
├── <hostname>-foreman-proxy-client.key # Client private key
├── <hostname>-puppet-client.crt # Puppet client certificate
└── <hostname>-puppet-client.key # Puppet client private key
```

When using the internal CA only, `katello-server-ca.crt` and `katello-default-ca.crt` are the same certificate. When custom server certificates are provided, `katello-server-ca.crt` contains the custom CA and `katello-default-ca.crt` contains the internal CA.

### Current Limitations

- Uses the same lifetime for both client and server certificates
Expand Down
43 changes: 43 additions & 0 deletions src/playbooks/certificate-bundle/certificate-bundle.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
- name: Generate a certificate bundle for a hostname
hosts:
- quadlet
become: true
vars_files:
- "../../vars/defaults.yml"
vars:
_certificates_root: /root/certificates
certificates_ca: false
certificates_source: default
certificates_signing_ca_directory: "{{ _certificates_root }}"
certificates_ca_directory: "{{ _certificates_root }}/hosts/{{ hostname }}"
certificates_hostnames:
- "{{ hostname }}"
_ca_crt: "{{ _certificates_root }}/certs/ca.crt"
_host_crt: "{{ certificates_ca_directory }}/certs/{{ hostname }}.crt"
_host_key: "{{ certificates_ca_directory }}/private/{{ hostname }}.key"
pre_tasks:
- name: Read CA password from remote host
ansible.builtin.slurp:
src: "{{ _certificates_root }}/private/ca.pwd"
register: _ca_pwd_file
no_log: true

- name: Set CA password
ansible.builtin.set_fact:
certificates_ca_password: "{{ _ca_pwd_file.content | b64decode }}"
no_log: true
roles:
- role: certificates
- role: certificate_bundle
vars:
certificate_bundle_hostname: "{{ hostname }}"
certificate_bundle_ca_certificate: "{{ _ca_crt }}"
certificate_bundle_server_ca_certificate: >-
{{ certificates_custom_server_ca_certificate | default(_ca_crt) }}
certificate_bundle_server_certificate: >-
{{ certificates_custom_server_certificate | default(_host_crt) }}
certificate_bundle_server_key: >-
{{ certificates_custom_server_key | default(_host_key) }}
certificate_bundle_client_certificate: "{{ certificates_ca_directory }}/certs/{{ hostname }}-client.crt"
certificate_bundle_client_key: "{{ certificates_ca_directory }}/private/{{ hostname }}-client.key"
33 changes: 33 additions & 0 deletions src/playbooks/certificate-bundle/metadata.obsah.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
help: |
Generate a certificate bundle

variables:
hostname:
parameter: hostname
help: Hostname to generate a certificate bundle for that will be the common name.
Comment thread
evgeni marked this conversation as resolved.
certificates_custom_server_certificate:
help: Path to a custom server certificate for the proxy.
type: AbsolutePath
parameter: --certificate-server-certificate
persist: false
certificates_custom_server_key:
help: Path to the private key for the custom server certificate.
type: AbsolutePath
parameter: --certificate-server-key
persist: false
certificates_custom_server_ca_certificate:
help: Path to the CA certificate that signed the custom server certificate.
type: AbsolutePath
parameter: --certificate-server-ca-certificate
persist: false

certificates_renew:
help: Regenerate certificates for this hostname.
parameter: --certificate-renew
action: store_true
persist: false

constraints:
required_together:
- [certificates_custom_server_certificate, certificates_custom_server_key, certificates_custom_server_ca_certificate]
3 changes: 3 additions & 0 deletions src/roles/certificate_bundle/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
certificate_bundle_output_directory: /root
certificate_bundle_server_ca_certificate: "{{ certificate_bundle_ca_certificate }}"
90 changes: 90 additions & 0 deletions src/roles/certificate_bundle/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
- name: Check for existing certificate bundle
ansible.builtin.stat:
path: "{{ certificate_bundle_output_directory }}/{{ certificate_bundle_hostname }}.tar.gz"
register: _certificate_bundle_tarball

- name: Build certificate bundle
when: not _certificate_bundle_tarball.stat.exists or (certificates_renew | default(false) | bool)
block:
- name: Create temporary directory
ansible.builtin.tempfile:
state: directory
suffix: certificate-build
register: certificate_bundle_build_directory

- name: Create directory structure
ansible.builtin.file:
state: directory
path: "{{ certificate_bundle_build_directory.path }}/ssl-build/{{ certificate_bundle_hostname }}"
mode: '0755'

- name: Copy default CA certificate
ansible.builtin.copy:
src: "{{ certificate_bundle_ca_certificate }}"
dest: "{{ certificate_bundle_build_directory.path }}/ssl-build/katello-default-ca.crt"
remote_src: true
mode: '0444'

- name: Copy server CA certificate
ansible.builtin.copy:
src: "{{ certificate_bundle_server_ca_certificate }}"
dest: "{{ certificate_bundle_build_directory.path }}/ssl-build/katello-server-ca.crt"
remote_src: true
mode: '0444'

- name: Copy server certificate
ansible.builtin.copy:
src: "{{ certificate_bundle_server_certificate }}"
dest: "{{ certificate_bundle_build_directory.path }}/ssl-build/{{ certificate_bundle_hostname }}/{{ certificate_bundle_hostname }}-{{ item }}"
remote_src: true
mode: '0444'
loop:
- apache.crt
- foreman-proxy.crt

- name: Copy server key
ansible.builtin.copy:
src: "{{ certificate_bundle_server_key }}"
dest: "{{ certificate_bundle_build_directory.path }}/ssl-build/{{ certificate_bundle_hostname }}/{{ certificate_bundle_hostname }}-{{ item }}"
remote_src: true
mode: '0600'
loop:
- apache.key
- foreman-proxy.key

- name: Copy client certificate
ansible.builtin.copy:
src: "{{ certificate_bundle_client_certificate }}"
dest: "{{ certificate_bundle_build_directory.path }}/ssl-build/{{ certificate_bundle_hostname }}/{{ certificate_bundle_hostname }}-{{ item }}"
remote_src: true
mode: '0444'
loop:
- foreman-proxy-client.crt
- puppet-client.crt

- name: Copy client key
ansible.builtin.copy:
src: "{{ certificate_bundle_client_key }}"
dest: "{{ certificate_bundle_build_directory.path }}/ssl-build/{{ certificate_bundle_hostname }}/{{ certificate_bundle_hostname }}-{{ item }}"
remote_src: true
mode: '0600'
loop:
- foreman-proxy-client.key
- puppet-client.key

- name: Create tarball
community.general.archive:
path: "{{ certificate_bundle_build_directory.path }}/ssl-build"
dest: "{{ certificate_bundle_output_directory }}/{{ certificate_bundle_hostname }}.tar.gz"
format: gz
mode: '0640'

- name: Remove temporary directory
ansible.builtin.file:
path: "{{ certificate_bundle_build_directory.path }}"
state: absent

- name: Report certificate bundle location
ansible.builtin.debug:
msg: "Certificate bundle created: {{ certificate_bundle_output_directory }}/{{ certificate_bundle_hostname }}.tar.gz"
1 change: 1 addition & 0 deletions src/roles/certificates/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ certificates_ca_directory: /root/certificates # Change this to /var/lib?
certificates_ca_directory_keys: "{{ certificates_ca_directory }}/private"
certificates_ca_directory_certs: "{{ certificates_ca_directory }}/certs"
certificates_ca_directory_requests: "{{ certificates_ca_directory }}/requests"
certificates_signing_ca_directory: "{{ certificates_ca_directory }}"
certificates_ca_subject: 'Foreman Self-signed CA'
certificates_cnames: []
certificates_algorithm_type: RSA
Expand Down
18 changes: 14 additions & 4 deletions src/roles/certificates/tasks/issue.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
---
- name: Ensure certificate directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- "{{ certificates_ca_directory_certs }}"
- "{{ certificates_ca_directory_keys }}"
- "{{ certificates_ca_directory_requests }}"

- name: Issue server certificate
when:
- (certificates_source != 'custom_server') or (certificates_hostname == 'localhost')
Expand Down Expand Up @@ -29,8 +39,8 @@
path: "{{ certificates_ca_directory_certs }}/{{ certificates_hostname }}.crt"
csr_path: "{{ certificates_ca_directory_requests }}/{{ certificates_hostname }}.csr"
provider: ownca
ownca_path: "{{ certificates_ca_directory_certs }}/ca.crt"
ownca_privatekey_path: "{{ certificates_ca_directory_keys }}/ca.key"
ownca_path: "{{ certificates_signing_ca_directory }}/certs/ca.crt"
ownca_privatekey_path: "{{ certificates_signing_ca_directory }}/private/ca.key"
ownca_privatekey_passphrase: "{{ certificates_ca_password }}"
ownca_not_after: "+{{ certificates_validity_days }}d"
force: "{{ certificates_renew | bool }}"
Expand Down Expand Up @@ -60,8 +70,8 @@
path: "{{ certificates_ca_directory_certs }}/{{ certificates_hostname }}-client.crt"
csr_path: "{{ certificates_ca_directory_requests }}/{{ certificates_hostname }}-client.csr"
provider: ownca
ownca_path: "{{ certificates_ca_directory_certs }}/ca.crt"
ownca_privatekey_path: "{{ certificates_ca_directory_keys }}/ca.key"
ownca_path: "{{ certificates_signing_ca_directory }}/certs/ca.crt"
ownca_privatekey_path: "{{ certificates_signing_ca_directory }}/private/ca.key"
ownca_privatekey_passphrase: "{{ certificates_ca_password }}"
ownca_not_after: "+{{ certificates_validity_days }}d"
force: "{{ certificates_renew | bool }}"
Loading
Loading