From 2487faa678e6853f076415cbcc74c9de52f7b204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Thu, 27 Aug 2026 01:46:17 -0300 Subject: [PATCH] fix(rpm): preserve local xcat.conf changes across upgrades xcat.conf was installed as an ordinary payload file and then deleted and recreated from the Apache-version template in %post. rpm therefore held no record of what was on disk, and an upgrade replaced an edited file silently, leaving neither .rpmnew nor .rpmsave. A site that had added Indexes to the /install block lost it on upgrade and directory listings began returning 403. Select the Apache 2.2 or 2.4 configuration at build time, using the same distribution macros the rest of the spec already relies on, and mark both /etc/httpd/conf.d/xcat.conf and /etc/apache2/conf.d/xcat.conf as %config(noreplace). rpm then keeps a modified file and installs the new vendor version alongside it as xcat.conf.rpmnew. The old payload recorded the 2.2 file while %post wrote the 2.4 one, so rpm cannot distinguish a stock file from an edited one across the transition. A migration compares the active file with the templates the outgoing package saved under conf.orig and removes it only when it is a regular file still byte-for-byte identical to one of them. A stock upgrade then completes without an unnecessary .rpmnew, and anything that differs is left untouched. That migration runs in %pretrans, not %pre. rpm fixes each config file's fate before %pre, so removing the active file there can happen after rpm has already resolved to write only xcat.conf.rpmnew, leaving the system with no active configuration at all. %pretrans runs before that decision. It is an embedded Lua scriptlet because a pre-transaction scriptlet cannot rely on any dependency being unpacked yet, which also means the comparison needs no external tool. bc was needed only by the version check the service-node package no longer performs. The Apache directives are unchanged. Document a later-loading conf.d file as the place for site rules, since that survives upgrades without a merge. --- .../advanced/security/apache_hardening.rst | 24 ++++++ xCAT/xCAT.spec | 75 ++++++++++++------- xCATsn/xCATsn.spec | 69 +++++++++++------ 3 files changed, 121 insertions(+), 47 deletions(-) diff --git a/docs/source/advanced/security/apache_hardening.rst b/docs/source/advanced/security/apache_hardening.rst index a07ac58ef..44cde248c 100644 --- a/docs/source/advanced/security/apache_hardening.rst +++ b/docs/source/advanced/security/apache_hardening.rst @@ -107,6 +107,30 @@ provisioning scripts crawl recursively. download all files from these directories and depend on Apache directory listings to discover file paths. +Local Customizations +-------------------- + +``xcat.conf`` belongs to the xCAT package. Keep site-specific rules in a +separate file that Apache reads after it: + +* EL: ``/etc/httpd/conf.d/zz-xcat-local.conf`` +* SUSE: ``/etc/apache2/conf.d/zz-xcat-local.conf`` + +Apache reads ``conf.d`` in alphabetical order, so the ``zz-`` prefix puts the +file after ``xcat.conf`` and its directives take precedence. Use the relative +form of ``Options`` (``+`` or ``-``): it adjusts the options inherited from the +xCAT block, where the absolute form replaces all of them. For example, to allow +browsing of one site directory:: + + # /etc/httpd/conf.d/zz-xcat-local.conf + + Options +Indexes + + +Editing ``xcat.conf`` in place still works: an upgrade keeps the modified file. +If the packaged configuration changed too, the new version is written alongside +it as ``xcat.conf.rpmnew`` and has to be merged by hand. + Security Response Headers and Server Banner ------------------------------------------- diff --git a/xCAT/xCAT.spec b/xCAT/xCAT.spec index 998f8d6cd..34cbfdf04 100644 --- a/xCAT/xCAT.spec +++ b/xCAT/xCAT.spec @@ -125,6 +125,44 @@ tar -xf postscripts.tar %build +%ifos linux +# rpm fixes each config file's fate before %pre runs, so a migration there can +# delete an active file rpm has already decided to leave as xcat.conf.rpmnew. +# %pretrans runs before that decision. It is Lua because a pre-transaction +# scriptlet cannot rely on any dependency being unpacked yet. +%pretrans -p +-- Older packages shipped xcat.conf as ordinary payload and then rewrote it +-- from conf.orig in %post, so rpm's recorded digest cannot tell a stock file +-- from a customised one. Compare against the outgoing package's templates, +-- still on disk at this point, and drop only a file that still matches one. +if arg[2] and tonumber(arg[2]) == 1 then return end + +local function contents(path) + local handle = io.open(path, "rb") + if not handle then return nil end + local data = handle:read("*a") + handle:close() + return data +end + +local templates = { contents("/etc/%httpconfigdir/conf.orig/xcat.conf.apach24"), + contents("/etc/%httpconfigdir/conf.orig/xcat.conf.apach22") } + +for _, active in ipairs({ "/etc/httpd/conf.d/xcat.conf", + "/etc/apache2/conf.d/xcat.conf" }) do + -- A symlink or any other non-regular path is treated as locally managed. + if posix.stat(active, "type") == "regular" then + local current = contents(active) + for _, template in ipairs(templates) do + if current and current == template then + os.remove(active) + break + end + end + end +end +%endif + %pre # this is now handled by requiring /usr/sbin/dhcpd #if [ -e "/etc/SuSE-release" ]; then @@ -199,8 +237,15 @@ chmod 755 $RPM_BUILD_ROOT/install/postscripts/* rm LICENSE.html mkdir -p postscripts/hostkeys cd - +# Pick the Apache generation at build time. Selecting it in the post scriptlet +# instead rewrites a file rpm has already checksummed, defeating noreplace. +%if 0%{?fedora} || 0%{?rhel} >= 7 || 0%{?suse_version} >= 1200 +cp %{SOURCE7} $RPM_BUILD_ROOT/etc/httpd/conf.d/xcat.conf +cp %{SOURCE7} $RPM_BUILD_ROOT/etc/apache2/conf.d/xcat.conf +%else cp %{SOURCE1} $RPM_BUILD_ROOT/etc/httpd/conf.d/xcat.conf cp %{SOURCE1} $RPM_BUILD_ROOT/etc/apache2/conf.d/xcat.conf +%endif cp %{SOURCE7} $RPM_BUILD_ROOT/etc/%httpconfigdir/conf.orig/xcat.conf.apach24 cp %{SOURCE1} $RPM_BUILD_ROOT/etc/%httpconfigdir/conf.orig/xcat.conf.apach22 cp %{SOURCE5} $RPM_BUILD_ROOT/etc/xCATMN @@ -211,25 +256,6 @@ cp LICENSE.html $RPM_BUILD_ROOT/%{prefix}/share/doc/packages/xCAT %post %ifos linux -#Apply the correct httpd/apache configuration file according to the httpd/apache version -if [ -n "$(httpd -v 2>&1 |grep -e '^Server version\s*:.*\/2.4')" ] -then - rm -rf /etc/httpd/conf.d/xcat.conf - cp /etc/%httpconfigdir/conf.orig/xcat.conf.apach24 /etc/httpd/conf.d/xcat.conf -fi - -if [ -n "$(apachectl -v 2>&1 |grep -e '^Server version\s*:.*\/2.4')" ] -then - rm -rf /etc/apache2/conf.d/xcat.conf - cp /etc/%httpconfigdir/conf.orig/xcat.conf.apach24 /etc/apache2/conf.d/xcat.conf -fi - -if [ -n "$(apache2ctl -v 2>&1 |grep -e '^Server version\s*:.*\/2.4')" ] -then - rm -rf /etc/apache2/conf.d/xcat.conf - cp /etc/%httpconfigdir/conf.orig/xcat.conf.apach24 /etc/apache2/conf.d/xcat.conf -fi - # On SUSE apache2, mod_headers is not loaded by default; enable it so the # security response headers in xcat.conf take effect (a no-op where a2enmod # is absent, e.g. httpd on EL where mod_headers is already loaded). @@ -282,10 +308,9 @@ if grep -q "command returned error code" "$xcatupgradeout"; then fi rm -f "$xcatupgradeout" -# The package replaces xcat.conf before this upgrade path runs. Reload an -# active web server so the new security headers and ServerTokens setting take -# effect immediately, but do not try to manage services while building an -# image in a chroot. +# rpm has either updated a stock xcat.conf or preserved a modified one and left +# the new file as xcat.conf.rpmnew. Reload an active web server so an updated +# stock configuration takes effect, but do not manage services in a chroot. if [ -f "/proc/cmdline" ] && [ "x$(stat -c '%i %d' /)" == "x$(stat -c '%i %d' /proc/1/root/. 2>/dev/null)" ]; then if [ -e "/etc/redhat-release" ]; then apachedaemon='httpd' @@ -311,8 +336,8 @@ exit 0 # one for sles, one for rhel. yes, it's ugly... /etc/%httpconfigdir/conf.orig/xcat.conf.apach24 /etc/%httpconfigdir/conf.orig/xcat.conf.apach22 -/etc/httpd/conf.d/xcat.conf -/etc/apache2/conf.d/xcat.conf +%config(noreplace) /etc/httpd/conf.d/xcat.conf +%config(noreplace) /etc/apache2/conf.d/xcat.conf /etc/xCATMN /install/postscripts /install/prescripts diff --git a/xCATsn/xCATsn.spec b/xCATsn/xCATsn.spec index d9585edc0..7540cd694 100644 --- a/xCATsn/xCATsn.spec +++ b/xCATsn/xCATsn.spec @@ -50,7 +50,6 @@ Conflicts: xCAT # on RHEL7, need to specify it explicitly Requires: net-tools Requires: /usr/bin/killall -Requires: /usr/bin/bc # yaboot-xcat is pulled in so any SN can manage ppc nodes Requires: httpd nfs-utils nmap bind # DHCP backend resolved at INSTALL time (not build time) via an RPM rich @@ -112,8 +111,15 @@ mkdir -p $RPM_BUILD_ROOT/etc/apache2/conf.d mkdir -p $RPM_BUILD_ROOT/etc/httpd/conf.d/ mkdir -p $RPM_BUILD_ROOT/%{prefix}/share/xcat/ # cd - +# Pick the Apache generation at build time. Selecting it in the post scriptlet +# instead rewrites a file rpm has already checksummed, defeating noreplace. +%if 0%{?fedora} || 0%{?rhel} >= 7 || 0%{?suse_version} >= 1200 +cp %{SOURCE6} $RPM_BUILD_ROOT/etc/apache2/conf.d/xcat.conf +cp %{SOURCE6} $RPM_BUILD_ROOT/etc/httpd/conf.d/xcat.conf +%else cp %{SOURCE1} $RPM_BUILD_ROOT/etc/apache2/conf.d/xcat.conf cp %{SOURCE1} $RPM_BUILD_ROOT/etc/httpd/conf.d/xcat.conf +%endif cp %{SOURCE3} $RPM_BUILD_ROOT/etc/xCATSN cp %{SOURCE1} $RPM_BUILD_ROOT/etc/xcat/conf.orig/xcat.conf.apach22 cp %{SOURCE6} $RPM_BUILD_ROOT/etc/xcat/conf.orig/xcat.conf.apach24 @@ -141,6 +147,44 @@ rm templates.tar %endif +%ifos linux +# rpm fixes each config file's fate before %pre runs, so a migration there can +# delete an active file rpm has already decided to leave as xcat.conf.rpmnew. +# %pretrans runs before that decision. It is Lua because a pre-transaction +# scriptlet cannot rely on any dependency being unpacked yet. +%pretrans -p +-- Older packages shipped xcat.conf as ordinary payload and then rewrote it +-- from conf.orig in %post, so rpm's recorded digest cannot tell a stock file +-- from a customised one. Compare against the outgoing package's templates, +-- still on disk at this point, and drop only a file that still matches one. +if arg[2] and tonumber(arg[2]) == 1 then return end + +local function contents(path) + local handle = io.open(path, "rb") + if not handle then return nil end + local data = handle:read("*a") + handle:close() + return data +end + +local templates = { contents("/etc/xcat/conf.orig/xcat.conf.apach24"), + contents("/etc/xcat/conf.orig/xcat.conf.apach22") } + +for _, active in ipairs({ "/etc/httpd/conf.d/xcat.conf", + "/etc/apache2/conf.d/xcat.conf" }) do + -- A symlink or any other non-regular path is treated as locally managed. + if posix.stat(active, "type") == "regular" then + local current = contents(active) + for _, template in ipairs(templates) do + if current and current == template then + os.remove(active) + break + end + end + end +end +%endif + %pre # only need to check on AIX %ifnos linux @@ -155,25 +199,6 @@ fi %post %ifos linux -#Apply the correct httpd/apache configuration file according to the httpd/apache version - -compare_version="2.4" -version=`rpm -qi httpd 2>&1 | grep 'Version' | awk '$NF { print $3 }' | cut -d. -f1,2` -if [ -n "$version" ]; then - if (( `bc <<< "$version >= $compare_version"` )); then - rm -rf /etc/httpd/conf.d/xcat.conf - cp /etc/xcat/conf.orig/xcat.conf.apach24 /etc/httpd/conf.d/xcat.conf - fi -fi - -version=`rpm -qi apache2 2>&1 | grep '^Version' | awk '$NF { print $3 }' | cut -d. -f1,2` -if [ -n "$version" ]; then - if (( `bc <<< "$version >= $compare_version"` )); then - rm -rf /etc/apache2/conf.d/xcat.conf - cp /etc/xcat/conf.orig/xcat.conf.apach24 /etc/apache2/conf.d/xcat.conf - fi -fi - # On SUSE apache2, mod_headers is not loaded by default; enable it so the # security response headers in xcat.conf take effect (a no-op where a2enmod # is absent, e.g. httpd on EL where mod_headers is already loaded). @@ -293,8 +318,8 @@ fi %ifos linux /etc/xcat/conf.orig/xcat.conf.apach24 /etc/xcat/conf.orig/xcat.conf.apach22 -/etc/httpd/conf.d/xcat.conf -/etc/apache2/conf.d/xcat.conf +%config(noreplace) /etc/httpd/conf.d/xcat.conf +%config(noreplace) /etc/apache2/conf.d/xcat.conf /etc/xcat/logrotate.conf/ /etc/xcat/rsyslog.conf/ %endif