Advanced Apache Configuration with SELinux on RHEL 7

We are going to configure a couple of Apache virtual hosts, configure access restrictions on directories, deploy a basic CGI application and configure TLS security. 

Install Apache

We use a RHEL 7.0 virtual server in this article.

Install a web server package group:

# yum groupinstall -y web-server

If we want to see what other groups are available, we can do:

# yum group list ids

Enable Apache service on boot and configure firewall:

# systemctl enable httpd && systemctl start httpd
# firewall-cmd --permanent --add-service={http,https}
# firewall-cmd --reload

Apache SELinux Related Settings

See what provides the packages that we need:

# yum provides *\sealert *\semanage *\sepolicy

Install required packages:

# yum install -y policycoreutils-python policycoreutils-devel setroubleshoot-server

Let us see the default SELinux context:

# 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

There are many SELinux booleans and context types for Apache. To get an overview of what’s available, do:

# getsebool -a | grep httpd
# semanage fcontext --list | grep httpd

SELinux Booleans

Some the most significant boolean settings with their default values are listed below.

To allow Apache to modify public files (with the public_content_rw_t type) used for public file transfer services:

httpd_anon_write --> off

To allow Apache scripts and modules to connect to the network using TCP:

httpd_can_network_connect --> off

To allow Apache scripts and modules to connect to databases over the network:

httpd_can_network_connect_db --> off

To allow Apache to act as an FTP client connecting to the ftp port and ephemeral ports:

httpd_can_connect_ftp --> off

To allow Apache to connect to the LDAP port:

httpd_can_connect_ldap --> off

To allow Apache daemon to send mail:

httpd_can_sendmail --> off

To allow Apache CGI support:

httpd_enable_cgi --> on

To allow Apache to read home directories:

httpd_enable_homedirs --> off

To unify Apache to communicate with the terminal. Needed for entering the passphrase for certificates at the terminal:

httpd_tty_comm --> off

To allow Apache to access CIFS or NFS file systems:

httpd_use_cifs --> off
httpd_use_nfs --> off

It can be tricky to remember all those SELinux setting above, therefore we need to know the place we can get some help.

The most powerful way of getting the SELinux information we need is by using man pages. On RHEL 7, however, SELinux man pages are not installed by default. We need to install them and update the manual page index caches:

# sepolicy manpage -a -p /usr/share/man/man8
# mandb

We can now search for SELinux pages:

# man -k _selinux | less

And we should find one for Apache:

httpd_selinux (8)   - Security Enhanced Linux Policy for the httpd processes

SELinux Context

To mitigate security risks, different SELinux context types are available. Some are listed below:

  1. httpd_sys_content_t – set on directories that Apache is allowed access to,
  2. httpd_sys_content_rw_t – set on directories that Apache is allowed read/write access to,
  3. httpd_sys_script_exec_t – used for directories that contain executable scripts.

To allow Apache to modify public files used for public file transfer services, directories/files must be labeled public_content_rw_t.

SELinux Ports

SELinux prevents Apache from binding on non-default ports. To see which ports are allowed, do:

# 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

Configure a Virtual Host

We are going to configure two virtual hosts, vhost1 and vhost2.

Open the file /etc/hosts and add the following line:

10.8.8.71 vhost1.rhce.local vhost2.rhce.local

We use the “rhce.local” domain within our lab. As you may guess, it’s an RHCE preparation.

Basic Virtual Host vhost1

Create a DocumentRoot with some basic content:

# mkdir /var/www/html/vhost1
# echo "vhost1" >/var/www/html/vhost1/index.html
# touch /var/www/html/vhost1/favicon.ico

Restore default SELinux security contexts:

# restorecon -Rv /var/www/html/vhost1

Create a virtual host configuration file /etc/httpd/conf.d/vhosts.conf and add the following:

<VirtualHost *:80>
ServerAdmin root@localhost
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>

Open the file /etc/httpd/conf/httpd.conf, find this line:

<Directory "/var/www/html">

And put the following:

Options None

Disable the default SSL site for now:

# mv /etc/httpd/conf.d/ssl.conf /etc/httpd/conf.d/ssl.conf.disabled

Test Apache configuration and restart the service:

# httpd -t
# systemctl restart httpd

Check virtual hosts:

# 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)

Install a CLI browser and test the site:

# yum install -y elinks
# elinks http://vhost1.rhce.local/

Virtual Host on a TCP Port 8888 and Mounted iSCSI Device vhost2

Add the following to /etc/httpd/conf/httpd.conf:

Listen 80
Listen 8888

If we now try to restart the Apache service, we’ll get the following error:

httpd[]: (13)Permission denied: AH00072: make_sock: could not bind to addres...:8888

SELinux is preventing Apache from binding to port 8888.

Allow Apache to listen on TCP port 8888:

# semanage port -a -t http_port_t -p tcp 8888

Configure firewall to allow inbound traffic:

# firewall-cmd --permanent --add-port=8888/tcp
# firewall-cmd --reload

Create a DocumentRoot:

# mkdir /mnt/block1/vhost2
# echo "vhost2" >/mnt/block1/vhost2/index.html

Add file-context for everything under /mnt/block1:

# semanage fcontext -a -t httpd_sys_content_t "/mnt/block1(/.*)?"
# restorecon -Rv /mnt/block1

Open the file /etc/httpd/conf.d/vhosts.conf and add the following:

<VirtualHost *:8888>
ServerAdmin root@localhost
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>

Check configuration and restart the service:

# httpd -t
# systemctl restart httpd

A new virtual host should now be listed:

# 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)

Test:

# elinks http://vhost2.rhce.local:8888/

Now, if we had a virtual host on an NFS share, we would want to allow Apache to access NFS file system. We would need to tell SELinux about this by enabling the httpd_use_nfs boolean:

# setsebool -P httpd_use_nfs=1

Another way to allow Apache to access files on NFS without enabling the boolean above would be to label them as httpd_sys_content_t or public_content_t (SELinux fcontext), othewise search permissions will be missing.

Configure Access Restrictions on Directories

We are going to configure authentication on a per-directory basis.

Host-based Security

On the vhost1, create a private directory that we can use with host-based restrictions:

# mkdir /var/www/html/vhost1/private
# echo "private-vh1" >/var/www/html/vhost1/private/index.html

Open the file /etc/httpd/conf.d/vhosts.conf and add the following under the vhost1 configuration:

<Directory "/var/www/html/vhost1/private">
   AllowOverride None
   Options None
   Require ip 10.8.8.1/32
</Directory>

Access to the private directory is allowed from the IP 10.8.8.1/32 only.

Now, if we try locally, we should get a 403 permission denied error.

# elinks http://vhost1.rhce.local/private/

If we wanted to allow access to every client but the IP 10.8.8.1/32, we would configure the virtual host this way:

<Directory "/var/www/html/vhost1/private">
   AllowOverride None
   Options None
   <RequireAll>
     Require all granted
     Require not ip 10.8.8.1/32
   </RequireAll>
</Directory>

Visitors coming from the address 10.8.8.1 will not be able to see any content.

As per Apache documentation, the Require provides a variety of different ways to allow or deny access to resources. In conjunction with the RequireAll, RequireAny and RequireNone directives, these requirements may be combined in arbitrarily complex ways, to enforce whatever access policy we want to have.

By default all Require directives are handled as though contained within a RequireAny container directive. In other words, if any of the specified authorisation methods succeed, then authorisation is granted.

For example, access restriction below would allow connections to portions of our site from any client but my1337.hacker.local, *.example.com and subnet 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

Only complete components are matched, so the above example will match test.example.org but it will not match testexample.org. This configuration will cause Apache to perform a double reverse DNS lookup on the client IP address, regardless of the setting of the HostnameLookups directive. It will do a reverse DNS lookup on the IP address to find the associated hostname, and then do a forward lookup on the hostname to assure that it matches the original IP address. Only if the forward and reverse DNS are consistent and the hostname matches will access be allowed.

User-based Security

On the vhost2, create a private directory that we can use for user-based authentication:

# mkdir /mnt/block1/vhost2/private
# echo "private-vh2" >/mnt/block1/vhost2/private/index.html

Create a password file /etc/httpd/conf/htpasswd to use with Apache, and add a couple of users, alice and vince:

# htpasswd -c /etc/httpd/conf/htpasswd alice
# htpasswd /etc/httpd/conf/htpasswd vince

Open the file /etc/httpd/conf.d/vhosts.conf and add the following under the vhost2 configuration:

<Directory "/mnt/block1/vhost2/private">
   AuthType Basic
   AuthName "Private"
   AuthUserFile "/etc/httpd/conf/htpasswd"
   Require user vince
</Directory>

To make Apache authentication configuration easier to remember, we can use the httpd-manual package:

# yum install -y httpd-manual

Manual pages can be accessed on the localhost:

# elinks http://localhost/manual/

However, rather than browsing the whole site, it’s sometimes easier to open specific pages (they are located under /usr/share/httpd/manual/) which we are interested in, for example, a manual to basic authentication:

# elinks /usr/share/httpd/manual/mod/mod_auth_basic.html

Check configuration for errors and restart the service:

# httpd -t
# systemctl restart httpd

Testing time, only logging in with vince’s credentials should allow access:

# elinks http://vhost2.rhce.local:8888/private/

Group Based Security

On the vhost2, create a group directory that we can use for group-based authentication:

# mkdir /mnt/block1/vhost2/group
# echo "group" >/mnt/block1/vhost2/group/index.html

Add another user, sandy, to the Apache password file:

# htpasswd /etc/httpd/conf/htpasswd sandy

Create a group file /etc/httpd/conf/htgroup to use with Apache, and add the following:

admins: alice sandy

Open the file /etc/httpd/conf.d/vhosts.conf and add the following under the vhost2 configuration:

<Directory "/mnt/block1/vhost2/group">
   AuthType Basic
   AuthName "Group"
   AuthGroupFile "/etc/httpd/conf/htgroup"
   AuthUserFile "/etc/httpd/conf/htpasswd"
   Require group admins
</Directory>

As always, check the config and restart the service:

# httpd -t
# systemctl restart httpd

Now logging in with vince’s credentials shouldn’t work, as only alice and sandy are members of the admins group.

# elinks http://vhost2.rhce.local:8888/group/

Configure Group-managed Content

By default, only the root user has write access to the DocumentRoot. If we have a group of users who need to be able to write files to the DocumentRoot, we need to configure write access.

To set an ACL that allows members of the devops group write access to the DocumentRoot of the vhost2 virtual host, we can use the following commands:

# groupadd devops
# setfacl -R -m g:devops:rwX /mnt/block1/vhost2
# setfacl -R -m d:g:devops:rwx /mnt/block1/vhost2

An uppercase X is used to set the execute bit only to directories and not to files.

# 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

Add a user to the devops group, login and try to create a new file to see if all works as expected:

# useradd -m -G devops dev1
# su - dev1
$ touch /mnt/block1/vhost2/somefile

Write a Basic CGI Application

We are going to write a bash application on the virtual host vhost2 that sends an email to the root user. We are going to need the mailx package:

# yum install -y mailx

There should be no mail at the moment:

# mailx
No mail for root

Create a folder to store web applications:

# mkdir /mnt/block1/vhost2/app

Create a web application and make it executable:

# touch /mnt/block1/vhost2/app/email.sh
# chmod a+x /mnt/block1/vhost2/app/email.sh

Now, as per Apache documentation, there are two main differences between “regular” programming, and CGI programming.

First, all output from our CGI program must be preceded by a MIME-type header. This is HTTP header that tells the client what sort of content it is receiving. Most of the time, this will look like Content-type: text/html.

Secondly, our output needs to be in HTML, or some other format that a browser will be able to display. Most of the time, this will be HTML, but occasionally we might write a CGI program that outputs a gif image, or other non-HTML content.

Taking the above into account, we can use the following content for our CGI application:

#!/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>";

The first line tells Apache that this program can be executed by feeding the file to the interpreter found at the location /bin/bash. The second line prints the content-type declaration we talked about, followed by two carriage-return newline pairs. This puts a blank line after the header to indicate the end of the HTTP headers, and the beginning of the body. The other lines send an email to the root user and print the string “Email has been sent.”.

One other thing, we need to take care of SELinux context for the app folder.

Let us take a look at the defaults:

# 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

We are going to apply the same context as for the cgi-bin folder:

# semanage fcontext -a -t httpd_sys_script_exec_t "/mnt/block1/vhost2/app(/.*)?"
# restorecon -Rv /mnt/block1/vhost2/app

Note that there is a very good example in the “Examples” section at the end of man semanage-fcontext.

We also need to allow Apache to send emails permanently, otherwise SELinux will block them:

# setsebool -P httpd_can_sendmail=1

Open the file /etc/httpd/conf.d/vhosts.conf and add the following under the vhost2 configuration:

<Directory "/mnt/block1/vhost2/app">
   AllowOverride None
   Options ExecCGI
   AddHandler cgi-script .sh
   Require all granted
</Directory>

We basically want to allow CGI program execution for any file ending .sh in the app directory.

Remember the http-manual package that we installed previously? Handy CGI configuration examples can be found here:

# elinks /usr/share/httpd/manual/howto/cgi.html

Verify configuration and restart the service:

# httpd -t
# systemctl restart httpd

Test:

# elinks http://vhost2.rhce.local:8888/app/email.sh

There should be an email received from Apache:

# mailx -H
>   1 Apache    Mon May 30 19:27  19/611   "WebApp"

Configure TLS Security

TLS Virtual Host

Let us take care of the firewall first, if not already configured:

# firewall-cmd --permanent --add-service=https 
# firewall-cmd --reload

We can generate an SSL certificate with openssl, or with genkey. For the latter, install the following:

# yum install crypto-utils

If we were to use genkey, it would look something like this:

# genkey --days 365 vhost1.rhce.local

However, a faster way for us to create a certificate is with openssl. For those struggling to remember the syntax, take a look at the file /etc/pki/tls/certs/make-dummy-cert. It contains the following line:

openssl req -newkey rsa:2048 -keyout $PEM1 -nodes -x509 -days 365 -out $PEM2

That’s all we need to generate a new self-signed SSL certificate.

# 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

Ensure the Common Name matches with the ServerName for the virtual host the certificate is generated for.

Enable the default SSL host:

# mv /etc/httpd/conf.d/ssl.conf.disabled /etc/httpd/conf.d/ssl.conf

Open the file and add the following, right under the <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

Check configuration and restart the service:

# httpd -t
# systemctl restart httpd

TLS virtual host should be present now:

# 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)

Note that elinks may not work with SSL, test with curl instead:

# curl --insecure https://vhost1.rhce.local
vhost1

Check if SSLv3 is disabled (it’s considered insecure nowadays):

# curl --insecure --sslv3 https://vhost1.rhce.local
curl: (35) Cannot communicate securely with peer: no common encryption algorithm(s).

Redirect to TLS

We want all HTTP traffic coming to the vhost1 to be redirected to HTTPS automatically.

We may configure the above by using mod_rewrite:

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

However, if we check the manual, there is a simple redirect that we can use:

# elinks /usr/share/httpd/manual/rewrite/avoid.html

Let us add the below line to the vhost1 configuration:

Redirect / https://vhost1.rhce.local/

Test configuration and restart the service:

# httpd -t
# systemctl restart httpd

Test with curl, automatic rewrite should be working:

# 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 May 2016 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: May 30 18:22:37 2016 GMT * expire date: May 30 18:22:37 2017 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 May 2016 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 May 2016 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

74 thoughts on “Advanced Apache Configuration with SELinux on RHEL 7

  1. I have been following up closely and practising on the examples on your blog , there are exceptionally explained

    I have noted this sub topic “Configure Group-managed Content” on this page ,i think this goes against the objective below
    HTTP/HTTPS
    Configure a virtsdual host
    Configure access restrictions on directories
    Deploy a basic CGI application
    Configure group-managed content
    Configure TLS security
    Group managed content should be what you explained on this sub topic below
    “Group Based Security ” ,unless my understanding of the objective is skewed.

    • “Configure Group-managed Content” sub topic on this blog post aims to explain the RHCE objectives that are listed under the section “Configure group-managed content” on the Red Hat website.

  2. Is the context httpd_sys_content_rw_t correct?

    # semanage fcontext -a -t httpd_sys_content_rw_t "/nginx(/.*)?"
    ValueError: Type httpd_sys_content_rw_t is invalid, must be a file or device type

    Worked with httpd_sys_rw_content_t though which I guess is the right one.

    • You’re right, it should be httpd_sys_rw_content_t rather than httpd_sys_content_rw_t. Updated the article.

      It’s funny that Sander’s book also mentions httpd_sys_content_rw_t :) That’s where I got it wrong I believe.

  3. Hi Tomas,
    I follow httpd-manual to set up the host-based restriction on server1 to only allow access the “private” directory from server2.

    AllowOverride None
    Options None
    Require host server2.example.com

    I receive “403-permission denied” error (as expected) on server1; however, I but also receive the same “403-permission denied” error on server2!
    I have tried using server2’s IP address instead of hostname, but still have the same error.
    Do I miss any other directives for host-based access?

    • Do you have an index file? You may be getting 403 permission denied because you have no index file and directory listing is disabled.

  4. Hi Tomas,
    is there any difference between:

    # setfacl -R -m g:devops:rwX /mnt/block1/vhost2
    # setfacl -R -m d:g:devops:rwx /mnt/block1/vhost2

    and

    # chmod 2770 /mnt/block1/vhost2

    ?

    • There is a difference. One sets permissions based on owner group all, while another allows for much greater control over who has access to a specific file or folder.

  5. Hi,
    thanks for the great article. I think I found a typo in the SSL host configuration, line:
    SSLCertificateFile /etc/pki/tls/certs/vhost1.rhce.local.key

    I think you meant:
    SSLCertificateFile /etc/pki/tls/certs/vhost1.rhce.local.crt
    Otherwise the https virtual host won’t work because Apache won’t find the certificate generated before

  6. Hi Tomas,

    First of all thanks for all the articles on your site, its a great resource.

    I think I got everything to work on vhosts and allowing/disallowing access to certain sites or directories. There one thing that bothers me though and I’d appreciate it if you can comment.

    I have this part:

    documentroot /var/www/html

    order deny,allow
    deny from all
    allow from 127.0.0.1 ::1 192.168.4.210

    servername server1.example.com
    errorlog “logs/server1-error.log”
    customlog “logs/server1-access.log” combined

    it works fine, I see the index.html contents when allowed and I get an error message when it is disallowed.

    Then I have this part:

    documentroot /srv/www.test.com

    allowoverride none
    options none

    require all granted
    require not host labipa.example.com
    require not ip 10.0.0.0/24

    servername test.com
    errorlog “logs/www.test.com-error.log”
    customlog “logs/www.test.com-access.log” combined

    When allowed, I get the index.html with the ‘welcome to test.com’ page. When not allowed, it actually redirects me to the default site in /var/www/html instead of giving me an error.

    When I, from a host disallowed, go to /index.html, it does actually give me an error message.

    The difference is that when it redirects me, I get a regular HTTP/200 but when it is disallowed, I get the HTTP/403 permission denied.

    Depending on how the exam is tested, I am worried that the redirect gives the HTTP/200 and I will fail for this part.

    In any case, I’d love to know how to force the error code when going to test.com as is the case with test.com/index.html. I don’t want the fallback to the default site.

    Do you have any ideas?

    Thanks a lot!

    • Ow the directives have disappeared. Hopefully this works using code tags – sorry!
      [code]

      documentroot /var/www/html

      order deny,allow
      deny from all
      allow from 127.0.0.1 ::1 192.168.4.210

      servername server1.example.com
      errorlog “logs/server1-error.log”
      customlog “logs/server1-access.log” combined

      [/code]

      [code]

      documentroot /srv/www.example.com

      allowoverride none
      options none

      require all granted
      require not host labipa.example.com
      require not ip 10.0.0.0/24
      #order deny,allow
      #deny from all
      #allow from 127.0.0.1 ::1 192.168.4.0/24

      servername http://www.example.com
      errorlog “logs/www.example.com-error.log”
      customlog “logs/www.example.com-access.log” combined

      [/code]

    • I don’t think it worked, these aren’t valid HTML tags.

      Try posting your Apache config but without any angle brackets. I should be able to figure it out.

      As you can see WordPress isn’t great when it comes to syntax formatting.

  7. Hi Tomas,

    Thanks for your quick reply. I am using:
    httpd.x86_64 2.4.6-45.el7.centos

    As for the config, it is here:

    <Virtualhost *:80>
        documentroot /var/www/html
        <Directory "/var/www/html/private">
            order deny,allow
            deny from all
        allow from 127.0.0.1 ::1 192.168.4.210
        </Directory>
        servername server1.example.com
        errorlog "logs/server1-error.log"
        customlog "logs/server1-access.log" combined
    </virtualhost>
    
    <Virtualhost *:80>
        documentroot /srv/www.test.com
        <Directory "/srv/www.test.com">
            allowoverride none
            options none
            <RequireAll>
                require all granted
                require not host labipa.example.com
                require not ip 10.0.0.0/24
            </RequireAll>
        </Directory>
        servername www.test.com
        errorlog "logs/www.test.com-error.log"
        customlog "logs/www.test.com-access.log" combined
    </virtualhost>

    Which parts exactly are 2.2 and which are 2.4? Should I avoid 2.2 syntax?

    Thanks again!

    • Thanks Tomas.

      So basically the vhost with 2.2 config works as expected, and the test.com vhost with 2.4 syntax isn’t :).

      Any ideas why I see the behavior I described earlier? I just changed it to 2.2 syntax and it behaves exactly the same.

      Browsing to test.com from 10.0.0./24 redirects to the default site and forcing to test.com/index.html shows the no permissions warning.

      Browsing from an allowed host to test.com shows the page (index.html).

    • Do you have DirectoryIndex defined for the site server1.example.com? Check httpd.conf. If you do, make sure you have it for the other site as well.

      Also, make sure that SELinux does not block access to the index.html page for the test website:

      # grep denied /var/log/audit/audit.log
    • http.conf contains the following, obviously with greater/smaller than characters. This is a global setting, not specific for any sites:

      [IfModule dir_module]
      DirectoryIndex index.html
      [</IfModule]

      There are no denies from selinux, the context is set and when accessing the site from a host that is allowed, the page shows.

      Maybe I am looking at default behavior, I don't know.

      maybe I should try setting the restrictions on the default site it redirects to, see what that does.

    • I was unable to replicate the problem even though I copied and pasted your Apache configuration.

      Show me the output of the following please.

      From a server inside 10.0.0.0/24:

      $ curl -IL http://www.test.com

      Plus Apache access and error log records for the request above.

      From a server inside 10.0.0.0/24:

      $ curl -IL http://www.test.com/index.html

      Plus Apache access and error log records for the request above.

      Also post the output of the following:

      # httpd -t -D DUMP_VHOSTS
    • Since you can’t replicate I decided to reset both my virtual machines and start over. I do this daily anyway to practise for the exam.

      If I run into the issue again, I will post the details you ask for. I see you are using curl, I am using elinks. When I look in my logs after I try to access from within 10.0.0.0/24, I first see the HTTP/403 permission denied followed by several HTTP/200 for the default. So maybe elinks is doing something automatically that curl isn’t doing. I’d be chasing a red herring, so to speak :).

      In any case, thanks for taking the time to respond and help out! I’ll let you know how it works out. Exam is 6 nov and my RHCSA expires 7 nov … so no slacking allowed! ;-).

  8. Hello Tomas,

    Is this configuration possible.
    Main Server: we setup main server with /etc/http/conf/httpd.conf (without virtual host )
    Virtual Server :Then setup a new virtual host using /etc/httpd/conf.d/xx.conf

    Both main server and v server should work normally
    or
    we need to setup both the servers under virtual hosting ?? to make them work ?

    • You can use any file to define your virtualhost configuration, it can go into httpd.conf, or can be split into separate files.

  9. I think i did not put my question well.
    my question is will both servers work fine if one is defined in virtual.conf and other in httpd.conf ( with out virtual)
    or do we need all the server as virtual if we want multiple servers ??

    • OK, I sense some confusion here. You use VirtualHost when you want to serve more than one domain (website) on a single server. Now, when you add a name-based virtual host to an existing server, and the virtual host arguments match pre-existing IP and port combinations, requests will be handled by an explicit virtual host. If you want to keep the default Apache website (which is in /var/www/html), I suggest you create a default virtual host for it. I hope this clarifies things.

  10. Redirection does not work for me ..

    Virtual host config:
    —–
    DocumentRoot “/var/www/html/vhost1”
    ServerName vhost1.example.local
    Redirect /var/www/html/vhost1 https:// vhost1.example.local/
    —-
    #curl -k https:// vhost1.example.local
    vhost1

    #curl http:// vhost1.example.local

    display the default apache page .

    Pls support

    • You’re redirecting /var/www/html/vhost1 to https:// vhost1.example.local/, but when you run a test, you use / as the path and not /var/www/html/vhost1. You may want to look up mod_alias for more info.

  11. Trying this also does not make any difference

    Redirect / https:// vhost1.example.local/

    I have read documentaion , but does not seem to work.
    same output.
    can you please give me a clue where i am making mistake.

    • Can you post the output of the following:

      $ curl -ILk http://vhost1.example.local

      Also post full Apache config for the vhost1 virtual host. Note that if you don’t use valid HTML then your Apache directives will be lost (thanks to WordPress), therefore post the config on pastebin instead and provide the URL.

  12. Anyone who get require to work, i have tried ip and host but nothing seems to work, running Server version: Apache/2.4.6 (CentOS).

    AuthType Basic
    AuthName “Restricted Files”
    # (Following line optional)
    AuthBasicProvider file
    AuthUserFile /etc/httpd/conf/passwords

    Require all granted
    Require not host maui.example.com
    Require user gary

  13. trying to achieve to not give access to maui, and get access to gary, gary works just fine but maui have all access.

  14. Hi Tomas,

    So doing the
    #yum groupinstall web-server

    I get and error with

    There is no installed groups file.
    Maybe run: yum groups mark convert (see man yum)
    Loading mirror speeds from cached hostfile
    Warning: group web-server does not exist.
    Maybe run: yum groups mark install (see man yum)
    Error: No packages in any requested group available to install or update

    So understandably I don’t have the “groups file”!
    How do i create that in the yum repo on my IPA server? Have any ideas? I haven’t found much when googling :(

    Thanks

    • Now I’ve googled a bit more and have fixed it for those who are not getting groups with their local repos.
      My DVD is mounted on /mnt and at the mo I’m using Apache to share my repos under /var/www/http/localrepo (change to ftp folder if using vsftp)
      With that you can fix it with 2 lines:

      [root@ipa localrepo]# cp /mnt/repodata/*comps*.xml /var/www/http/localrepo/repodata/comps.xml
      [root@ipa localrepo]# createrepo -g repodata/comps.xml .

      Good to know :)

  15. Hello, Tomas. This blog is very useful, thank you!

    I have a problem (rhel 7.4, httpd 2.4.6, elinks 0.12-0.36).
    After I configured user-based or group-based security using this manual line-by-line (httpd’s syntax was checked and the service was restarted), when I try to check this configuration by the elinks, after the ht user logins I get the message: 301 Moved Permanently “The document has moved here”.
    If I specify the absolute path elinks http:// vhost2.rhce.local:8888/private/index.html, I get 401 Unauthorized.
    The log contents the messages only: 192.168.10.3 – alice … “GET /private/index.html HTP/1.1”
    No error/warning messages found.

    I tried to reconfigure a secure directory to vhost1.rhel.local, the htpasswd configuration, other user’s credentials but the same error was showed. And I also did it on the fresh installed CentOS 7.4 server and I get the same issues.
    If I replace Auth* lines on ‘Require all granted’, the error disapiared and it works well but without privacy.

    My RHCE Exam will be 07 March and I worry now =)
    Do you have any ideas?

    Thank you!

    • I’ve checked all httpd’s logs again and don’t find any messages related with redirection except as the message above. I didn’t edite the default httpd.conf file and the vhost.conf file does not content any redirection settings. I notice if press Enter in the elinks when the 302 message appears, the line “private-vh1” from the private/index.html is shown there. I’ve checked it out with the Firefox now and it works well, without 302.
      On the exam the task maybe defined like “The page should say Hello”. I hope that the rhel checking script will not use the elinks and not consider this point as my error ;)
      Thank you for answering!

  16. Hello,
    The brief remark. In the block “User Based Security”, better to use # elinks http:// vhost2.rhce.local:8888/private/, with a forward slash at the end of the line (now it is not there). In other case, may be errors.

  17. Hey dude, excellent content, I am a bit disappointed how Sander went over his RHCE course, but your materials definitely helped, I will take my RHCE shortly, will let you know how it went :) !

  18. Having an issue with a CGI Virtual Host, it only works if I have Selinux in permissive mode. Otherwise I get 500 Internal Server Error and this in my log:

    [Fri Jan 11 13:26:25.419842 2019] [cgi:error] [pid 3472] [client 172.28.14.250:56184] AH01215: /bin/bash: /etc/httpd/conf.d/vhosts/rhce3/rhce3.sh: Permission denied
    [Fri Jan 11 13:26:25.419865 2019] [cgi:error] [pid 3472] [client 172.28.14.250:56184] End of script output before headers: rhce3.sh

    [root@prod conf.d]# cat rhce3.example.com.conf

    ServerName rhce3.example.com
    ServerAlias ww.rhce3.example.com
    DocumentRoot /etc/httpd/conf.d/vhosts/rhce3
    DirectoryIndex rhce3.sh

    Options ExecCGI
    SetHandler cgi-script

    [root@prod ~]# ll -Z /etc/httpd/conf.d/vhosts/rhce3/rhce3.sh
    -rwxr-xr-x. root root unconfined_u:object_r:httpd_sys_script_exec_t:s0 /etc/httpd/conf.d/vhosts/rhce3/rhce3.sh

    [root@prod conf.d]# getsebool -a | grep httpd_enable_cgi
    httpd_enable_cgi –> on

    Any ideas?

    • The first thing to do when you hit an SElinux issue is to look at the audit log /var/log/audit/audit.log. See what gets blocked, and try to resolve accordingly.

    • My script contained lsblk, which SELinux doesn’t like.
      I ran sealert -a /var/log/audit/audit.log:
      SELinux is preventing /usr/bin/lsblk from read access on the file
      SELinux is preventing /usr/bin/lsblk from getattr access on the file
      SELinux is preventing /usr/libexec/colord from getattr access on the file

      Unrelated question… I’ve been using elinks in combination with Firefox to test out httpd configurations. The latter is much easier to work with. Can I have access to a graphical browser on the exam?

    • I’m glad that you managed to resolve it.

      I don’t know about a graphical browser, but have you tried elinks -dump? It’s great for viewing content.

  19. Hi Tomas,
    If I want to configure virtual host and still be able to see web server configured in httpd.conf, do I need to reconfigured it to be also as virtual host?
    You have this quiestion in sample tes, and I assume, that only solution is configure all web servers ase virtual hosts (if we are talking about named based virtual host, which are needed for RHCE — at least Sander Van Vugt mention that ip based is not objective)

    Here guy has similar problem:
    https://stackoverflow.com/questions/15103945/apache-vhost-overriding-config

  20. Hi Tomas,

    Im having a small problem. Im Trying to redirect the http traffic to https but it is not redirecting. I have two vhosts vhost1 and vhost2

    vhost1 displays “This is Site1”
    vhost2 displays “This is Site2”

    vhost2 is setup for https, content of vhost2.conf

    ServerName vhost2.rhce.local
    Redirect / https:// vhost2.rhce.local/

    SSLEngine on
    SSLProtocol all -SSLv2 -SSLv3
    SSLCertificateFile /etc/pki/tls/certs/vhost2.rhce.local.crt
    SSLCertificateKeyFile /etc/pki/tls/private/vhost2.rhce.local.key
    ServerAdmin admin@localhost
    DocumentRoot “/var/www/html/vhost2”

    options none
    allowoverride none
    require all granted

    ServerName vhost2.rhce.local
    loglevel info
    ErrorLog “logs/vhost2_error_log”
    CustomLog “logs/vhost2_access_log” common

    • [Thu Apr 04 10:52:56.231833 2019] [ssl:info] [pid 1410] AH01914: Configuring server vhost2.rhce.local:443 for SSL protocol
      [Thu Apr 04 10:52:56.232330 2019] [ssl:debug] [pid 1410] ssl_engine_init.c(331): AH01893: Configuring TLS extension handling
      [Thu Apr 04 10:52:56.232343 2019] [ssl:debug] [pid 1410] ssl_engine_init.c(842): AH02232: Configuring RSA server certificate
      [Thu Apr 04 10:52:56.232703 2019] [ssl:warn] [pid 1410] AH01906: RSA server certificate is a CA certificate (BasicConstraints: CA == TRUE !?)
      [Thu Apr 04 10:52:56.232811 2019] [ssl:debug] [pid 1410] ssl_util_ssl.c(407): AH02412: [vhost2.rhce.local:443] Cert matches for name ‘vhost2.rhce.local’ [subject: CN=vhost2.rhce.local,O=Default Company Ltd,L=Default City,C=SA / issuer: CN=vhost2.rhce.local,O=Default Company Ltd,L=Default City,C=SA / serial: 978F3032E80BEB91 / notbefore: Apr 2 08:44:23 2019 GMT / notafter: Apr 1 08:44:23 2020 GMT]
      [Thu Apr 04 10:52:56.232825 2019] [ssl:debug] [pid 1410] ssl_engine_init.c(897): AH02236: Configuring RSA server private key
      [Thu Apr 04 10:53:16.555295 2019] [ssl:info] [pid 1410] AH02200: Loading certificate & private key of SSL-aware server ‘vhost2.rhce.local:443’
      [Thu Apr 04 10:53:16.555902 2019] [ssl:debug] [pid 1410] ssl_engine_pphrase.c(506): AH02249: unencrypted RSA private key – pass phrase not required
      [Thu Apr 04 10:53:16.556925 2019] [ssl:info] [pid 1410] AH01914: Configuring server vhost2.rhce.local:443 for SSL protocol
      [Thu Apr 04 10:53:16.559696 2019] [ssl:debug] [pid 1410] ssl_engine_init.c(331): AH01893: Configuring TLS extension handling
      [Thu Apr 04 10:53:16.559724 2019] [ssl:debug] [pid 1410] ssl_engine_init.c(842): AH02232: Configuring RSA server certificate
      [Thu Apr 04 10:53:16.559908 2019] [ssl:warn] [pid 1410] AH01906: RSA server certificate is a CA certificate (BasicConstraints: CA == TRUE !?)
      [Thu Apr 04 10:53:16.560378 2019] [ssl:debug] [pid 1410] ssl_util_ssl.c(407): AH02412: [vhost2.rhce.local:443] Cert matches for name ‘vhost2.rhce.local’ [subject: CN=vhost2.rhce.local,O=Default Company Ltd,L=Default City,C=SA / issuer: CN=vhost2.rhce.local,O=Default Company Ltd,L=Default City,C=SA / serial: 978F3032E80BEB91 / notbefore: Apr 2 08:44:23 2019 GMT / notafter: Apr 1 08:44:23 2020 GMT]
      [Thu Apr 04 10:53:16.560400 2019] [ssl:debug] [pid 1410] ssl_engine_init.c(897): AH02236: Configuring RSA server private key

      These are the messages in error log, i dont see any problem

    • These are only SSL logs that you’ve posted, how about others?

      When you say it’s not redirecting, what happens exactly?

    • When i manually type “https://vhost2.rhce.local” it displays “This is Site2” but when i type “http://vhost2.rhce.local” it show “This is site1”

    • Can you post the output from curl:

      $ curl -IL http://vhost2.rhce.local

      Also post the output from Apache:

      # httpd -t -D DUMP_VHOSTS
    • Tomas actually i restored the snapshot of VM so old settings are gone. But this time same issue happening on serv1.rhce.local vhost(I’m using your provided sample exam)

      $ curl -IL serv1.rhce.local

      HTTP/1.1 403 Forbidden
      Date: Tue, 09 Apr 2019 09:27:36 GMT
      Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.1e-fips mod_fcgid/2.3.9
      Last-Modified: Tue, 17 Jun 2014 16:00:47 GMT
      ETag: “1310-4fc0a3f32a9c0”
      Accept-Ranges: bytes
      Content-Length: 4880
      Content-Type: text/html; charset=UTF-8

      # httpd -t -D DUMP_VHOSTS

      VirtualHost configuration:
      *:443 is a NameVirtualHost
      default server serv1.rhce.local (/etc/httpd/conf.d/serv1-vhost.conf:1)
      port 443 namevhost serv1.rhce.local (/etc/httpd/conf.d/serv1-vhost.conf:1)
      alias serv1
      port 443 namevhost serv1.rhce.local (/etc/httpd/conf.d/serv1-vhost.conf:1)
      alias serv1
      port 443 namevhost serv1.rhce.local (/etc/httpd/conf.d/ssl.conf:56)
      port 443 namevhost serv1.rhce.local (/etc/httpd/conf.d/ssl.conf:56)

      same issue . I have to manually type the https url everytime

    • Try mod_rewrite and see if that resolves the problem:

      RewriteEngine On
      RewriteCond %{HTTPS} off
      RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
    • This time it started working without just “Redirect permanent / https:// serv1.rhce.local/”

    • Hi Tomas,

      One question can we create multiple Virtual hosts file for each web server or just one Vhost file . e.g i was trying to put two host one running on port 80 other for port 443 . Below is the content of that vhost.conf file

      ServerName secsite.rhce.local
      serveralias secsite
      SSLEngine on
      SSLProtocol all -SSLv2
      SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5
      SSLCertificateFile /etc/pki/tls/certs/secsite.rhce.local.crt
      SSLCertificateKeyFile /etc/pki/tls/private/secsite.rhce.local.key
      ServerAdmin [email protected]
      DocumentRoot “/var/www/html/secsite”
      directoryindex index.html

      #require all granted
      require host rhce.local

      loglevel info
      ErrorLog “/var/log/httpd/secsite-error_log”
      CustomLog “/var/log/httpd/secite-access_log” common

      ServerAdmin [email protected]
      DocumentRoot “/var/www/html/”

      #require all granted
      require host rhce.local

      ServerName site1.rhce.local
      serveralias site1
      loglevel info
      ErrorLog “/var/log/httpd/site1-error_log”
      CustomLog “/var/log/httpd/site1-access_log” common

      When i access HTTP using “elinks http:// 10.8.8.50” it shows me “This is Site One” which is correct but when i try to access “curl –insecure “https:// 10.8.8.50” it still shows “This is Site One” although index file has “This is secured site”

      httpd -t -D DUMP_VHOSTS

      VirtualHost configuration:
      *:443 is a NameVirtualHost
      default server secsite.rhce.local (/etc/httpd/conf.d/ssl.conf:56)
      port 443 namevhost secsite.rhce.local (/etc/httpd/conf.d/ssl.conf:56)
      port 443 namevhost secsite.rhce.local (/etc/httpd/conf.d/ssl.conf:56)
      port 443 namevhost secsite.rhce.local (/etc/httpd/conf.d/website.conf:2)
      alias secsite
      port 443 namevhost secsite.rhce.local (/etc/httpd/conf.d/website.conf:2)
      alias secsite
      *:80 is a NameVirtualHost
      default server site1.rhce.local (/etc/httpd/conf.d/website.conf:23)
      port 80 namevhost site1.rhce.local (/etc/httpd/conf.d/website.conf:23)
      alias site1
      port 80 namevhost site1.rhce.local (/etc/httpd/conf.d/website.conf:23)
      alias site1

    • You can have multiple files, that’s how you do webhosting in practice.

      When testing websites with SSL/TLS, use SNI and not the IP address.

    • I read apache documentation and came to know Apache reads the files alphabetically , I used one website.conf file and placed all vhosts in it. and configuration was not working as i want. So i renamed the file ot 00-website.conf and reload the service and everything started working as it should.

    • That’s correct. You can get a list of all virtualhosts in their order by running the following command:

      # httpd -S
  21. Hi Tomas,

    I am trying to restrict portion of my website what i am trying to is to allow only localhost to access /second through Requireall container but some how localhost is also getting forbidden message

    Directory “/var/www/html/second”>
    AllowOverride None

    Require all denied
    Require local

    but when i do following it works i am confused when to use container and when not

    Directory “/var/www/html/second”>
    AllowOverride None
    Require all denied
    Require local

    • sorry all the config was not properly copied
      Working:

      AllowOverride None
      Require all denied
      Require local

      Not working:

      AllowOverride None

      Require all denied
      Require local

  22. Hi,

    for selinux man pages – there is a shortcut (compared to using sepolicy manpage) ,which I learnt recently:
    yum install selinux-policy-doc
    In RHEL 7 that package is availabe from the rhel-7-server-optional-rpms repository.

Leave a Reply

Your email address will not be published. Required fields are marked *