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