Skip to content

Commit f1dba59

Browse files
pablomhclaude
andcommitted
Issue a dedicated TLS certificate for the Candlepin container hostname
foreman-certs generates certificates only for localhost and the server FQDN. Now that Foreman connects to Candlepin via the bridge DNS name "candlepin", TLS hostname validation fails against those certificates. A dedicated certificate with SAN=candlepin, signed by the installer CA, is generated using the openssl CLI (consistent with the rest of the codebase). The certificate validity is set to 7300 days (20 years), matching the default used by puppet-certs (theforeman/puppet-certs manifests/init.pp $expiration parameter). The signing step runs on every deployment to ensure the certificate is always freshly dated, consistent with how foreman-certs handles the other installer certificates. The localhost certificate previously used by Candlepin's Tomcat is now unused and removed: - "localhost" dropped from certificates_hostnames — the certificates role no longer generates the cert. - localhost_key / localhost_certificate removed from default_certificates.yml and installer_certificates.yml. The healthcheck is updated to validate the new certificate instead of skipping verification with --insecure. --resolve candlepin:23443:127.0.0.1 forces the connection to the loopback so it works in both bridge networking (where other containers reach Candlepin by DNS name) and host networking (where "candlepin" would not resolve via container DNS). Tests are updated to route connectivity checks through the foreman container (which shares the same bridge network as candlepin), to verify against the new certificate, and to check candlepin_certificate expiry instead of the now-removed localhost_certificate. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8bc989f commit f1dba59

7 files changed

Lines changed: 65 additions & 38 deletions

File tree

development/roles/foreman_installer_certs/tasks/main.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,47 @@
1414
- name: Generate certs
1515
ansible.builtin.command: foreman-certs --apache true --foreman true --candlepin true
1616
changed_when: false
17+
18+
# foreman-certs only generates certs for localhost and the server FQDN. Candlepin
19+
# runs in its own container and is reachable by other containers via the DNS name
20+
# "candlepin" on the bridge network. Foreman validates the TLS certificate hostname
21+
# when connecting to https://candlepin:23443, so a dedicated cert with SAN=candlepin
22+
# is required.
23+
- name: Create candlepin cert directory
24+
ansible.builtin.file:
25+
path: /root/ssl-build/candlepin
26+
state: directory
27+
mode: '0755'
28+
29+
- name: Generate candlepin Tomcat private key
30+
ansible.builtin.command: >
31+
openssl genrsa
32+
-out /root/ssl-build/candlepin/candlepin-tomcat.key
33+
4096
34+
args:
35+
creates: /root/ssl-build/candlepin/candlepin-tomcat.key
36+
37+
- name: Generate candlepin Tomcat certificate signing request
38+
ansible.builtin.command: >
39+
openssl req
40+
-new
41+
-key /root/ssl-build/candlepin/candlepin-tomcat.key
42+
-subj "/CN=candlepin"
43+
-addext "subjectAltName = DNS:candlepin"
44+
-out /root/ssl-build/candlepin/candlepin-tomcat.csr
45+
args:
46+
creates: /root/ssl-build/candlepin/candlepin-tomcat.csr
47+
48+
- name: Sign candlepin certificate with installer CA
49+
ansible.builtin.command: >
50+
openssl x509
51+
-req
52+
-in /root/ssl-build/candlepin/candlepin-tomcat.csr
53+
-CA /root/ssl-build/katello-default-ca.crt
54+
-CAkey /root/ssl-build/katello-default-ca.key
55+
-CAcreateserial
56+
-days 7300
57+
-passin "file:/root/ssl-build/katello-default-ca.pwd"
58+
-copy_extensions copy
59+
-out /root/ssl-build/candlepin/candlepin-tomcat.crt
60+
changed_when: true

src/roles/candlepin/tasks/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
After=foreman.target redis.service postgresql.service
101101
[Service]
102102
TimeoutStartSec=300
103-
healthcheck: curl --fail --insecure https://localhost:23443/candlepin/status
103+
healthcheck: curl --fail --cacert /etc/candlepin/certs/candlepin-ca.crt --resolve candlepin:23443:127.0.0.1 https://candlepin:23443/candlepin/status
104104
sdnotify: healthy
105105

106106
- name: Run daemon reload to make Quadlet create the service files

src/vars/base.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
certificates_hostnames:
33
- "{{ ansible_facts['fqdn'] }}"
4-
- localhost
4+
- candlepin
55

66
certificates_ca_password: "CHANGEME"
77
candlepin_keystore_password: "CHANGEME"
@@ -10,8 +10,8 @@ candlepin_oauth_secret: "CHANGEME"
1010
candlepin_ca_key_password: "{{ ca_key_password }}"
1111
candlepin_ca_key: "{{ ca_key }}"
1212
candlepin_ca_certificate: "{{ ca_certificate }}"
13-
candlepin_tomcat_key: "{{ localhost_key }}"
14-
candlepin_tomcat_certificate: "{{ localhost_certificate }}"
13+
candlepin_tomcat_key: "{{ candlepin_key }}"
14+
candlepin_tomcat_certificate: "{{ candlepin_certificate }}"
1515
candlepin_client_key: "{{ client_key }}"
1616
candlepin_client_certificate: "{{ client_certificate }}"
1717

src/vars/default_certificates.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ server_ca_certificate: "{{ certificates_ca_directory }}/certs/ca.crt"
99
client_certificate: "{{ certificates_ca_directory }}/certs/{{ ansible_facts['fqdn'] }}-client.crt"
1010
client_key: "{{ certificates_ca_directory }}/private/{{ ansible_facts['fqdn'] }}-client.key"
1111
client_ca_certificate: "{{ certificates_ca_directory }}/certs/ca.crt"
12-
localhost_key: "{{ certificates_ca_directory }}/private/localhost.key"
13-
localhost_certificate: "{{ certificates_ca_directory }}/certs/localhost.crt"
12+
candlepin_key: "{{ certificates_ca_directory }}/private/candlepin.key"
13+
candlepin_certificate: "{{ certificates_ca_directory }}/certs/candlepin.crt"

src/vars/installer_certificates.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ server_ca_certificate: "/root/ssl-build/katello-server-ca.crt"
88
client_certificate: "/root/ssl-build/{{ ansible_facts['fqdn'] }}/{{ ansible_facts['fqdn'] }}-foreman-client.crt"
99
client_key: "/root/ssl-build/{{ ansible_facts['fqdn'] }}/{{ ansible_facts['fqdn'] }}-foreman-client.key"
1010
client_ca_certificate: "{{ ca_certificate }}"
11-
localhost_key: "/root/ssl-build/localhost/localhost-tomcat.key"
12-
localhost_certificate: "/root/ssl-build/localhost/localhost-tomcat.crt"
11+
candlepin_key: "/root/ssl-build/candlepin/candlepin-tomcat.key"
12+
candlepin_certificate: "/root/ssl-build/candlepin/candlepin-tomcat.crt"

tests/candlepin_test.py

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import re
2-
31

42
def assert_secret_content(server, secret_name, secret_value):
53
secret = server.run(f'podman secret inspect --format {"{{.SecretData}}"} --showsecret {secret_name}')
@@ -12,24 +10,14 @@ def test_candlepin_service(server):
1210
assert candlepin.is_running
1311

1412

15-
def test_candlepin_port(server):
16-
candlepin = server.addr("localhost")
17-
assert candlepin.port("23443").is_reachable
18-
19-
20-
def test_candlepin_status(server, certificates):
21-
status = server.run(f"curl --cacert {certificates['ca_certificate']} --resolve candlepin:23443:127.0.0.1 --silent --output /dev/null --write-out '%{{http_code}}' https://candlepin:23443/candlepin/status")
13+
def test_candlepin_status(server):
14+
status = server.run("podman exec foreman curl --cacert /etc/foreman/katello-default-ca.crt --silent --output /dev/null --write-out '%{http_code}' https://candlepin:23443/candlepin/status")
2215
assert status.succeeded
2316
assert status.stdout == '200'
2417

2518

26-
def test_artemis_port(server):
27-
candlepin = server.addr("localhost")
28-
assert candlepin.port("61613").is_reachable
29-
30-
31-
def test_artemis_auth(server, certificates):
32-
cmd = server.run(f'echo "" | openssl s_client -CAfile {certificates["ca_certificate"]} -cert {certificates["client_certificate"]} -key {certificates["client_key"]} -connect 127.0.0.1:61613 -servername candlepin')
19+
def test_artemis_auth(server):
20+
cmd = server.run('podman exec foreman bash -c \'echo "" | openssl s_client -CAfile /etc/foreman/katello-default-ca.crt -cert /etc/foreman/client_cert.pem -key /etc/foreman/client_key.pem -connect candlepin:61613 -servername candlepin\'')
3321
assert cmd.succeeded, f"exit: {cmd.rc}\n\nstdout:\n{cmd.stdout}\n\nstderr:\n{cmd.stderr}"
3422

3523

@@ -40,21 +28,16 @@ def test_certs_users_file(server, certificates):
4028

4129

4230
def test_tls(server):
43-
result = server.run('nmap -sT --script +ssl-enum-ciphers localhost -p 23443')
44-
result = result.stdout
45-
# We don't enable TLSv1.3 by default yet. TLSv1.3 support was added in tomcat 7.0.92
46-
# But tomcat 7.0.76 is the latest version available on EL7
47-
assert "TLSv1.3" not in result
48-
49-
# Test that TLSv1.2 is enabled
50-
assert "TLSv1.2" in result
31+
ca = '/etc/foreman/katello-default-ca.crt'
5132

52-
# Test that older TLS versions are disabled
53-
assert "TLSv1.1" not in result
54-
assert "TLSv1.0" not in result
33+
# TLSv1.2 should be enabled
34+
result = server.run(f'podman exec foreman bash -c "echo Q | openssl s_client -connect candlepin:23443 -tls1_2 -CAfile {ca} 2>&1"')
35+
assert "Cipher is" in result.stdout, f"TLSv1.2 not available:\n{result.stdout}"
5536

56-
# Test that the least cipher strength is "strong" or "A"
57-
assert "least strength: A" in result
37+
# TLSv1.3, TLSv1.1 and TLSv1.0 should be disabled
38+
for flag in ['-tls1_3', '-tls1_1', '-tls1']:
39+
result = server.run(f'podman exec foreman bash -c "echo Q | openssl s_client -connect candlepin:23443 {flag} -CAfile {ca} 2>&1"')
40+
assert result.rc != 0, f"TLS version ({flag}) should be disabled:\n{result.stdout}"
5841

5942

6043
def test_cert_roles(server):

tests/certificates_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ def certificate_info(server, certificate):
66
openssl_result = server.run(f"openssl x509 -in {certificate} -noout -enddate -dateopt iso_8601 -subject -issuer")
77
return dict([x.split('=', 1) for x in openssl_result.stdout.splitlines()])
88

9-
@pytest.mark.parametrize("certificate_type", ['ca_certificate', 'server_certificate', 'client_certificate', 'localhost_certificate'])
9+
@pytest.mark.parametrize("certificate_type", ['ca_certificate', 'server_certificate', 'client_certificate', 'candlepin_certificate'])
1010
def test_certificate_expiry(server, certificates, certificate_type):
1111
openssl_data = certificate_info(server, certificates[certificate_type])
1212
not_after = dateutil.parser.parse(openssl_data['notAfter'])

0 commit comments

Comments
 (0)