在RHEL 7上使用SELinux进行高级Apache配置

时间:2020-03-21 11:49:54  来源:igfitidea点击:

我们将配置几个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