diff --git a/Dockerfile b/Dockerfile index 485a0a58a..3d2954b53 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,8 +47,6 @@ COPY --from=builder /tmp/esp.img /tmp/uefi_esp.img COPY config/ironic.conf.j2 /etc/ironic/ -# TODO(dtantsur): remove scripts/runironic script when we stop supporting -# running both API and conductor processes via one entry point. COPY scripts/ /bin/ COPY config/dnsmasq.conf.j2 /etc/ COPY config/inspector.ipxe.j2 config/dualboot.ipxe /tmp/ @@ -57,5 +55,6 @@ COPY config/inspector.ipxe.j2 config/dualboot.ipxe /tmp/ RUN rm -f /etc/httpd/conf.d/autoindex.conf /etc/httpd/conf.d/welcome.conf /etc/httpd/conf.modules.d/*.conf COPY config/httpd.conf /etc/httpd/conf.d/ COPY config/httpd-modules.conf /etc/httpd/conf.modules.d/ +COPY config/apache2-ironic-api.conf.j2 /etc/httpd-ironic-api.conf.j2 ENTRYPOINT ["/bin/runironic"] diff --git a/config/apache2-ironic-api.conf.j2 b/config/apache2-ironic-api.conf.j2 new file mode 100644 index 000000000..c5ca9a7fe --- /dev/null +++ b/config/apache2-ironic-api.conf.j2 @@ -0,0 +1,52 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +Listen 6385 + +{% if env.LISTEN_ALL_INTERFACES | lower == "true" %} + +{% else %} + +{% endif %} + + WSGIDaemonProcess ironic user=ironic group=ironic threads=10 display-name=%{GROUP} + WSGIScriptAlias / /usr/bin/ironic-api-wsgi + + SetEnv APACHE_RUN_USER ironic + SetEnv APACHE_RUN_GROUP ironic + WSGIProcessGroup ironic + + ErrorLog /dev/stderr + LogLevel debug + CustomLog /dev/stdout combined + +{% if env.IRONIC_TLS_SETUP == "true" %} + ServerName {{ env.IRONIC_IP }} + SSLEngine on + SSLCertificateFile {{ env.IRONIC_CERT_FILE }} + SSLCertificateKeyFile {{ env.IRONIC_KEY_FILE }} +{% endif %} + + + WSGIProcessGroup ironic + WSGIApplicationGroup %{GLOBAL} + AllowOverride All + Require all granted + + {% if "HTTP_BASIC_HTPASSWD" in env and env.HTTP_BASIC_HTPASSWD | length %} + AuthType Basic + AuthName "Restricted WSGI area" + AuthUserFile "/etc/ironic/htpasswd" + Require valid-user + {% endif %} + + diff --git a/config/httpd-modules.conf b/config/httpd-modules.conf index b20503325..8fee385ab 100644 --- a/config/httpd-modules.conf +++ b/config/httpd-modules.conf @@ -5,3 +5,17 @@ LoadModule dir_module modules/mod_dir.so LoadModule authz_core_module modules/mod_authz_core.so LoadModule unixd_module modules/mod_unixd.so LoadModule mpm_event_module modules/mod_mpm_event.so +LoadModule wsgi_module modules/mod_wsgi_python3.so +LoadModule ssl_module modules/mod_ssl.so +LoadModule env_module modules/mod_env.so +LoadModule proxy_module modules/mod_proxy.so +LoadModule proxy_ajp_module modules/mod_proxy_ajp.so +LoadModule proxy_balancer_module modules/mod_proxy_balancer.so +LoadModule proxy_http_module modules/mod_proxy_http.so +LoadModule slotmem_shm_module modules/mod_slotmem_shm.so +LoadModule headers_module modules/mod_headers.so +LoadModule authn_core_module modules/mod_authn_core.so +LoadModule auth_basic_module modules/mod_auth_basic.so +LoadModule authn_file_module modules/mod_authn_file.so +LoadModule authz_user_module modules/mod_authz_user.so + diff --git a/config/ironic.conf.j2 b/config/ironic.conf.j2 index 0d1e62b83..fa83e9be2 100644 --- a/config/ironic.conf.j2 +++ b/config/ironic.conf.j2 @@ -1,10 +1,5 @@ [DEFAULT] -{% if "HTTP_BASIC_HTPASSWD" in env and env.HTTP_BASIC_HTPASSWD | length %} -auth_strategy = http_basic -http_basic_auth_user_file = /etc/ironic/htpasswd -{% else %} auth_strategy = noauth -{% endif %} debug = true default_boot_interface = ipxe default_deploy_interface = direct @@ -27,7 +22,7 @@ require_agent_token = true # NOTE(dtantsur): the default md5 is not compatible with FIPS mode hash_ring_algorithm = sha256 my_ip = {{ env.IRONIC_IP }} -{% if env.IRONIC_DEPLOYMENT == "Combined" or (env.IRONIC_DEPLOYMENT == "Conductor" and env.JSON_RPC_AUTH_STRATEGY == "noauth") %} +{% if env.IRONIC_DEPLOYMENT == "Conductor" and env.JSON_RPC_AUTH_STRATEGY == "noauth" %} # if access is unauthenticated, we bind only to localhost - use that as the # host name also, so that the client can find the server # If we run both API and conductor in the same pod, use localhost @@ -125,10 +120,9 @@ command_retry_timeout = 60 # containers are in host networking. auth_strategy = {{ env.JSON_RPC_AUTH_STRATEGY }} http_basic_auth_user_file = /etc/ironic/htpasswd-rpc -{% if env.IRONIC_DEPLOYMENT == "Combined" or (env.IRONIC_DEPLOYMENT == "Conductor" and env.JSON_RPC_AUTH_STRATEGY == "noauth" ) %} +{% if env.IRONIC_DEPLOYMENT == "Conductor" and env.JSON_RPC_AUTH_STRATEGY == "noauth" %} # if access is unauthenticated, we bind only to localhost - use that as the # host name also, so that the client can find the server -# If we run both API and conductor in the same container, use localhost host_ip = localhost {% else %} host_ip = {% if env.LISTEN_ALL_INTERFACES | lower == "true" %}::{% else %}{{ env.IRONIC_IP }}{% endif %} diff --git a/main-packages-list.txt b/main-packages-list.txt index ce4a1b0ec..b2425dbce 100644 --- a/main-packages-list.txt +++ b/main-packages-list.txt @@ -3,6 +3,8 @@ dnsmasq gdisk genisoimage httpd +python3-mod_wsgi +mod_ssl httpd-tools ipmitool iproute diff --git a/prepare-image.sh b/prepare-image.sh index 3bf01dc36..c75b07098 100755 --- a/prepare-image.sh +++ b/prepare-image.sh @@ -11,6 +11,10 @@ if [[ ! -z ${EXTRA_PKGS_LIST:-} ]]; then xargs -rtd'\n' dnf --setopt=install_weak_deps=False install -y < /tmp/${EXTRA_PKGS_LIST} fi fi + +# TODO: Delete the below line of code after the PR https://github.com/metal3-io/baremetal-operator/pull/728 go in +dnf install -y net-tools + dnf clean all rm -rf /var/cache/{yum,dnf}/* if [[ ! -z ${PATCH_LIST:-} ]]; then @@ -19,3 +23,6 @@ if [[ ! -z ${PATCH_LIST:-} ]]; then fi fi rm -f /bin/patch-image.sh + +chown ironic:ironic /var/log/ironic +rm /etc/httpd/conf.d/ssl.conf # This file is generated after installing mod_ssl and it affects our configuration diff --git a/scripts/configure-httpd-ipa.sh b/scripts/configure-httpd-ipa.sh new file mode 100755 index 000000000..cef3f752f --- /dev/null +++ b/scripts/configure-httpd-ipa.sh @@ -0,0 +1,42 @@ +#!/usr/bin/bash + +IRONIC_CERT_FILE=${IRONIC_CERT_FILE:-/certs/ironic/tls.crt} +HTTP_PORT=${HTTP_PORT:-"80"} + +# Whether to enable fast_track provisioning or not +IRONIC_FAST_TRACK=${IRONIC_FAST_TRACK:-true} + +wait_for_interface_or_ip + +mkdir -pm 0777 /shared/html + +if [ -f "$IRONIC_CERT_FILE" ]; then + IRONIC_BASE_URL="https://${IRONIC_URL_HOST}" +else + IRONIC_BASE_URL="http://${IRONIC_URL_HOST}" +fi + +if [[ $IRONIC_FAST_TRACK == true ]]; then + INSPECTOR_EXTRA_ARGS=" ipa-api-url=${IRONIC_BASE_URL}:6385 ipa-inspection-callback-url=${IRONIC_BASE_URL}:5050/v1/continue" +else + INSPECTOR_EXTRA_ARGS=" ipa-inspection-callback-url=${IRONIC_BASE_URL}:5050/v1/continue" +fi + +# Copy files to shared mount +render_j2_config /tmp/inspector.ipxe.j2 /shared/html/inspector.ipxe +cp /tmp/dualboot.ipxe /tmp/uefi_esp.img /shared/html/ + +# Use configured values +sed -i -e s/IRONIC_IP/${IRONIC_URL_HOST}/g \ + -e s/HTTP_PORT/${HTTP_PORT}/g \ + -e "s|EXTRA_ARGS|${INSPECTOR_EXTRA_ARGS}|g" \ + /shared/html/inspector.ipxe + +sed -i 's/^Listen .*$/Listen [::]:'"$HTTP_PORT"'/' /etc/httpd/conf/httpd.conf +sed -i -e 's|\(^[[:space:]]*\)\(DocumentRoot\)\(.*\)|\1\2 "/shared/html"|' \ + -e 's|||' \ + -e 's|||' /etc/httpd/conf/httpd.conf + +# Log to std out/err +sed -i -e 's%^ \+CustomLog.*% CustomLog /dev/stderr combined%g' /etc/httpd/conf/httpd.conf +sed -i -e 's%^ErrorLog.*%ErrorLog /dev/stderr%g' /etc/httpd/conf/httpd.conf diff --git a/scripts/configure-ironic.sh b/scripts/configure-ironic.sh index cfb7d40ed..fd786c3a7 100755 --- a/scripts/configure-ironic.sh +++ b/scripts/configure-ironic.sh @@ -27,7 +27,6 @@ fi . /bin/ironic-common.sh -export HTTP_PORT=${HTTP_PORT:-"80"} export MARIADB_PASSWORD=${MARIADB_PASSWORD:-"change_me"} # TODO(dtantsur): remove the explicit default once we get # https://review.opendev.org/761185 in the repositories @@ -35,7 +34,6 @@ NUMPROC=$(cat /proc/cpuinfo | grep "^processor" | wc -l) NUMPROC=$(( NUMPROC <= 4 ? NUMPROC : 4 )) export NUMWORKERS=${NUMWORKERS:-$NUMPROC} export LISTEN_ALL_INTERFACES="${LISTEN_ALL_INTERFACES:-"true"}" -export IRONIC_DEPLOYMENT="${IRONIC_DEPLOYMENT:-"Combined"}" # Whether to enable fast_track provisioning or not export IRONIC_FAST_TRACK=${IRONIC_FAST_TRACK:-true} @@ -89,11 +87,6 @@ HTPASSWD_FILE=/etc/ironic/htpasswd # The user can provide HTTP_BASIC_HTPASSWD and HTTP_BASIC_HTPASSWD_RPC. If # - we are running conductor and HTTP_BASIC_HTPASSWD is set, # use HTTP_BASIC_HTPASSWD for RPC. -# - we are running combined and HTTP_BASIC_HTPASSWD is set, i.e. API is -# authenticated. We want to authenticate RPC by default, but the user might -# override. Then try to infere the authentication strategy and credentials -# from /auth/ironic-rpc/auth-config. If not present, then generate a username -# and password, create the config file the htpasswd content export JSON_RPC_AUTH_STRATEGY="noauth" if [ -n "${HTTP_BASIC_HTPASSWD}" ]; then if [ "${IRONIC_DEPLOYMENT}" == "Conductor" ]; then @@ -104,52 +97,16 @@ if [ -n "${HTTP_BASIC_HTPASSWD}" ]; then fi fi - -# When running both API and Conductor in the same container, we'll try to get the credentials -# from /auth/ironic-rpc/auth-config if present, or generate it -if [ "${IRONIC_DEPLOYMENT}" == "Combined" ]; then - # We try to read the credentials from the config file as it is probably mounted read-only, - # We cannot modify it. If it is not set to basic, then do not authenticate the RPC. This is - # to ensure that the setup will work if the user gives a specific config for rpc set to no_auth - if [ -f "/auth/ironic-rpc/auth-config" ]; then - IRONIC_RPC_TMP_TYPE="$(crudini --get /auth/ironic-rpc/auth-config json_rpc auth_type)" || exit 1 - if [ "${IRONIC_RPC_TMP_TYPE}" == "http_basic" ]; then - IRONIC_RPC_TMP_USERNAME="$(crudini --get /auth/ironic-rpc/auth-config json_rpc username)" || exit 1 - IRONIC_RPC_TMP_PASSWORD="$(crudini --get /auth/ironic-rpc/auth-config json_rpc password)" || exit 1 - else - export JSON_RPC_AUTH_STRATEGY="noauth" - fi - # We do not have an auth config file, so we generate one - else - IRONIC_RPC_TMP_USERNAME="rpc-user" - IRONIC_RPC_TMP_PASSWORD="$(tr -dc 'a-zA-Z0-9' < /dev/urandom | fold -w 12 | head -n 1)" - mkdir -p "/auth/ironic-rpc" - cat << EOF > "/auth/ironic-rpc/auth-config" -[json_rpc] -auth_type=http_basic -username=${IRONIC_RPC_TMP_USERNAME} -password=${IRONIC_RPC_TMP_PASSWORD} -http_basic_username=${IRONIC_RPC_TMP_USERNAME} -http_basic_password=${IRONIC_RPC_TMP_PASSWORD} -EOF - fi - - # Populate HTTP_BASIC_HTPASSWD_RPC - if [ -n "${IRONIC_RPC_TMP_USERNAME:-}" ]; then - htpasswd -n -b -B "${IRONIC_RPC_TMP_USERNAME}" "${IRONIC_RPC_TMP_PASSWORD}" >"${HTPASSWD_FILE}-rpc" - fi -fi - # The original ironic.conf is empty, and can be found in ironic.conf_orig render_j2_config /etc/ironic/ironic.conf.j2 /etc/ironic/ironic.conf # Configure auth for clients -IRONIC_CONFIG_OPTIONS="--config-file /etc/ironic/ironic.conf" - configure_client_basic_auth() { local auth_config_file="/auth/$1/auth-config" if [ -f ${auth_config_file} ]; then - IRONIC_CONFIG_OPTIONS+=" --config-file ${auth_config_file}" + # Merge configurations in the "auth" directory into the default ironic configuration file because there is no way to choose the configuration file + # when running the api as a WSGI app. + crudini --merge "/etc/ironic/ironic.conf" < ${auth_config_file} fi } diff --git a/scripts/ironic-common.sh b/scripts/ironic-common.sh old mode 100755 new mode 100644 diff --git a/scripts/runironic b/scripts/runironic deleted file mode 100755 index 4082b0853..000000000 --- a/scripts/runironic +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/bash - -export IRONIC_DEPLOYMENT="Combined" - -. /bin/configure-ironic.sh - -ironic-dbsync --config-file /etc/ironic/ironic.conf upgrade - -# Remove log files from last deployment -rm -rf /shared/log/ironic - -mkdir -p /shared/log/ironic - -/usr/bin/ironic-conductor ${IRONIC_CONFIG_OPTIONS} & -/usr/bin/ironic-api --config-file /usr/share/ironic/ironic-dist.conf ${IRONIC_CONFIG_OPTIONS} & - -wait -n diff --git a/scripts/runironic-api b/scripts/runironic-api index 6857aec20..8c872511b 100755 --- a/scripts/runironic-api +++ b/scripts/runironic-api @@ -14,4 +14,13 @@ while true ; do sleep 5 done -exec /usr/bin/ironic-api --config-file /usr/share/ironic/ironic-dist.conf ${IRONIC_CONFIG_OPTIONS} + +# TODO: Delete the line of code below and uncomment the next line after the PR https://github.com/metal3-io/baremetal-operator/pull/728 goes in +[ ! "$(netstat -l | grep ${HTTP_PORT})" ] && . /bin/configure-httpd-ipa.sh +# . /bin/configure-httpd-ipa.sh +# The code above avoids the httpd instance in this container to listen on port HTTP_PORT when it has been opened by BMO. + +python3 -c 'import os; import sys; import jinja2; sys.stdout.write(jinja2.Template(sys.stdin.read()).render(env=os.environ))' < /etc/httpd-ironic-api.conf.j2 > /etc/httpd/conf.d/ironic.conf +sed -i "/Listen 80/c\#Listen 80" /etc/httpd/conf/httpd.conf +exec /usr/sbin/httpd -DFOREGROUND + diff --git a/scripts/runironic-conductor b/scripts/runironic-conductor index 05d497621..2a94b1a8b 100755 --- a/scripts/runironic-conductor +++ b/scripts/runironic-conductor @@ -13,5 +13,4 @@ until ironic-dbsync --config-file /etc/ironic/ironic.conf upgrade; do echo "WARNING: ironic-dbsync failed, retrying" sleep 1 done - -exec /usr/bin/ironic-conductor ${IRONIC_CONFIG_OPTIONS} +exec /usr/bin/ironic-conductor