diff --git a/README.md b/README.md index 99864a4f8..9537bd6e9 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,8 @@ available, it falls back to the default IPA images. The following environment variables control IPA kernel boot parameters (all have sensible defaults): -- `IRONIC_IPA_INSECURE` - Allow insecure connections (default: `1`) +- `IRONIC_IPA_INSECURE` - Allow insecure connections (default: `0` if Ironic + certificate can be verified locally, `1` otherwise) - `IRONIC_IPA_DEBUG` - Enable debug mode (default: `1`) - `IRONIC_IPA_INSPECTION_DHCP_ALL_INTERFACES` - Request DHCP on all interfaces during inspection (default: `1`) @@ -186,7 +187,8 @@ have sensible defaults): - `IRONIC_JSON_RPC_PORT` - port used by the ironic json-rpc service (default to 6189). - `WEBSERVER_CACERT_FILE` - Specifies the CA or CA bundle that will be used - by Ironic to verify disk and IPA images. + by Ironic to verify disk and IPA images. Will also be used by IPA to verify + disk images. The following mountpoints can be passed in to customize run-time functionality: diff --git a/ironic-config/inspector.ipxe.j2 b/ironic-config/inspector.ipxe.j2 index 070c9e969..e8c013ac1 100644 --- a/ironic-config/inspector.ipxe.j2 +++ b/ironic-config/inspector.ipxe.j2 @@ -3,7 +3,7 @@ echo In inspector.ipxe {%- macro kernel_cmdline(kernel_path, ramdisk_name) -%} -kernel --timeout 60000 {{ kernel_path }} ipa-insecure={{ env.IRONIC_IPA_INSECURE | default('1') }} ipa-inspection-collectors={{ env.IRONIC_IPA_COLLECTORS }} systemd.journald.forward_to_console={{ env.IPA_FORWARD_CONSOLE | default('yes') }} BOOTIF=${mac} ipa-debug={{ env.IRONIC_IPA_DEBUG | default('1') }} ipa-enable-vlan-interfaces={{ env.IRONIC_ENABLE_VLAN_INTERFACES }} ipa-inspection-dhcp-all-interfaces={{ env.IRONIC_IPA_INSPECTION_DHCP_ALL_INTERFACES | default('1') }} ipa-collect-lldp={{ env.IRONIC_IPA_COLLECT_LLDP | default('1') }} {{ env.INSPECTOR_EXTRA_ARGS }} initrd={{ ramdisk_name }} {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} || goto retry_boot +kernel --timeout 60000 {{ kernel_path }} ipa-insecure={{ env.IRONIC_IPA_INSECURE | default('0') }} ipa-inspection-collectors={{ env.IRONIC_IPA_COLLECTORS }} systemd.journald.forward_to_console={{ env.IPA_FORWARD_CONSOLE | default('yes') }} BOOTIF=${mac} ipa-debug={{ env.IRONIC_IPA_DEBUG | default('1') }} ipa-enable-vlan-interfaces={{ env.IRONIC_ENABLE_VLAN_INTERFACES }} ipa-inspection-dhcp-all-interfaces={{ env.IRONIC_IPA_INSPECTION_DHCP_ALL_INTERFACES | default('1') }} ipa-collect-lldp={{ env.IRONIC_IPA_COLLECT_LLDP | default('1') }} {{ env.INSPECTOR_EXTRA_ARGS }} initrd={{ ramdisk_name }} {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} || goto retry_boot {%- endmacro -%} {% if env.DEPLOY_KERNEL_BY_ARCH is defined %} @@ -55,6 +55,7 @@ imgfree # ironic-inspector-image and configuration in configure-ironic.sh {{ kernel_cmdline('${ipa_kernel}', '${ipa_ramdisk_name}') }} initrd --timeout 60000 ${ipa_ramdisk} || goto retry_boot +initrd --timeout 60000 {{ env.IRONIC_HTTP_URL }}/ipa-cacert-bundle || goto retry_boot boot {% else %} :retry_boot @@ -63,5 +64,6 @@ imgfree # ironic-inspector-image and configuration in configure-ironic.sh {{ kernel_cmdline(env.IRONIC_HTTP_URL + '/images/ironic-python-agent.kernel', 'ironic-python-agent.initramfs') }} initrd --timeout 60000 {{ env.IRONIC_HTTP_URL }}/images/ironic-python-agent.initramfs || goto retry_boot +initrd --timeout 60000 {{ env.IRONIC_HTTP_URL }}/ipa-cacert-bundle || goto retry_boot boot {% endif %} diff --git a/ironic-config/ipxe_config.template b/ironic-config/ipxe_config.template index 33ba26eb4..dab5c3b0e 100644 --- a/ironic-config/ipxe_config.template +++ b/ironic-config/ipxe_config.template @@ -7,21 +7,28 @@ goto deploy :deploy imgfree +{%- set ipxe_tls_setup = false %} +{%- if ipxe_tls_setup %} {%- if pxe_options.deployment_aki_path %} {%- set aki_path_https_elements = pxe_options.deployment_aki_path.split(':') %} {%- set aki_port_and_path = aki_path_https_elements[2].split('/') %} {%- set aki_afterport = aki_port_and_path[1:]|join('/') %} -{%- set aki_path_https = ['https:', aki_path_https_elements[1], ':8084/', aki_afterport]|join %} +{%- set aki_path = ['https:', aki_path_https_elements[1], ':8084/', aki_afterport]|join %} {%- endif %} {%- if pxe_options.deployment_ari_path %} {%- set ari_path_https_elements = pxe_options.deployment_ari_path.split(':') %} {%- set ari_port_and_path = ari_path_https_elements[2].split('/') %} {%- set ari_afterport = ari_port_and_path[1:]|join('/') %} -{%- set ari_path_https = ['https:', ari_path_https_elements[1], ':8084/', ari_afterport]|join %} +{%- set ari_path = ['https:', ari_path_https_elements[1], ':8084/', ari_afterport]|join %} {%- endif %} -kernel {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ aki_path_https }} selinux=0 troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} BOOTIF=${mac} initrd={{ pxe_options.initrd_filename|default("deploy_ramdisk", true) }} || goto retry +{%- else %} +{%- set ari_path = pxe_options.deployment_ari_path %} +{%- set aki_path = pxe_options.deployment_aki_path %} +{%- endif %} +kernel {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ aki_path }} selinux=0 troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} BOOTIF=${mac} initrd={{ pxe_options.initrd_filename|default("deploy_ramdisk", true) }} || goto retry -initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ ari_path_https }} || goto retry +initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ ari_path }} || goto retry +initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %} ../ipa-cacert-bundle || goto retry boot :retry @@ -39,8 +46,9 @@ poweroff :boot_anaconda imgfree -kernel {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ aki_path_https }} text {{ pxe_options.pxe_append_params|default("", true) }} inst.ks={{ pxe_options.ks_cfg_url }} {% if pxe_options.repo_url %}inst.repo={{ pxe_options.repo_url }}{% else %}inst.stage2={{ pxe_options.stage2_url }}{% endif %} initrd=ramdisk || goto boot_anaconda -initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ ari_path_https }} || goto boot_anaconda +kernel {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ aki_path }} text {{ pxe_options.pxe_append_params|default("", true) }} inst.ks={{ pxe_options.ks_cfg_url }} {% if pxe_options.repo_url %}inst.repo={{ pxe_options.repo_url }}{% else %}inst.stage2={{ pxe_options.stage2_url }}{% endif %} initrd=ramdisk || goto boot_anaconda +initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ ari_path }} || goto boot_anaconda +initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %} ../ipa-cacert-bundle || goto boot_anaconda boot :boot_ramdisk @@ -48,8 +56,9 @@ imgfree {%- if pxe_options.boot_iso_url %} sanboot {{ pxe_options.boot_iso_url }} {%- else %} -kernel {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ aki_path_https }} root=/dev/ram0 text {{ pxe_options.pxe_append_params|default("", true) }} {{ pxe_options.ramdisk_opts|default('', true) }} initrd=ramdisk || goto boot_ramdisk -initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ ari_path_https }} || goto boot_ramdisk +kernel {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ aki_path }} root=/dev/ram0 text {{ pxe_options.pxe_append_params|default("", true) }} {{ pxe_options.ramdisk_opts|default('', true) }} initrd=ramdisk || goto boot_ramdisk +initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ ari_path }} || goto boot_ramdisk +initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %} ../ipa-cacert-bundle || goto boot_ramdisk boot {%- endif %} diff --git a/ironic-config/ironic.conf.j2 b/ironic-config/ironic.conf.j2 index 93d30704e..be26507e4 100644 --- a/ironic-config/ironic.conf.j2 +++ b/ironic-config/ironic.conf.j2 @@ -54,6 +54,7 @@ deploy_logs_local_path = /shared/log/ironic/deploy # See https://bugzilla.redhat.com/show_bug.cgi?id=1822763 max_command_attempts = 30 certificates_path = {{ env.IRONIC_GEN_CERT_DIR }} +api_ca_file = {{ env.IPA_CACERT_FILE }} [api] {% if env.IRONIC_REVERSE_PROXY_SETUP == "true" %} @@ -233,20 +234,18 @@ images_path = /shared/html/tmp instance_master_path = /shared/html/master_images tftp_master_path = /shared/tftpboot/master_images tftp_root = /shared/tftpboot -kernel_append_params = nofb nomodeset vga=normal ipa-insecure=1 {% if env.ENABLE_FIPS_IPA %}fips={{ env.ENABLE_FIPS_IPA|trim }}{% endif %} {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} systemd.journald.forward_to_console={{ env.IPA_FORWARD_CONSOLE | default('yes') }} +kernel_append_params = nofb nomodeset vga=normal initrd=ipa-cacert-bundle ipa-insecure={{ env.IRONIC_IPA_INSECURE | default('0') }} {% if env.ENABLE_FIPS_IPA %}fips={{ env.ENABLE_FIPS_IPA|trim }}{% endif %} {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} systemd.journald.forward_to_console={{ env.IPA_FORWARD_CONSOLE | default('yes') }} # This makes networking boot templates generated even for nodes using local # boot (the default), ensuring that they boot correctly even if they start # netbooting for some reason (e.g. with the noop management interface). enable_netboot_fallback = true # Enable the fallback path to in-band inspection ipxe_fallback_script = inspector.ipxe -{% if env.IPXE_TLS_SETUP | lower == "true" %} -ipxe_config_template = /templates/ipxe_config.template -{% endif %} +ipxe_config_template = {{ env.IRONIC_CONF_DIR }}/ipxe_config.template [redfish] use_swift = false -kernel_append_params = nofb nomodeset vga=normal ipa-insecure=1 {% if env.ENABLE_FIPS_IPA %}fips={{ env.ENABLE_FIPS_IPA|trim }}{% endif %} {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} systemd.journald.forward_to_console={{ env.IPA_FORWARD_CONSOLE | default('yes') }} +kernel_append_params = nofb nomodeset vga=normal ipa-insecure={{ env.IRONIC_IPA_INSECURE | default('0') }} {% if env.ENABLE_FIPS_IPA %}fips={{ env.ENABLE_FIPS_IPA|trim }}{% endif %} {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} systemd.journald.forward_to_console={{ env.IPA_FORWARD_CONSOLE | default('yes') }} {% if env.BMC_TLS_ENABLED == "true" %} # idrac uses the same options as the redfish driver verify_ca = {{ env.BMC_CACERT_FILE }} diff --git a/main-packages-list.txt b/main-packages-list.txt index 1b09b8629..6ae297d2c 100644 --- a/main-packages-list.txt +++ b/main-packages-list.txt @@ -1,3 +1,4 @@ +cpio dnsmasq dosfstools httpd diff --git a/scripts/configure-ironic.sh b/scripts/configure-ironic.sh index 994d831ab..9f594cfd6 100755 --- a/scripts/configure-ironic.sh +++ b/scripts/configure-ironic.sh @@ -92,6 +92,12 @@ if [[ -f /proc/sys/crypto/fips_enabled ]]; then export ENABLE_FIPS_IPA fi +if [[ "${IPXE_TLS_SETUP,,}" == "true" ]]; then + sed 's/set ipxe_tls_setup = false/set ipxe_tls_setup = true/' /templates/ipxe_config.template > "${IRONIC_CONF_DIR}/ipxe_config.template" +else + cp /templates/ipxe_config.template "${IRONIC_CONF_DIR}/ipxe_config.template" +fi + # The original ironic.conf is empty, and can be found in ironic.conf_orig render_j2_config "/etc/ironic/ironic.conf.j2" \ "${IRONIC_CONF_DIR}/ironic.conf" diff --git a/scripts/rundnsmasq b/scripts/rundnsmasq index e2ea2965d..c83be1c86 100755 --- a/scripts/rundnsmasq +++ b/scripts/rundnsmasq @@ -20,6 +20,7 @@ if [[ "${DNS_IP:-}" == "provisioning" ]]; then fi mkdir -p /shared/tftpboot +mkdir -p /shared/html # Copy files to shared mount if [[ -r "${IPXE_CUSTOM_FIRMWARE_DIR}" ]]; then diff --git a/scripts/runironic b/scripts/runironic index ff0e851b1..2e8b5c8d0 100755 --- a/scripts/runironic +++ b/scripts/runironic @@ -11,6 +11,14 @@ if [[ "${IRONIC_SKIP_DBSYNC:-false}" != true ]]; then run_ironic_dbsync fi +generate_cacert_bundle_initrd /shared/html/ipa-cacert-bundle + +if [[ "${IRONIC_IPA_INSECURE:-0}" -eq 0 ]] && [[ -n "${WEBSERVER_CACERT_FILE:-}" ]]; then + ipa_cert_update_enabled="true" +else + ipa_cert_update_enabled="false" +fi +configure_restart_on_certificate_update "${ipa_cert_update_enabled}" ironic "${WEBSERVER_CACERT_FILE:-}" configure_restart_on_certificate_update "${IRONIC_TLS_SETUP}" ironic "${IRONIC_CERT_FILE}" configure_ironic_auth diff --git a/scripts/tls-common.sh b/scripts/tls-common.sh index e43b2c1f1..3b0bbb9e6 100644 --- a/scripts/tls-common.sh +++ b/scripts/tls-common.sh @@ -5,6 +5,8 @@ export IRONIC_SSL_PROTOCOL=${IRONIC_SSL_PROTOCOL:-"-ALL +TLSv1.2 +TLSv1.3"} export IPXE_SSL_PROTOCOL=${IPXE_SSL_PROTOCOL:-"-ALL +TLSv1.2 +TLSv1.3"} export IRONIC_VMEDIA_SSL_PROTOCOL=${IRONIC_VMEDIA_SSL_PROTOCOL:-"ALL"} +export DEFAULT_CACERT_BUNDLE=${DEFAULT_CACERT_BUNDLE:-"/etc/ssl/cert.pem"} + # Node image storage is using the same cert and port as the API export IRONIC_CERT_FILE=/certs/ironic/tls.crt export IRONIC_KEY_FILE=/certs/ironic/tls.key @@ -22,7 +24,8 @@ export RESTART_CONTAINER_CERTIFICATE_UPDATED=${RESTART_CONTAINER_CERTIFICATE_UPD export MARIADB_CACERT_FILE=/certs/ca/mariadb/tls.crt export BMC_CACERTS_PATH=/certs/ca/bmc export BMC_CACERT_FILE=/conf/bmc-tls.pem -export IRONIC_CACERT_FILE=/certs/ca/ironic/tls.crt +export IRONIC_CACERT_FILE=${IRONIC_CACERT_FILE:-"/certs/ca/ironic/tls.crt"} +export IPA_CACERT_FILE=/conf/ipa-tls.pem export IPXE_TLS_PORT="${IPXE_TLS_PORT:-8084}" @@ -129,3 +132,42 @@ if ls "${BMC_CACERTS_PATH}"/* > /dev/null 2>&1; then else export BMC_TLS_ENABLED="false" fi + +if [[ -f "${WEBSERVER_CACERT_FILE:-}" ]]; then + copy_atomic "${WEBSERVER_CACERT_FILE}" "${IPA_CACERT_FILE}" +elif [[ -f "${DEFAULT_CACERT_BUNDLE}" ]]; then + copy_atomic "${DEFAULT_CACERT_BUNDLE}" "${IPA_CACERT_FILE}" +fi + +if [[ -f "${IRONIC_CACERT_FILE}" ]]; then + cat "${IRONIC_CACERT_FILE}" >> "${IPA_CACERT_FILE}" +fi + +if ! openssl verify -CAfile "${IPA_CACERT_FILE}" "${IRONIC_CERT_FILE}" > /dev/null 2>&1; then + # if we are unable to verify the Ironic cert file set IPA_INSECURE to true + export IRONIC_IPA_INSECURE="1" +fi + +generate_cacert_bundle_initrd() +( + set -euo pipefail + + local output_path="$1" + local temp_dir + + temp_dir="$(mktemp -d)" + trap 'rm -rf "${temp_dir}"' EXIT + + chmod 0755 "${temp_dir}" + + cd "${temp_dir}" + + mkdir -p etc/ironic-python-agent.d etc/ironic-python-agent + cp "${IPA_CACERT_FILE}" etc/ironic-python-agent/ironic.crt + cat > etc/ironic-python-agent.d/ironic-tls.conf <> "${output_path}" +)