在RHEL 7上使用SELinux进行高级Apache配置
我们将配置几个Apache虚拟主机,配置目录的访问限制,部署基本的CGI应用程序并配置TLS安全性。
安装Apache
我们在本文中使用RHEL 7.0虚拟服务器。
安装Web服务器软件包组:
# yum groupinstall -y web-server
如果要查看还有哪些其他组可用,我们可以执行以下操作:
# yum group list ids
在启动时启用Apache服务并配置防火墙:
# systemctl enable httpd && systemctl start httpd # firewall-cmd --permanent --add-service={http,https} # firewall-cmd --reload
Apache SELinux相关设置
看看什么提供了我们需要的软件包:
# yum provides *\sealert *\semanage *\sepolicy
安装所需的软件包:
# yum install -y policycoreutils-python policycoreutils-devel setroubleshoot-server
让我们看看默认的SELinux上下文:
# ls -Z /var/www drwxr-xr-x. root root system_u:object_r:httpd_sys_script_exec_t:s0 cgi-bin drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 html
Apache有许多SELinux布尔值和上下文类型。
要获得可用功能的概述,请执行以下操作:
# getsebool -a | grep httpd # semanage fcontext --list | grep httpd
SELinux布尔值
下面列出了一些最重要的布尔设置及其默认值。
要允许Apache修改用于公共文件传输服务的公共文件(具有public_content_rw_t类型):
httpd_anon_write --> off
要允许Apache脚本和模块使用TCP连接到网络,请执行以下操作:
httpd_can_network_connect --> off
要允许Apache脚本和模块通过网络连接到数据库:
httpd_can_network_connect_db --> off
要允许Apache充当连接到ftp端口和临时端口的FTP客户端,请执行以下操作:
httpd_can_connect_ftp --> off
要允许Apache连接到LDAP端口,请执行以下操作:
httpd_can_connect_ldap --> off
要允许Apache守护程序发送邮件,请执行以下操作:
httpd_can_sendmail --> off
要允许Apache CGI支持:
httpd_enable_cgi --> on
要允许Apache读取主目录:
httpd_enable_homedirs --> off
统一Apache与终端进行通信。
在终端上输入证书的密码短语时需要:
httpd_tty_comm --> off
要允许Apache访问CIFS或者NFS文件系统:
httpd_use_cifs --> off httpd_use_nfs --> off
记住上面所有的SELinux设置可能很棘手,因此我们需要知道可以获得帮助的地方。
获取所需的SELinux信息的最强大方法是使用手册页。
但是,在RHEL 7上,默认情况下未安装SELinux手册页。
我们需要安装它们并更新手动页面索引缓存:
# sepolicy manpage -a -p /usr/share/man/man8 # mandb
现在我们可以搜索SELinux页面:
# man -k _selinux | less
我们应该为Apache找到一个:
httpd_selinux (8) - Security Enhanced Linux Policy for the httpd processes
SELinux上下文
为了减轻安全风险,可以使用不同的SELinux上下文类型。
下面列出了一些:
- httpd_sys_content_t-在允许Apache访问的目录上设置,
- httpd_sys_content_rw_t-在允许Apache进行读/写访问的目录上设置,
- httpd_sys_script_exec_t-用于包含可执行脚本的目录。
为了允许Apache修改用于公共文件传输服务的公共文件,目录/文件必须标记为public_content_rw_t。
SELinux端口
SELinux阻止Apache绑定非默认端口。
要查看允许哪些端口,请执行以下操作:
# semanage port --list | grep http http_cache_port_t tcp 8080, 8118, 8123, 10001-10010 http_cache_port_t udp 3130 http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
配置虚拟主机
我们将配置两个虚拟主机,vhost1和vhost2.
打开文件“/etc/hosts”并添加以下行:
10.8.8.71 vhost1.rhce.local vhost2.rhce.local
我们在实验室中使用“ rhce.local”域,我们可能会猜到这是对RHCE的准备。
基本虚拟主机vhost1
创建具有一些基本内容的DocumentRoot:
# mkdir /var/www/html/vhost1 # echo "vhost1" >/var/www/html/vhost1/index.html # touch /var/www/html/vhost1/favicon.ico
恢复默认的SELinux安全上下文:
# restorecon -Rv /var/www/html/vhost1
创建虚拟主机配置文件“ /etc/httpd/conf.d/vhosts.conf”并添加以下内容:
<VirtualHost *:80> ServerAdmin Hyman@theitroad ServerName vhost1.rhce.local DocumentRoot "/var/www/html/vhost1" <Directory "/var/www/html/vhost1"> Options None AllowOverride None Require all granted </Directory> LogLevel info ErrorLog "logs/vhost1-error_log" CustomLog "logs/vhost1-access_log" common </VirtualHost>
打开文件“ /etc/httpd/conf/httpd.conf”,找到以下行:
<Directory "/var/www/html">
并放置以下内容:
Options None
现在禁用默认的SSL站点:
# mv /etc/httpd/conf.d/ssl.conf /etc/httpd/conf.d/ssl.conf.disabled
测试Apache配置并重新启动服务:
# httpd -t # systemctl restart httpd
检查虚拟主机:
# httpd -t -D DUMP_VHOSTS VirtualHost configuration: *:80 is a NameVirtualHost default server vhost1.rhce.local (/etc/httpd/conf.d/vhosts.conf:1) port 80 namevhost vhost1.rhce.local (/etc/httpd/conf.d/vhosts.conf:1) port 80 namevhost vhost1.rhce.local (/etc/httpd/conf.d/vhosts.conf:1)
安装CLI浏览器并测试站点:
# yum install -y elinks # elinks http://vhost1.rhce.local/
TCP端口8888和已安装的iSCSI设备vhost2上的虚拟主机
将以下内容添加到'/etc/httpd/conf/httpd.conf'中:
Listen 80 Listen 8888
如果现在尝试重新启动Apache服务,则会收到以下错误:
httpd[]: (13)Permission denied: AH00072: make_sock: could not bind to addres...:8888
SELinux阻止Apache绑定到端口8888.
允许Apache监听TCP端口8888:
# semanage port -a -t http_port_t -p tcp 8888
配置防火墙以允许入站流量:
# firewall-cmd --permanent --add-port=8888/tcp # firewall-cmd --reload
创建一个DocumentRoot:
# mkdir /mnt/block1/vhost2 # echo "vhost2" >/mnt/block1/vhost2/index.html
为'/mnt/block1'下的所有内容添加文件上下文:
# semanage fcontext -a -t httpd_sys_content_t "/mnt/block1(/.*)?" # restorecon -Rv /mnt/block1
打开文件“ /etc/httpd/conf.d/vhosts.conf”并添加以下内容:
<VirtualHost *:8888> ServerAdmin Hyman@theitroad ServerName vhost2.rhce.local DocumentRoot "/mnt/block1/vhost2" <Directory "/mnt/block1/vhost2"> Options None AllowOverride None Require all granted </Directory> LogLevel error ErrorLog "logs/vhost2-error_log" CustomLog "logs/vhost2-access_log" common </VirtualHost>
检查配置并重新启动服务:
# httpd -t # systemctl restart httpd
现在应列出一个新的虚拟主机:
# httpd -t -D DUMP_VHOSTS VirtualHost configuration: *:80 is a NameVirtualHost default server vhost1.rhce.local (/etc/httpd/conf.d/vhosts.conf:1) port 80 namevhost vhost1.rhce.local (/etc/httpd/conf.d/vhosts.conf:1) port 80 namevhost vhost1.rhce.local (/etc/httpd/conf.d/vhosts.conf:1) *:8888 is a NameVirtualHost default server vhost2.rhce.local (/etc/httpd/conf.d/vhosts.conf:17) port 8888 namevhost vhost2.rhce.local (/etc/httpd/conf.d/vhosts.conf:17) port 8888 namevhost vhost2.rhce.local (/etc/httpd/conf.d/vhosts.conf:17)
测试:
# elinks http://vhost2.rhce.local:8888/
现在,如果我们在NFS共享上有一个虚拟主机,我们希望允许Apache访问NFS文件系统。
我们需要通过启用httpd_use_nfs布尔值来告知SELinux:
# setsebool -P httpd_use_nfs=1
允许Apache访问NFS上的文件而不启用上面的布尔值的另一种方法是将它们标记为httpd_sys_content_t或者public_content_t(SELinux fcontext),否则将丢失搜索权限。
在目录上配置访问限制
我们将基于每个目录配置身份验证。
基于主机的安全性
在vhost1上,创建一个可用于基于主机的限制的私有目录:
# mkdir /var/www/html/vhost1/private # echo "private-vh1" >/var/www/html/vhost1/private/index.html
打开文件“ /etc/httpd/conf.d/vhosts.conf”,并在vhost1配置下添加以下内容:
<Directory "/var/www/html/vhost1/private"> AllowOverride None Options None Require ip 10.8.8.1/32 </Directory>
仅允许从IP 10.8.8.1/32访问私有目录。
现在,如果我们在本地尝试,我们将收到403拒绝权限错误。
# elinks http://vhost1.rhce.local/private/
如果我们想允许访问除IP 10.8.8.1/32之外的每个客户端,则可以通过以下方式配置虚拟主机:
<Directory "/var/www/html/vhost1/private"> AllowOverride None Options None <RequireAll> Require all granted Require not ip 10.8.8.1/32 </RequireAll> </Directory>
来自地址10.8.8.1的访问者将看不到任何内容。
根据Apache文档,Require提供了多种不同的方式来允许或者拒绝对资源的访问。
结合RequireAll,RequireAny和RequireNone指令,可以以任意复杂的方式组合这些要求,以实施我们想要的任何访问策略。
默认情况下,所有Require伪指令都被视为包含在RequireAny容器伪指令中。
换句话说,如果任何指定的授权方法成功,则将授予授权。
例如,下面的访问限制将允许除my1337.hacker.local,*。
example.com和子网10.8.8.0/24之外的任何客户端连接到我们的某些部分:
<RequireAll> Require all granted Require not host my1337.hacker.local Require not host example.com Require not ip 10.8.8.0/24 </RequireAll
仅匹配完整的组件,因此上面的示例将匹配test.example.org,但不会匹配testexample.org。
无论HostnameLookups指令的设置如何,此配置都将导致Apache在客户端IP地址上执行双向反向DNS查找。
它将对IP地址进行反向DNS查找,以找到关联的主机名,然后对主机名进行正向查找,以确保其与原始IP地址匹配。
仅当正向和反向DNS一致且主机名匹配时,才允许访问。
基于用户的安全性
在vhost2上,创建一个可用于基于用户的身份验证的私有目录:
# mkdir /mnt/block1/vhost2/private # echo "private-vh2" >/mnt/block1/vhost2/private/index.html
创建一个密码文件“/etc/httpd/conf/htpasswd”以与Apache一起使用,并添加两个用户:alice和vince:
# htpasswd -c /etc/httpd/conf/htpasswd alice # htpasswd /etc/httpd/conf/htpasswd vince
打开文件“ /etc/httpd/conf.d/vhosts.conf”,并在vhost2配置下添加以下内容:
<Directory "/mnt/block1/vhost2/private"> AuthType Basic AuthName "Private" AuthUserFile "/etc/httpd/conf/htpasswd" Require user vince </Directory>
为了使Apache身份验证配置更容易记住,我们可以使用httpd-manual包:
# yum install -y httpd-manual
可以在本地主机上访问手册页:
# elinks http://localhost/manual/
但是,与浏览整个站点相比,有时更容易打开我们感兴趣的特定页面(它们位于'/usr/share/httpd/manual /'下),例如,基本身份验证手册:
# elinks /usr/share/httpd/manual/mod/mod_auth_basic.html
检查配置是否存在错误,然后重新启动服务:
# httpd -t # systemctl restart httpd
测试时间,只有使用vince的凭据登录才能允许访问:
# elinks http://vhost2.rhce.local:8888/private/
基于组的安全性
在vhost2上,创建一个可用于基于组的身份验证的组目录:
# mkdir /mnt/block1/vhost2/group # echo "group" >/mnt/block1/vhost2/group/index.html
将另一个用户sandy添加到Apache密码文件中:
# htpasswd /etc/httpd/conf/htpasswd sandy
创建一个组文件“/etc/httpd/conf/htgroup”以与Apache一起使用,并添加以下内容:
admins: alice sandy
打开文件“ /etc/httpd/conf.d/vhosts.conf”,并在vhost2配置下添加以下内容:
<Directory "/mnt/block1/vhost2/group"> AuthType Basic AuthName "Group" AuthGroupFile "/etc/httpd/conf/htgroup" AuthUserFile "/etc/httpd/conf/htpasswd" Require group admins </Directory>
与往常一样,检查配置并重新启动服务:
# httpd -t # systemctl restart httpd
现在,使用vince的凭据登录将不起作用,因为只有alice和sandy是admins组的成员。
# elinks http://vhost2.rhce.local:8888/group/
配置组管理的内容
默认情况下,只有root用户具有对DocumentRoot的写权限。
如果我们有一组需要将文件写入DocumentRoot的用户,则需要配置写访问权限。
要设置一个允许devops组成员对vhost2虚拟主机的DocumentRoot进行写访问的ACL,我们可以使用以下命令:
# groupadd devops # setfacl -R -m g:devops:rwX /mnt/block1/vhost2 # setfacl -R -m d:g:devops:rwx /mnt/block1/vhost2
大写的X用于仅将执行位设置为目录,而不是文件。
# getfacl /mnt/block1/vhost2 # file: mnt/block1/vhost2 # owner: root # group: root user::rwx group::r-x group:devops:rwx mask::rwx other::r-x default:user::rwx default:group::r-x default:group:devops:rwx default:mask::rwx default:other::r-x
将用户添加到devops组,登录并尝试创建一个新文件,以查看所有文件是否均按预期工作:
# useradd -m -G devops dev1 # su - dev1 $touch /mnt/block1/vhost2/somefile
编写基本的CGI应用程序
我们将在虚拟主机vhost2上编写一个bash应用程序,该应用程序将电子邮件发送给root用户。
我们将需要mailx包:
# yum install -y mailx
目前应该没有邮件:
# mailx No mail for root
创建一个文件夹来存储Web应用程序:
# mkdir /mnt/block1/vhost2/app
创建一个Web应用程序并使它可执行:
# touch /mnt/block1/vhost2/app/email.sh # chmod a+x /mnt/block1/vhost2/app/email.sh
现在,根据Apache文档,“常规”编程和CGI编程之间有两个主要区别。
首先,我们CGI程序的所有输出都必须以MIME类型的标头开头。
这是HTTP标头,告诉客户端它正在接收哪种内容。
在大多数情况下,这看起来类似于'Content-type:text/html'。
其次,我们的输出必须为HTML或者浏览器将能够显示的其他格式。
在大多数情况下,这将是HTML,但有时我们可能会编写一个CGI程序,该程序输出gif图像或者其他非HTML内容。
考虑到上述因素,我们可以在CGI应用程序中使用以下内容:
#!/bin/bash echo -e "Content-type: text/html\n\n"; echo "<html>"; echo "<body>"; echo "email from httpd"|mailx -s WebApp root; echo "Email has been sent."; echo "</body>"; echo "</html>";
第一行告诉Apache,可以通过将文件提供给位于'/bin/bash'位置的解释器来执行该程序。
第二行显示我们所讨论的内容类型声明,其后是两个回车换行符。
这会在标头之后放置一个空白行,以指示HTTP标头的结 tail和正文的开头。
其他行将电子邮件发送给root用户,并打印字符串“电子邮件已发送”。
另一件事,我们需要注意app文件夹的SELinux上下文。
让我们看一下默认值:
# ls -Z /var/www/ drwxr-xr-x. root root system_u:object_r:httpd_sys_script_exec_t:s0 cgi-bin drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 html
我们将应用与cgi-bin文件夹相同的上下文:
# semanage fcontext -a -t httpd_sys_script_exec_t "/mnt/block1/vhost2/app(/.*)?" # restorecon -Rv /mnt/block1/vhost2/app
注意,在man semanage-fcontext结 tail的“示例”部分中有一个很好的示例。
我们还需要允许Apache永久发送电子邮件,否则SELinux将阻止它们:
# setsebool -P httpd_can_sendmail=1
打开文件“ /etc/httpd/conf.d/vhosts.conf”,并在vhost2配置下添加以下内容:
<Directory "/mnt/block1/vhost2/app"> AllowOverride None Options ExecCGI AddHandler cgi-script .sh Require all granted </Directory>
我们基本上希望允许对app目录中以.sh结 tail的任何文件执行CGI程序。
还记得我们之前安装的http-manual软件包吗?
方便的CGI配置示例可以在这里找到:
# elinks /usr/share/httpd/manual/howto/cgi.html
验证配置并重新启动服务:
# httpd -t # systemctl restart httpd
测试:
# elinks http://vhost2.rhce.local:8888/app/email.sh
应该收到来自Apache的电子邮件:
# mailx -H > 1 Apache Mon Jan 30 19:27 19/611 "WebApp"
配置TLS安全性
TLS虚拟主机
如果尚未配置防火墙,请首先照顾它:
# firewall-cmd --permanent --add-service=https # firewall-cmd --reload
我们可以使用openssl或者genkey生成SSL证书。
对于后者,请安装以下内容:
# yum install crypto-utils
如果我们要使用genkey,它将看起来像这样:
# genkey --days 365 vhost1.rhce.local
但是,使用openssl可以更快地创建证书。
对于那些努力记住语法的人,请查看文件'/etc/pki/tls/certs/make-dummy-cert'。
它包含以下行:
openssl req -newkey rsa:2048 -keyout $PEM1 -nodes -x509 -days 365 -out $PEM2
这就是生成新的自签名SSL证书所需要的。
# openssl req -newkey rsa:2048 -keyout /etc/pki/tls/private/vhost1.rhce.local.key \ -nodes -x509 -days 365 -out /etc/pki/tls/certs/vhost1.rhce.local.crt
确保公共名称与为其生成证书的虚拟主机的ServerName匹配。
启用默认的SSL主机:
# mv /etc/httpd/conf.d/ssl.conf.disabled /etc/httpd/conf.d/ssl.conf
打开文件并在“ <VirtualHost default:443>”下添加以下内容:
DocumentRoot "/var/www/html/vhost1" ServerName vhost1.rhce.local SSLEngine on SSLProtocol all -SSLv2 -SSLv3 SSLCertificateFile /etc/pki/tls/certs/vhost1.rhce.local.crt SSLCertificateKeyFile /etc/pki/tls/private/vhost1.rhce.local.key
检查配置并重新启动服务:
# httpd -t # systemctl restart httpd
TLS虚拟主机现在应该存在:
# httpd -t -D DUMP_VHOSTS VirtualHost configuration: *:443 is a NameVirtualHost default server vhost1.rhce.local (/etc/httpd/conf.d/ssl.conf:56) port 443 namevhost vhost1.rhce.local (/etc/httpd/conf.d/ssl.conf:56) port 443 namevhost vhost1.rhce.local (/etc/httpd/conf.d/ssl.conf:56) *:80 is a NameVirtualHost default server vhost1.rhce.local (/etc/httpd/conf.d/vhosts.conf:1) port 80 namevhost vhost1.rhce.local (/etc/httpd/conf.d/vhosts.conf:1) port 80 namevhost vhost1.rhce.local (/etc/httpd/conf.d/vhosts.conf:1) *:8888 is a NameVirtualHost default server vhost2.rhce.local (/etc/httpd/conf.d/vhosts.conf:26) port 8888 namevhost vhost2.rhce.local (/etc/httpd/conf.d/vhosts.conf:26) port 8888 namevhost vhost2.rhce.local (/etc/httpd/conf.d/vhosts.conf:26)
请注意,elink可能不适用于SSL,而应使用curl测试:
# curl --insecure https://vhost1.rhce.local vhost1
检查是否已禁用SSLv3(目前认为它不安全):
# curl --insecure --sslv3 https://vhost1.rhce.local curl: (35) Cannot communicate securely with peer: no common encryption algorithm(s).
重定向到TLS
我们希望将所有到达vhost1的HTTP流量自动重定向到HTTPS。
我们可以使用mod_rewrite配置以上内容:
RewriteEngine On RewriteCond %{HTTPS} off RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
但是,如果我们查看手册,则可以使用一个简单的重定向:
# elinks /usr/share/httpd/manual/rewrite/avoid.html
让我们将以下行添加到vhost1配置中:
Redirect/https://vhost1.rhce.local/
测试配置并重新启动服务:
# httpd -t # systemctl restart httpd
使用curl进行测试,自动重写应该可以正常工作:
# curl -v -L --insecure http://vhost1.rhce.local * About to connect() to vhost1.rhce.local port 80 (#0) * Trying 10.8.8.71... * Connected to vhost1.rhce.local (10.8.8.71) port 80 (#0) > GET/HTTP/1.1 > User-Agent: curl/7.29.0 > Host: vhost1.rhce.local > Accept: */* > < HTTP/1.1 302 Found < Date: Mon, 30 Jan 2015 18:55:44 GMT < Server: Apache/2.4.6 (Red Hat) OpenSSL/1.0.1e-fips mod_fcgid/2.3.9 < Location: https://vhost1.rhce.local/ < Content-Length: 210 < Content-Type: text/html; charset=iso-8859-1 < * Ignoring the response-body * Connection #0 to host vhost1.rhce.local left intact * Issue another request to this URL: 'https://vhost1.rhce.local/' * Found bundle for host vhost1.rhce.local: 0x7542c0 * About to connect() to vhost1.rhce.local port 443 (#1) * Trying 10.8.8.71... * Connected to vhost1.rhce.local (10.8.8.71) port 443 (#1) * Initializing NSS with certpath: sql:/etc/pki/nssdb * skipping SSL peer certificate verification * SSL connection using TLS_DHE_RSA_WITH_AES_128_CBC_SHA * Server certificate: * subject: CN=vhost1.rhce.local,O=vhost1,L=vhost1,C=GB * start date: Jan 30 18:22:37 2015 GMT * expire date: Jan 30 18:22:37 2016 GMT * common name: vhost1.rhce.local * issuer: CN=vhost1.rhce.local,O=vhost1,L=vhost1,C=GB > GET/HTTP/1.1 > User-Agent: curl/7.29.0 > Host: vhost1.rhce.local > Accept: */* > < HTTP/1.1 200 OK < Date: Mon, 30 Jan 2015 18:55:44 GMT < Server: Apache/2.4.6 (Red Hat) OpenSSL/1.0.1e-fips mod_fcgid/2.3.9 < Last-Modified: Mon, 30 Jan 2015 17:19:37 GMT < ETag: "7-534127600f458" < Accept-Ranges: bytes < Content-Length: 7 < Content-Type: text/html; charset=UTF-8 < vhost1 * Connection #1 to host vhost1.rhce.local left intact