diff --git a/docs/source/advanced/security/apache_hardening.rst b/docs/source/advanced/security/apache_hardening.rst
index 22787ffbc..a07ac58ef 100644
--- a/docs/source/advanced/security/apache_hardening.rst
+++ b/docs/source/advanced/security/apache_hardening.rst
@@ -23,13 +23,27 @@ provisioning scripts crawl recursively.
**Apache 2.4** (RHEL 7+, SLES 12+, Ubuntu 16.04+)::
# /etc/httpd/conf.d/xcat.conf
+ ServerTokens Prod
+
- Options FollowSymLinks Includes MultiViews
+ Options FollowSymLinks
+
+ Header always set X-Frame-Options SAMEORIGIN
+ Header always set X-Content-Type-Options nosniff
+ Header always set Content-Security-Policy "script-src 'self' 'unsafe-eval'"
+ Header always set X-Permitted-Cross-Domain-Policies none
+
AllowOverride None
Require all granted
- Options FollowSymLinks Includes MultiViews
+ Options FollowSymLinks
+
+ Header always set X-Frame-Options SAMEORIGIN
+ Header always set X-Content-Type-Options nosniff
+ Header always set Content-Security-Policy "script-src 'self' 'unsafe-eval'"
+ Header always set X-Permitted-Cross-Domain-Policies none
+
AllowOverride None
Require all granted
@@ -47,14 +61,28 @@ provisioning scripts crawl recursively.
**Apache 2.2** (RHEL 6, SLES 11)::
# /etc/httpd/conf.d/xcat.conf
+ ServerTokens Prod
+
- Options FollowSymLinks Includes MultiViews
+ Options FollowSymLinks
+
+ Header always set X-Frame-Options SAMEORIGIN
+ Header always set X-Content-Type-Options nosniff
+ Header always set Content-Security-Policy "script-src 'self' 'unsafe-eval'"
+ Header always set X-Permitted-Cross-Domain-Policies none
+
AllowOverride None
Order allow,deny
Allow from all
- Options FollowSymLinks Includes MultiViews
+ Options FollowSymLinks
+
+ Header always set X-Frame-Options SAMEORIGIN
+ Header always set X-Content-Type-Options nosniff
+ Header always set Content-Security-Policy "script-src 'self' 'unsafe-eval'"
+ Header always set X-Permitted-Cross-Domain-Policies none
+
AllowOverride None
Order allow,deny
Allow from all
@@ -79,6 +107,23 @@ provisioning scripts crawl recursively.
download all files from these directories and depend on Apache directory
listings to discover file paths.
+Security Response Headers and Server Banner
+-------------------------------------------
+
+xCAT configures ``ServerTokens Prod`` to omit the Apache version and operating
+system from the HTTP ``Server`` header. It also applies the following response
+headers to ``/install`` and ``/tftpboot``:
+
+* ``X-Frame-Options: SAMEORIGIN``
+* ``X-Content-Type-Options: nosniff``
+* ``Content-Security-Policy: script-src 'self' 'unsafe-eval'``
+* ``X-Permitted-Cross-Domain-Policies: none``
+
+These directives require ``mod_headers``. The xCAT packages enable that module
+on distributions where it is not loaded by default and activate the updated
+configuration during installation or upgrade. Keep the ``Header always set``
+directives when adding local access-control rules to either directory.
+
Sensitive Directories
---------------------
@@ -141,7 +186,13 @@ For additional protection, restrict access to the provisioning subnet::
# Apache 2.4+
- Options FollowSymLinks Includes MultiViews
+ Options FollowSymLinks
+
+ Header always set X-Frame-Options SAMEORIGIN
+ Header always set X-Content-Type-Options nosniff
+ Header always set Content-Security-Policy "script-src 'self' 'unsafe-eval'"
+ Header always set X-Permitted-Cross-Domain-Policies none
+
AllowOverride None
Require ip 10.0.0.0/16
diff --git a/xCAT-test/autotest/bundle/rhels_ppc_daily.bundle b/xCAT-test/autotest/bundle/rhels_ppc_daily.bundle
index 50a0de914..d95863ff3 100644
--- a/xCAT-test/autotest/bundle/rhels_ppc_daily.bundle
+++ b/xCAT-test/autotest/bundle/rhels_ppc_daily.bundle
@@ -100,6 +100,9 @@ gettab_key_table
getmacs_d
getmacs_f_D
getmacs_noderange
+httpd_security_headers_install
+httpd_security_headers_tftpboot
+httpd_server_banner_masked
tabdump_servicenode
lsdef_a
lsdef_null
diff --git a/xCAT-test/autotest/bundle/rhels_ppcle_daily.bundle b/xCAT-test/autotest/bundle/rhels_ppcle_daily.bundle
index a39991440..5e1884cb6 100644
--- a/xCAT-test/autotest/bundle/rhels_ppcle_daily.bundle
+++ b/xCAT-test/autotest/bundle/rhels_ppcle_daily.bundle
@@ -106,6 +106,9 @@ gettab_H
gettab_err
gettab_h
gettab_key_table
+httpd_security_headers_install
+httpd_security_headers_tftpboot
+httpd_server_banner_masked
lsdef_a
lsdef_null
lsdef_t
diff --git a/xCAT-test/autotest/bundle/rhels_x86_daily.bundle b/xCAT-test/autotest/bundle/rhels_x86_daily.bundle
index 608e862ee..a8a5e53fb 100644
--- a/xCAT-test/autotest/bundle/rhels_x86_daily.bundle
+++ b/xCAT-test/autotest/bundle/rhels_x86_daily.bundle
@@ -106,6 +106,9 @@ gettab_H
gettab_err
gettab_h
gettab_key_table
+httpd_security_headers_install
+httpd_security_headers_tftpboot
+httpd_server_banner_masked
lsdef_a
lsdef_null
lsdef_t
diff --git a/xCAT-test/autotest/bundle/sles_ppc_daily.bundle b/xCAT-test/autotest/bundle/sles_ppc_daily.bundle
index fcb5b2d5c..7122a39ae 100644
--- a/xCAT-test/autotest/bundle/sles_ppc_daily.bundle
+++ b/xCAT-test/autotest/bundle/sles_ppc_daily.bundle
@@ -68,6 +68,9 @@ gettab_H
gettab_err
gettab_h
gettab_key_table
+httpd_security_headers_install
+httpd_security_headers_tftpboot
+httpd_server_banner_masked
lsdef_a
lsdef_null
lsdef_t
diff --git a/xCAT-test/autotest/bundle/sles_ppcle_daily.bundle b/xCAT-test/autotest/bundle/sles_ppcle_daily.bundle
index db5fb3832..d24c5268c 100644
--- a/xCAT-test/autotest/bundle/sles_ppcle_daily.bundle
+++ b/xCAT-test/autotest/bundle/sles_ppcle_daily.bundle
@@ -72,6 +72,9 @@ gettab_H
gettab_err
gettab_h
gettab_key_table
+httpd_security_headers_install
+httpd_security_headers_tftpboot
+httpd_server_banner_masked
lsdef_a
lsdef_null
lsdef_t
diff --git a/xCAT-test/autotest/bundle/sles_x86_daily.bundle b/xCAT-test/autotest/bundle/sles_x86_daily.bundle
index 33c28f32a..875c477d1 100644
--- a/xCAT-test/autotest/bundle/sles_x86_daily.bundle
+++ b/xCAT-test/autotest/bundle/sles_x86_daily.bundle
@@ -72,6 +72,9 @@ gettab_H
gettab_err
gettab_h
gettab_key_table
+httpd_security_headers_install
+httpd_security_headers_tftpboot
+httpd_server_banner_masked
lsdef_a
lsdef_null
lsdef_t
diff --git a/xCAT-test/autotest/bundle/ubuntu_ppcle_daily.bundle b/xCAT-test/autotest/bundle/ubuntu_ppcle_daily.bundle
index b0182265c..f5aa4d2f2 100644
--- a/xCAT-test/autotest/bundle/ubuntu_ppcle_daily.bundle
+++ b/xCAT-test/autotest/bundle/ubuntu_ppcle_daily.bundle
@@ -48,6 +48,9 @@ gettab_H
gettab_err
gettab_h
gettab_key_table
+httpd_security_headers_install
+httpd_security_headers_tftpboot
+httpd_server_banner_masked
lsdef_a
lsdef_null
lsdef_t
diff --git a/xCAT-test/autotest/bundle/ubuntu_x86_daily.bundle b/xCAT-test/autotest/bundle/ubuntu_x86_daily.bundle
index 154c66025..16681fb82 100644
--- a/xCAT-test/autotest/bundle/ubuntu_x86_daily.bundle
+++ b/xCAT-test/autotest/bundle/ubuntu_x86_daily.bundle
@@ -48,6 +48,9 @@ gettab_H
gettab_err
gettab_h
gettab_key_table
+httpd_security_headers_install
+httpd_security_headers_tftpboot
+httpd_server_banner_masked
lsdef_a
lsdef_null
lsdef_t
diff --git a/xCAT-test/autotest/testcase/httpd/cases0 b/xCAT-test/autotest/testcase/httpd/cases0
new file mode 100644
index 000000000..27d0c3ee7
--- /dev/null
+++ b/xCAT-test/autotest/testcase/httpd/cases0
@@ -0,0 +1,30 @@
+start:httpd_security_headers_install
+description:verify xcat.conf sets the security response headers on /install
+label:mn_only,ci_test,httpd
+cmd:curl -sSI --max-time 15 http://localhost/install/ 2>&1
+check:rc==0
+check:output=~X-Frame-Options:\s*SAMEORIGIN
+check:output=~X-Content-Type-Options:\s*nosniff
+check:output=~Content-Security-Policy:\s*script-src
+check:output=~X-Permitted-Cross-Domain-Policies:\s*none
+end
+
+start:httpd_security_headers_tftpboot
+description:verify xcat.conf sets the security response headers on /tftpboot
+label:mn_only,ci_test,httpd
+cmd:curl -sSI --max-time 15 http://localhost/tftpboot/ 2>&1
+check:rc==0
+check:output=~X-Frame-Options:\s*SAMEORIGIN
+check:output=~X-Content-Type-Options:\s*nosniff
+check:output=~Content-Security-Policy:\s*script-src
+check:output=~X-Permitted-Cross-Domain-Policies:\s*none
+end
+
+start:httpd_server_banner_masked
+description:verify ServerTokens Prod masks the Apache version in the Server header
+label:mn_only,ci_test,httpd
+cmd:curl -sSI --max-time 15 http://localhost/install/ 2>&1
+check:rc==0
+check:output=~Server:\s*Apache
+check:output!~Apache/[0-9]
+end
diff --git a/xCAT-test/unit/httpd_security_packaging.t b/xCAT-test/unit/httpd_security_packaging.t
new file mode 100644
index 000000000..6fc6f18d4
--- /dev/null
+++ b/xCAT-test/unit/httpd_security_packaging.t
@@ -0,0 +1,51 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use File::Spec;
+use FindBin;
+use Test::More;
+
+my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' );
+
+sub read_file {
+ my ($relative_path) = @_;
+ my $path = File::Spec->catfile( $repo_root, split( '/', $relative_path ) );
+ open( my $fh, '<', $path ) or die "Unable to read $path: $!";
+ my $contents = do { local $/; <$fh> };
+ close($fh);
+ return $contents;
+}
+
+my $xcat_spec = read_file('xCAT/xCAT.spec');
+like( $xcat_spec, qr{a2enmod headers},
+ 'management RPM enables mod_headers where Apache requires it' );
+like( $xcat_spec,
+ qr{if \[ "\$1" = "1" \]; then.*?xcatconfig -i.*?else.*?xcatconfig -u -V.*?systemctl is-active --quiet "\$apachedaemon".*?systemctl reload "\$apachedaemon".*?/etc/init\.d/\$apachedaemon reload}s,
+ 'management RPM reloads an active Apache service after upgrades' );
+like( $xcat_spec, qr{apachedaemon='httpd'.*?apachedaemon='apache2'}s,
+ 'management RPM covers both EL and SUSE Apache service names' );
+like( $xcat_spec,
+ qr{/proc/1/root/\. 2>/dev/null.*?systemctl is-active --quiet "\$apachedaemon"}s,
+ 'management RPM skips service management inside image chroots' );
+
+my $xcatsn_spec = read_file('xCATsn/xCATsn.spec');
+like( $xcatsn_spec, qr{a2enmod headers},
+ 'service-node RPM enables mod_headers where Apache requires it' );
+like( $xcatsn_spec,
+ qr{apacheserviceunit='httpd\.service'.*?apacheserviceunit='apache2\.service'}s,
+ 'service-node RPM covers both EL and SUSE Apache service units' );
+like( $xcatsn_spec,
+ qr{# for install or upgrade restart the daemon.*?/etc/init\.d/\$apachedaemon reload.*?systemctl reload \$apacheserviceunit}s,
+ 'service-node RPM reloads Apache after installs and upgrades' );
+
+my $xcat_postinst = read_file('xCAT/debian/postinst');
+like( $xcat_postinst, qr{a2enmod -q headers.*?/etc/init\.d/apache2 restart}s,
+ 'management Debian package enables mod_headers before restarting Apache' );
+
+my $xcatsn_postinst = read_file('xCATsn/debian/postinst');
+like( $xcatsn_postinst,
+ qr{a2enmod -q headers.*?/etc/init\.d/\$apachedaemon reload}s,
+ 'service-node Debian package enables mod_headers before reloading Apache' );
+
+done_testing();
diff --git a/xCAT/debian/postinst b/xCAT/debian/postinst
index 299450e15..d47009379 100644
--- a/xCAT/debian/postinst
+++ b/xCAT/debian/postinst
@@ -45,6 +45,14 @@ case "$1" in
ln -s -f /etc/apache2/conf-available/xcat.conf.apach24 /etc/apache2/conf-enabled/xcat.conf
+ # xcat.conf sets security response headers via mod_headers, which is
+ # not enabled by default on Debian/Ubuntu. Enable it so the headers
+ # take effect (the directives are wrapped in
+ # so a missing module never breaks the apache restart below).
+ if command -v a2enmod >/dev/null 2>&1; then
+ a2enmod -q headers >/dev/null 2>&1 || :
+ fi
+
/etc/init.d/apache2 restart
# Let rsyslogd perform close of any open files
diff --git a/xCAT/xCAT.spec b/xCAT/xCAT.spec
index 57031a8b5..4e563a876 100644
--- a/xCAT/xCAT.spec
+++ b/xCAT/xCAT.spec
@@ -225,6 +225,13 @@ then
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).
+if [ -e /etc/apache2/conf.d/xcat.conf ] && command -v a2enmod >/dev/null 2>&1; then
+ a2enmod headers >/dev/null 2>&1 || :
+fi
+
# Let rsyslogd perform close of any open files
if [ -e /var/run/rsyslogd.pid ]; then
kill -HUP $(/dev/null 2>&1 || :
@@ -258,6 +265,26 @@ fi
mkdir -p /var/log/xcat
date >> /var/log/xcat/upgrade.log
$RPM_INSTALL_PREFIX0/sbin/xcatconfig -u -V >> /var/log/xcat/upgrade.log 2>&1
+
+# 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.
+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'
+ else
+ apachedaemon='apache2'
+ fi
+
+ if command -v systemctl >/dev/null 2>&1; then
+ if systemctl is-active --quiet "$apachedaemon"; then
+ systemctl reload "$apachedaemon" >/dev/null 2>&1 || :
+ fi
+ elif [ -x "/etc/init.d/$apachedaemon" ]; then
+ /etc/init.d/$apachedaemon reload >/dev/null 2>&1 || :
+ fi
+fi
fi
exit 0
diff --git a/xCAT/xcat.conf b/xCAT/xcat.conf
index 6138086c4..c205a24f9 100644
--- a/xCAT/xcat.conf
+++ b/xCAT/xcat.conf
@@ -4,17 +4,31 @@
# http://localhost/xcat-doc/
# Updates to xCAT/xcat.conf should also be made to xCATsn/xcat.conf
#
+ServerTokens Prod
+
AliasMatch ^/install/(.*)$ "/install/$1"
AliasMatch ^/tftpboot/(.*)$ "/tftpboot/$1"
- Options FollowSymLinks Includes MultiViews
+ Options FollowSymLinks
+
+ Header always set X-Frame-Options SAMEORIGIN
+ Header always set X-Content-Type-Options nosniff
+ Header always set Content-Security-Policy "script-src 'self' 'unsafe-eval'"
+ Header always set X-Permitted-Cross-Domain-Policies none
+
AllowOverride None
Order allow,deny
Allow from all
- Options FollowSymLinks Includes MultiViews
+ Options FollowSymLinks
+
+ Header always set X-Frame-Options SAMEORIGIN
+ Header always set X-Content-Type-Options nosniff
+ Header always set Content-Security-Policy "script-src 'self' 'unsafe-eval'"
+ Header always set X-Permitted-Cross-Domain-Policies none
+
AllowOverride None
Order allow,deny
Allow from all
diff --git a/xCAT/xcat.conf.apach24 b/xCAT/xcat.conf.apach24
index d1720149f..0b032f246 100644
--- a/xCAT/xcat.conf.apach24
+++ b/xCAT/xcat.conf.apach24
@@ -4,16 +4,30 @@
# http://localhost/xcat-doc/
# Updates to xCAT/xcat.conf should also be made to xCATsn/xcat.conf
#
+ServerTokens Prod
+
AliasMatch ^/install/(.*)$ "/install/$1"
AliasMatch ^/tftpboot/(.*)$ "/tftpboot/$1"
- Options FollowSymLinks Includes MultiViews
+ Options FollowSymLinks
+
+ Header always set X-Frame-Options SAMEORIGIN
+ Header always set X-Content-Type-Options nosniff
+ Header always set Content-Security-Policy "script-src 'self' 'unsafe-eval'"
+ Header always set X-Permitted-Cross-Domain-Policies none
+
AllowOverride None
Require all granted
- Options FollowSymLinks Includes MultiViews
+ Options FollowSymLinks
+
+ Header always set X-Frame-Options SAMEORIGIN
+ Header always set X-Content-Type-Options nosniff
+ Header always set Content-Security-Policy "script-src 'self' 'unsafe-eval'"
+ Header always set X-Permitted-Cross-Domain-Policies none
+
AllowOverride None
Require all granted
diff --git a/xCATsn/debian/postinst b/xCATsn/debian/postinst
index c7056f716..f674fb7eb 100644
--- a/xCATsn/debian/postinst
+++ b/xCATsn/debian/postinst
@@ -56,6 +56,14 @@ case "$1" in
update-rc.d $apachedaemon enable
+ # xcat.conf sets security response headers via mod_headers, which is not
+ # enabled by default on Debian/Ubuntu. Enable it so the headers take
+ # effect (directives are wrapped in so a missing
+ # module never breaks the apache reload below).
+ if command -v a2enmod >/dev/null 2>&1; then
+ a2enmod -q headers >/dev/null 2>&1 || :
+ fi
+
if [ -f "/proc/cmdline" ]; then # prevent running it during install into chroot image
if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then
systemctl start xcatd.service
diff --git a/xCATsn/xCATsn.spec b/xCATsn/xCATsn.spec
index c69e43643..02a620e5a 100644
--- a/xCATsn/xCATsn.spec
+++ b/xCATsn/xCATsn.spec
@@ -169,6 +169,13 @@ if [ -n "$version" ]; then
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).
+if [ -e /etc/apache2/conf.d/xcat.conf ] && command -v a2enmod >/dev/null 2>&1; then
+ a2enmod headers >/dev/null 2>&1 || :
+fi
+
# Let rsyslogd perform close of any open files
if [ -e /var/run/rsyslogd.pid ]; then
@@ -219,6 +226,7 @@ if [ -e "/etc/redhat-release" ]; then
apacheserviceunit='httpd.service'
else # SuSE
apachedaemon='apache2'
+ apacheserviceunit='apache2.service'
fi
# enable and reload the web server on linux
[ -e "/etc/init.d/$apachedaemon" ] && chkconfig $apachedaemon on
diff --git a/xCATsn/xcat.conf b/xCATsn/xcat.conf
index 6138086c4..c205a24f9 100644
--- a/xCATsn/xcat.conf
+++ b/xCATsn/xcat.conf
@@ -4,17 +4,31 @@
# http://localhost/xcat-doc/
# Updates to xCAT/xcat.conf should also be made to xCATsn/xcat.conf
#
+ServerTokens Prod
+
AliasMatch ^/install/(.*)$ "/install/$1"
AliasMatch ^/tftpboot/(.*)$ "/tftpboot/$1"
- Options FollowSymLinks Includes MultiViews
+ Options FollowSymLinks
+
+ Header always set X-Frame-Options SAMEORIGIN
+ Header always set X-Content-Type-Options nosniff
+ Header always set Content-Security-Policy "script-src 'self' 'unsafe-eval'"
+ Header always set X-Permitted-Cross-Domain-Policies none
+
AllowOverride None
Order allow,deny
Allow from all
- Options FollowSymLinks Includes MultiViews
+ Options FollowSymLinks
+
+ Header always set X-Frame-Options SAMEORIGIN
+ Header always set X-Content-Type-Options nosniff
+ Header always set Content-Security-Policy "script-src 'self' 'unsafe-eval'"
+ Header always set X-Permitted-Cross-Domain-Policies none
+
AllowOverride None
Order allow,deny
Allow from all
diff --git a/xCATsn/xcat.conf.apach24 b/xCATsn/xcat.conf.apach24
index 0abfff396..251111903 100644
--- a/xCATsn/xcat.conf.apach24
+++ b/xCATsn/xcat.conf.apach24
@@ -4,16 +4,30 @@
# http://localhost/xcat-doc/
# Updates to xCAT/xcat.conf should also be made to xCATsn/xcat.conf
#
+ServerTokens Prod
+
AliasMatch ^/install/(.*)$ "/install/$1"
AliasMatch ^/tftpboot/(.*)$ "/tftpboot/$1"
- Options FollowSymLinks Includes MultiViews
+ Options FollowSymLinks
+
+ Header always set X-Frame-Options SAMEORIGIN
+ Header always set X-Content-Type-Options nosniff
+ Header always set Content-Security-Policy "script-src 'self' 'unsafe-eval'"
+ Header always set X-Permitted-Cross-Domain-Policies none
+
AllowOverride None
Require all granted
- Options FollowSymLinks Includes MultiViews
+ Options FollowSymLinks
+
+ Header always set X-Frame-Options SAMEORIGIN
+ Header always set X-Content-Type-Options nosniff
+ Header always set Content-Security-Policy "script-src 'self' 'unsafe-eval'"
+ Header always set X-Permitted-Cross-Domain-Policies none
+
AllowOverride None
Require all granted