Skip to content

Commit 19678c6

Browse files
committed
Support Let's Encrypt certs for Ironic HTTPS vmedia server
Add optional lzBmcHostname variable so that publicly trusted certificates (e.g. Let's Encrypt) can be used for the Ironic HTTPS vmedia server. Let's Encrypt only issues DNS SANs, not IP SANs, so the previous requirement that the cert SAN cover lzBmcIP was incompatible with LE. When lzBmcHostname is set, IRONIC_EXTERNAL_IP is set to the hostname instead of lzBmcIP, producing vmedia URLs of the form https://<hostname>:6183/... that the BMC can resolve via DNS. The PROVISIONING_IP (used by Apache for socket binding) remains lzBmcIP. Cert SAN validation in validations.sh is updated to check against the hostname when set, falling back to lzBmcIP for the existing IP-SAN flow. generate_ironic_cert.sh also emits a DNS+IP SAN when a hostname is configured, keeping CI self-signed certs working in both modes. Signed-off-by: Rafa Porres Molina <rporresm@redhat.com> Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4344047 commit 19678c6

9 files changed

Lines changed: 54 additions & 9 deletions

File tree

config/certificates.example.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ sslCACertificate: |
5050
5151
# Ironic HTTP Server TLS Certificate (optional)
5252
# Required when serving boot ISOs over HTTPS to the BMC.
53-
# The SAN on this certificate must cover lzBmcIP.
53+
# The SAN on this certificate must cover lzBmcHostname (DNS SAN, for Let's Encrypt certs)
54+
# or lzBmcIP (IP SAN, for private CA certs). Set lzBmcHostname in global.yaml when using
55+
# a publicly trusted certificate such as Let's Encrypt.
5456
ironicHTTPSCertificate: ""
5557

5658
ironicHTTPSKey: ""

config/global.example.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ rendezvousIP: YOUR_RENDEZVOUS_IP # Example: 192.168.2.24
2929
# LZ IP where HTTP server serves the installation ISO to IPMI nodes
3030
lzBmcIP: YOUR_LZ_WEB_SERVER_IP # Example: 100.64.1.10
3131

32+
# DNS hostname for the LZ BMC interface (optional).
33+
# When set, Ironic HTTPS vmedia URLs use this hostname instead of lzBmcIP.
34+
# Required when using publicly trusted certificates (e.g. Let's Encrypt), which
35+
# only support DNS SANs. The hostname must resolve to lzBmcIP from the BMC network.
36+
# lzBmcHostname: mirror.example.com
37+
3238
# ============================================================================
3339
# Quay Registry Configuration
3440
# ============================================================================

docs/CONFIGURATION_REFERENCE.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,25 @@ lzBmcIP: 100.64.1.10
233233
- ISO will be served at `http://{{ lzBmcIP }}/assisted/agent.x86_64.iso`
234234
- Ensure HTTP server (Apache/Nginx) is running on this host
235235

236+
#### `lzBmcHostname`
237+
238+
**Description**: DNS hostname for the LZ BMC interface. Optional. When set, Ironic constructs HTTPS vmedia URLs using this hostname instead of `lzBmcIP`.
239+
240+
**Type**: String (DNS hostname)
241+
242+
**Required**: No. Set this when using publicly trusted TLS certificates (e.g. Let's Encrypt) for the Ironic vmedia server. Let's Encrypt only issues DNS SANs, not IP SANs, so the certificate SAN must match a hostname rather than an IP address.
243+
244+
**Example**:
245+
```yaml
246+
lzBmcHostname: mirror.example.com
247+
```
248+
249+
**Notes**:
250+
- The hostname must resolve to `lzBmcIP` from the BMC network
251+
- When set, `ironicHTTPSCertificate` must have a DNS SAN matching this hostname
252+
- When not set, the IP-based flow is used and `ironicHTTPSCertificate` must have an IP SAN matching `lzBmcIP`
253+
- `PROVISIONING_IP` (used for Apache binding) always remains `lzBmcIP`; only the vmedia URL changes
254+
236255
#### `defaultNtpServers`
237256

238257
**Description**: Optional list of additional NTP server addresses for cluster nodes. When not set, the cluster uses its default NTP sources.
@@ -1098,7 +1117,9 @@ instead of plain HTTP.
10981117
**Type**: String (PEM format)
10991118

11001119
**Required**: Yes, when HTTPS vmedia is desired. Must be provided together
1101-
with `ironicHTTPSKey`. The certificate SAN must cover `lzBmcIP`.
1120+
with `ironicHTTPSKey`. The certificate SAN must cover `lzBmcHostname`
1121+
(DNS SAN, for publicly trusted certs such as Let's Encrypt) or `lzBmcIP`
1122+
(IP SAN, for private CA certs) depending on which is configured.
11021123

11031124
**Example**:
11041125
```yaml

playbooks/tasks/deploy_ironic_containers.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
ironic_env: >-
8888
{{ ironic_env | combine({
8989
'VMEDIA_TLS_PORT': '6183',
90-
'IRONIC_EXTERNAL_IP': lzBmcIP
90+
'IRONIC_EXTERNAL_IP': lzBmcHostname | default(lzBmcIP)
9191
}) }}
9292
9393
- name: Run ironic container

playbooks/templates/quadlets/metal3-ironic-api.container.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Environment=WEBSERVER_CACERT_FILE=/certs/ca-bundle.crt
3434
{% endif %}
3535
{% if ironic_https_configured | default(false) | bool %}
3636
Environment=VMEDIA_TLS_PORT=6183
37-
Environment=IRONIC_EXTERNAL_IP={{ lzBmcIP }}
37+
Environment=IRONIC_EXTERNAL_IP={{ lzBmcHostname | default(lzBmcIP) }}
3838
{% endif %}
3939
Exec=/bin/runironic
4040
HealthCmd=curl -sf http://localhost:6385/

schemas/definitions.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ definitions:
1616
type: string
1717
minLength: 1
1818
description: A non-empty string value
19+
hostname:
20+
type: string
21+
pattern: "^[a-zA-Z0-9]([a-zA-Z0-9\\-]*[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9\\-]*[a-zA-Z0-9])?)*$"
22+
description: A valid DNS hostname
1923
disconnected:
2024
type: boolean
2125
description: >-

schemas/variables.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ properties:
6060
type: array
6161
items:
6262
type: string
63+
lzBmcHostname:
64+
"$ref": "#/definitions/hostname"
6365
lzBmcIP:
6466
"$ref": "#/definitions/ipv4Address"
6567
machineNetwork:

scripts/deployment/generate_ironic_cert.sh

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,17 @@ if ! ssh_test_connection; then
6262
exit 1
6363
fi
6464

65-
# Read lzBmcIP from config/global.yaml on the Landing Zone
65+
# Read lzBmcIP and optional lzBmcHostname from config/global.yaml on the Landing Zone
6666
LZ_BMC_IP=$(ssh_exec "awk '/^lzBmcIP:/ {print \$2}' ${LZ_ENCLAVE_DIR}/config/global.yaml")
6767

6868
if [ -z "$LZ_BMC_IP" ]; then
6969
error "Could not read lzBmcIP from config/global.yaml on Landing Zone"
7070
exit 1
7171
fi
7272

73+
LZ_BMC_HOSTNAME=$(ssh_exec "awk '/^lzBmcHostname:/ {print \$2}' ${LZ_ENCLAVE_DIR}/config/global.yaml" 2>/dev/null || echo "")
74+
7375
info "Generating TLS certificate for Ironic ISO server"
74-
info "SAN: IP:${LZ_BMC_IP}"
7576

7677
# Work in a temp directory; cleaned up on exit
7778
WORK_DIR=$(mktemp -d)
@@ -85,8 +86,15 @@ openssl req -new \
8586
-out "${WORK_DIR}/server.csr" \
8687
2>/dev/null
8788

88-
# Write SAN extension to a file (process substitution does not work with sudo)
89-
echo "subjectAltName=IP:${LZ_BMC_IP}" > "${WORK_DIR}/san.ext"
89+
# Write SAN extension - include DNS SAN when lzBmcHostname is configured so that
90+
# publicly trusted (e.g. Let's Encrypt) certs can also be used
91+
if [[ -n "$LZ_BMC_HOSTNAME" ]]; then
92+
info "SAN: DNS:${LZ_BMC_HOSTNAME},IP:${LZ_BMC_IP}"
93+
echo "subjectAltName=DNS:${LZ_BMC_HOSTNAME},IP:${LZ_BMC_IP}" > "${WORK_DIR}/san.ext"
94+
else
95+
info "SAN: IP:${LZ_BMC_IP}"
96+
echo "subjectAltName=IP:${LZ_BMC_IP}" > "${WORK_DIR}/san.ext"
97+
fi
9098

9199
# Sign with CA; serial file goes into the temp dir to avoid writing to the
92100
# sudo-owned SUSHY_DIR

validations.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,9 +341,11 @@ checkCerts ingress "*.apps.$cluster_fqdn" .sslIngressCertificateKey .sslIngressC
341341

342342
# Check Ironic HTTPS certificate (optional)
343343
lz_bmc_ip=$(getValue .lzBmcIP)
344+
lz_bmc_hostname=$(getValue .lzBmcHostname 2>/dev/null || echo "")
345+
ironic_cert_name="${lz_bmc_hostname:-$lz_bmc_ip}"
344346
ironic_https_cert=$(getValue .ironicHTTPSCertificate)
345347
if [[ -n "$ironic_https_cert" && "$ironic_https_cert" != "null" ]]; then
346-
checkCerts ironic_https "$lz_bmc_ip" .ironicHTTPSKey .ironicHTTPSCertificate
348+
checkCerts ironic_https "$ironic_cert_name" .ironicHTTPSKey .ironicHTTPSCertificate
347349
else
348350
validation pass ironic_https_skipped "Ironic HTTPS certificate not configured. Skipping validation"
349351
fi

0 commit comments

Comments
 (0)