Apache HTTP 2.4: How to Build a Docker Image for SSL/TLS Mutual Authentication

Image title


Many times I have to write clients that need to interact with services (HTTP, SOAP, REST, etc.) that are protected by an SSL/TLS Mutual Authentication mechanism. For this reason, I decided to create a project whose purpose is to provide a ready-to-use template and which realizes a mutual authentication or bilateral SSL/TLS authentication system based on Apache HTTP. In this way, I have the possibility to test the client, in particular, the process of mutual authentication.

In this article, I will describe the project and how to use it. The source code of the project is available on the my GitHub repository Apache HTTP 2.4 - Docker image for SSL/TLS Mutual Authentication.

The latest version of the project (1.2.2) was installed on Google Cloud and available online at https://tls-auth.dontesta.it. In this video, you can see the project in action.


1. Overview

It is a Docker project that starts from the basic Ubuntu image (version 18.04), specialized to meet the minimum requirements for an SSL/TLS Mutual Authentication system. The basic software installed is:

The installation of PHP and the Apache module is completely optional. The two modules were installed exclusively to build the user's landing page after the authentication phase. This page shows a series of basic information extracted from the digital certificate used for authentication.

Mutual authentication based on the SSL/TLS protocol refers to two parties that mutually authenticate each other by verifying the digital certificate provided so that both participants are sure of the identity of others. Briefly, the process of authenticating and creating an encrypted channel using certificate-based mutual authentication (or mutual authentication) involves the following steps:

  1. A client requests access to a protected resource;

  2. The server presents its certificate to the client;

  3. The client verifies the server certificate;

  4. If successful, the client sends its certificate to the server;

  5. The server verifies the client's credentials;

  6. If successful, the server grants access to the protected resource requested by the client.

Figure 1 shows what happens during the mutual authentication process (or mutual authentication).


Cosa succede durante il processo di autenticazione reciproca

Figure 1 - What happens during the mutual authentication process 


2. Structure of the Dockerfile

To create an image we need to build the Dockerfile, thanks to a series of directives allows us to create an image as needed. We try to understand which are the most significant sections of the Dockerfile. The first line of the file (as anticipated above) makes the container start from the image  ubuntu: 18.04 .

FROM ubuntu:18.04


Below is the section on environment variables that are purely specific to Apache HTTP. The values of these environment variables can be changed to suit your needs.

# Apache ENVs
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_SERVER_NAME tls-auth.dontesta.it
ENV APACHE_SERVER_ADMIN tls-auth@dontesta.it
ENV APACHE_SSL_CERTS tls-auth.dontesta.it.cer 
ENV APACHE_SSL_PRIVATE tls-auth.dontesta.it.key
ENV APACHE_SSL_PORT 10443
ENV APACHE_LOG_LEVEL info
ENV APACHE_SSL_LOG_LEVEL info
ENV APACHE_SSL_VERIFY_CLIENT optional
ENV APACHE_HTTP_PROTOCOLS http/1.1
ENV APPLICATION_URL https://${APACHE_SERVER_NAME}:${APACHE_SSL_PORT}
ENV CLIENT_VERIFY_LANDING_PAGE /error.php
ENV API_BASE_PATH /secure/api
ENV HTTPBIN_BASE_URL http://127.0.0.1:8000${API_BASE_PATH}


The first group of four variables are very clear and I don't think they need further explanation. The following variables and in particular  APACHE_SSL_CERTS  and  APACHE_SSL_PRIVATE set:

  1. The name of the file that contains the public certificate of the server in PEM (Privacy-Enhanced Mail) format;

  2. The name of the file containing the private key (in PEM format) of the public certificate.

The server certificate used in this project was issued by a private Certification Authority created ad hoc and obviously not recognized. The CN (Common Name) of this specific certificate is set to tls-auth.dontesta.it issued by Antonio Musarra's Blog Certification Authority.

By default the HTTPS port is set to 10443 by the variable  APACHE_SSL_PORT . The APPLICATION_URL  variable defines the redirect path if it is accessed via HTTP and not HTTPS.

The APACHE_LOG_LEVEL   and APACHE_SSL_LOG_LEVEL   variables allow you to modify the general log level and the specific one for the SSL module. The default value is set to INFO . For more information you can consult the documentation on LogLevel Directive.

The variable APACHE_SSL_VERIFY_CLIENT   acts on the configuration of the client side certificate verification process. The default value is set to optional. Making the verification optional, allows a more flexible management of the error in case the validation of the client certificate fails.

If the value of the Apache SSLVerifyClient  directive is optional  or  optional_no_ca , if some validation error should occur, then the specific page defined by the CLIENT_VERIFY_LANDING_PAGE   variable would be displayed.

The APACHE_HTTP_PROTOCOLS  variable specifies the list of supported protocols for the virtual server/host. The list determines the allowed protocols that a customer can negotiate for this server/host.

Protocols must be set up if you want to extend the available protocols for a server/host. By default, only the http/1.1 protocol is allowed (which includes compatibility with clients 1.0 and 0.9).

Valid protocols are http/1.1 for HTTP and HTTPS connections, h2 on HTTPS and h2c connections for HTTP connections.

For more information, consult the Apache Module mod_http2 documentation.

The following section of the Dockerfile contains all the directives necessary for the installation of the software indicated above. Since the chosen distribution is Ubuntu, the apt  command is responsible for package management, and therefore, for installation.


# Install services, packages and do cleanup
RUN apt update \
    && apt install -y apache2 \
    && apt install -y php php7.2-fpm \
    && apt install -y curl \
    && apt install -y python3-pip \
    && apt install -y git \
    && rm -rf /var/lib/apt/lists/*


The following section of the Dockerfile, copies the Apache configurations appropriately modified in order to enable mutual authentication.

# Copy Apache configuration file
COPY configs/httpd/000-default.conf /etc/apache2/sites-available/
COPY configs/httpd/default-ssl.conf /etc/apache2/sites-available/
COPY configs/httpd/ssl-params.conf /etc/apache2/conf-available/
COPY configs/httpd/dir.conf /etc/apache2/mods-enabled/
COPY configs/httpd/ports.conf /etc/apache2/


The following section of the Dockerfile copies the server's key pair and the CA's public certificate.

# Copy Server (pub and key) tls-auth.dontesta.it
# Copy CA (Certification Authority) Public Key
COPY configs/certs/blog.dontesta.it.ca.cer /etc/ssl/certs/
COPY configs/certs/tls-auth.dontesta.it.cer /etc/ssl/certs/
COPY configs/certs/tls-auth.dontesta.it.key /etc/ssl/private/


The following section of the Dockerfile copies three PHP scripts for testing purposes on the standard Apache document root.

# Copy php samples script and other
COPY configs/www/*.php /var/www/html/
COPY configs/www/assets /var/www/html/assets
COPY configs/www/secure /var/www/html/secure
COPY images/favicon.ico /var/www/html/favicon.ico


The following section of the Dockerfile copies the entrypoint script that starts Apache HTTP server.

# Copy scripts and entrypoint
COPY scripts/entrypoint /entrypoint


The following section of the Dockerfile performs the following main activities:

  1. Enable the SSL module

  2. Enable the headers module

  3. Enable the MPM Worker module

  4. Enable the HTTP2 module

  5. Enable Proxy, HTTP Proxy and FCGI Proxy (Fast CGI) modules

  6. Enables the default site SSL with the mutual authentication configuration

  7. Enable configuration options to strengthen SSL/TLS security

  8. Performs re-hash of certificates. This is necessary so that Apache can read the new certificates

RUN a2enmod ssl \
    && a2enmod headers \
    && a2enmod rewrite \
    && a2dismod mpm_prefork \
    && a2dismod mpm_event \
    && a2enmod mpm_worker \
    && a2enmod proxy_fcgi \
    && a2enmod http2 \
    && a2enmod proxy \
    && a2enmod proxy_http \
    && a2enmod remoteip \
    && a2ensite default-ssl \
    && a2enconf ssl-params \
    && a2enconf php7.2-fpm \
    && c_rehash /etc/ssl/certs/


The two last directives indicated on the Dockerfile declare the HTTPS port (APACHE_SSL_PORT) that must be published and the command to execute to put in listen (or listen) the new Apache HTTP service.

3. Project Organization

In terms of directories and files, the project is organized as shown below. The heart of it all is the configs directory.


├── Dockerfile
├── configs
│    ├── certs
│    │   ├── blog.dontesta.it.ca.cer
│    │   ├── blog.dontesta.it.ca.key
|    |   ├── tls-auth.dontesta.it.cer
|    |   ├── tls-auth.dontesta.it.key
|    |   ├── tls-auth.dontesta.it.req
|    |   ├── tls-client.dontesta.it.cer
|    |   ├── tls-client.dontesta.it.key
|    |   ├── tls-client.dontesta.it.p12
|    |   ├── tls-client.dontesta.it.req
|    |   ├── mrossi.dontesta.it.cer
|    |   ├── mrossi.dontesta.it.key
|    |   ├── mrossi.dontesta.it.p12
|    |   └── mrossi.dontesta.it.req
│    ├── httpd
│    │   ├── 000-default.conf
│    │   ├── default-ssl.conf
│    │   ├── dir.conf
│    │   ├── ports.conf
│    │   └── ssl-params.conf
│    ├── openssl
│    │   └── openssl.cnf
│    └── wwww
└── scripts
    └── entrypoint


The configs directory contains other folders and files, in particular:

  1. certs

    1. contains the server certificate (public key, private key and CSR);

    2. contains the CA certificate (public key and private key);

    3. contains the personal client certificate for browser authentication. The following are available: public key, private key, CSR, key pair in PKCS # 12 format;

    4. contains the client certificate to be used to authenticate an application or device. The following are available: public key, private key, CSR, key pair in PKCS # 12 format;

  2. openssl: contains the configuration file for the openssl tool with the default settings;

  3. httpd: contains all the Apache configurations necessary to enable mutual authentication;

  4. www: contains a simple web interface;

  5. scripts: contains the entrypoint script that starts the Apache server

4. Quickstart

The image of this Docker project is available on my Dockerhub account.

Following are the commands to pull the Docker image hosted on the Dockerhub. The first command pulls the latest version (latest tag), while the second command pulls the specific version of the image which in this case is version 1.0.0.

docker pull amusarra/apache-ssl-tls-mutual-authentication
docker pull amusarra/apache-ssl-tls-mutual-authentication:1.0.0


Once the Docker image (version 1.0.0) has been pulled, the new container can be created using the command below.

docker run -i -t -d -p 10443:10443 --name=apache-ssl-tls-mutual-authentication amusarra/apache-ssl-tls-mutual-authentication:1.0.0


Using the command  docker ps  we should be able to see the new container in the list, as indicated below.

CONTAINER ID        IMAGE                                  COMMAND                  CREATED             STATUS              PORTS                      NAMES
bb707fb00e89        amusarra/apache-ssl-tls-mutual-authentication:1.0.0   "/usr/sbin/apache2ct…"   6 seconds ago       Up 4 seconds        0.0.0.0:10443->10443/tcp   apache-ssl-tls-mutual-authentication


If you want to make changes to the Dockerfile, you should then proceed with the build of the new image and at the end run the image obtained. Below are the docker commands to be executed from your terminal.


The Docker build and run commands must be executed from the root of the project directory after cloning this repository.

docker build -t apache-ssl-tls-mutual-authentication .
docker run -i -t -d -p 10443:10443 --name=apache-ssl-tls-mutual-authentication apache-ssl-tls-mutual-authentication:latest


At this point on our system, we should have the new image with the name apache-ssl-tls-mutual-authentication and running the new container called apache-ssl-tls-mutual-authentication. For convenience reasons, I called the container with the same name as the image, but no one forbids giving a different name.

Using the Docker images command we should be able to see the new image in the list, as indicated below.

REPOSITORY                                      TAG                 IMAGE ID            CREATED             SIZE
apache-ssl-tls-mutual-authentication                           latest              1a145475d1f1        30 minutes ago      242MB


Using the command  docker ps  we should be able to see the new container in the list, as indicated below.

CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS              PORTS                      NAMES
65c874216624        apache-ssl-tls-mutual-authentication:latest   "/usr/sbin/apache2ct…"   36 minutes ago      Up 36 minutes       0.0.0.0:10443->10443/tcp   apache-ssl-tls-mutual-authentication


From this moment it is possible to reach the SSL/TLS mutual authentication service using the browser.

To avoid the SSL_ERROR_BAD_CERT_DOMAIN error from the browser by accessing the service via the URL https://127.0.0.1:10443/, the following line must be added to your hosts file.

##
# Servizio di mutua autenticazione via Apache HTTPD
##
127.0.0.1       tls-auth.dontesta.it


In the test and/or production environment the name of the service or FQDN will be registered on a DNS.

On the server-side everything is ready, but a client-side configuration is missing; that is, the installation of the personal digital certificate on your browser. Within the project I made available two example certificates that can be used as client certificates, in particular:

  1. mrossi.dontesta.it.p12

  2. tls-client.dontesta.it.p12

The first is a personal digital certificate; the second digital certificate can be used to authenticate an application. Both certificates (hence the key pair) are contained within a PKCS#12. The subjects of the respective certificates are shown below.

The password of both PKCS#12 is set to: secret. This is the password to use to import certificates.

Subject: C=IT, L=Rome, ST=Italy, O=Mario Rossi S.r.l, OU=Horse Club, CN=mario.rossi@horseclubsample.com/emailAddress=admin@horseclubsample.com

Subject: C=IT, L=Bronte, ST=Italy, O=Judio Horse Club, OU=IT Services, CN=tls-client.dontesta.it/emailAddress=info@dontesta.it


The two certificates were issued by CA Antonio Musarra's Blog Certification Authority. Following the details of the CA that released all the certificates available within the project.

Issuer: C=IT, L=Rome, ST=Italy, O=Antonio Musarra's Blog, OU=IT Security Department, CN=Antonio Musarra's Blog Certification Authority/emailAddress=info@dontesta.it


Once the client certificate (file with extension .p12) is installed on your browser (for example Firefox), you can run the mutual authentication test with a certificate.

Figure 2 shows the digital certificate installed on the Firefox browser. This is the certificate to use for authentication.

Certificato Digitale Personale installato sul browserFigure 2 - Installation of the personal digital certificate on the Firefox browser


Pointing to the address https://tls-auth.dontesta.it:10443 the following should happen:

  1. Click on the Login button or login with your digital certificate.

  2. Select the previously installed digital certificate.

  3. Display of the welcome page.

The images below show the result of the three steps previously indicated.

Welcome Page di AccessoFigure 3 - Access Welcome Page via client certificate


Selezione certificatoFigure 4 - Selection of the client certificate


Image title

Figure 5 - Welcome Page after successful login


By accessing the Apache access logs it is possible to note two useful pieces of information for tracking the operations performed by the user:

  1. The SSL protocol

  2. The value of the  SSL_CLIENT_S_DN_CN  variable

172.17.0.1 TLSv1.2 - $$anonymous$$ [11/Apr/2019:20:17:53 +0000] "GET /secure/ HTTP/1.1" 200 4745 "https://tls-auth.dontesta.it:10443/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0"


The value of SSL_CLIENT_S_DN_CN   is also set as SSLUserName , which causes the variable REMOTE_USER  to be set with the CN of the digital certificate that uniquely identifies the user.

In case of error during validation of the client certificate, the error page shown in Figure 6 is shown.

Pagina di errore in caso di certificato non validoFigure 6 - Error page in case of invalid certificate


5. Build, Run and Push Docker image via Makefile

In order to simplify the build, run and push operations of the Docker image, the Makefile was introduced. To use the Makefile, the build tools must be installed correctly on your machine.

The available targets are the following:

  1. build: Default target that builds the image;

  2. debug: Perform image build and then open a bash shell on the container;

  3. run: Performs image build and then creates the container by launching the application (Apache HTTPD 2.4);

  4. clean: Performs an image prune;

  5. remove: Remove the last image created;

  6. release: Performs the build of the image and then pushes it to dockerhub.

It is possible to execute the target release only on the master branch, moreover, the push of the image to DockerHub requires access (via username and password) using the docker login command. Following the extract of the Makefile showing the available tasks.


docker_build:
# Build Docker image
docker build \
  --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
  --build-arg VERSION=$(CODE_VERSION) \
  --build-arg VCS_URL=`git config --get remote.origin.url` \
  --build-arg VCS_REF=$(GIT_COMMIT) \
-t $(DOCKER_IMAGE):$(DOCKER_TAG) .

docker_push:
# Tag image as latest
docker tag $(DOCKER_IMAGE):$(DOCKER_TAG) $(DOCKER_IMAGE):latest

# Push to DockerHub
docker push $(DOCKER_IMAGE):$(DOCKER_TAG)
docker push $(DOCKER_IMAGE):latest

docker_debug:
# Run bash shell on Container
docker run --rm -it $(DOCKER_IMAGE):$(DOCKER_TAG) /bin/bash

docker_run:
# Run Container
docker run --rm -it -d --name=apache-ssl-tls-mutual-authentication -p ${EXPOSE_HTTPS_PORT}:10443 $(DOCKER_IMAGE):$(DOCKER_TAG)

docker_image_prune:
# Docker image prune
docker image prune --force

docker_image_remove_last_build:
# Docker image remove
docker image rm $(DOCKER_IMAGE):$(DOCKER_TAG)

output:
@echo Docker Image: $(DOCKER_IMAGE):$(DOCKER_TAG)


6. How The Certificates Were Generated

All sample certificates available within the project were generated using the OpenSSL tool. All the following commands have been and may need to be executed by the project root. The commands have the goal of:

  1. Create your own Certificate Authority;

  2. Create the private key of the server certificate and CSR (Certificate Signing Request);

  3. Sign the server certificate from the CA (previously created);

  4. Create private keys for client certificates;

  5. Create CSRs for client certificates;

  6. Sign client certificates from the CA;

  7. Export the key pair in PKCS#12 format. 

Creation of your own Certificate Authority

$ openssl req -config ./configs/openssl/openssl.cnf -newkey rsa -nodes \
-keyform PEM -keyout ./configs/certs/blog.dontesta.it.ca.key \
-x509 -days 3650 -extensions certauth \
-outform PEM -out ./configs/certs/blog.dontesta.it.ca.cer


Creation of the private key of the server and CSR certificate

$ openssl genrsa -out ./configs/certs/tls-auth.dontesta.it.key 4096
$ openssl req -config ./configs/openssl/openssl.cnf -new \
-key ./configs/certs/tls-auth.dontesta.it.key \
-out ./configs/certs/tls-auth.dontesta.it.req


Since version 1.2.2 of the project, server certificates have been updated with certificates issued by Let's Encrypt (via ZeroSSL)

Following are the OpenSSL commands used to create client certificates.

Signature of the server certificate by the CA

$ openssl x509 -req -in ./configs/certs/tls-auth.dontesta.it.req -sha512 \
-CA ./configs/certs/blog.dontesta.it.ca.cer \
-CAkey ./configs/certs/blog.dontesta.it.ca.key \
-set_serial 100 -extfile ./configs/openssl/openssl.cnf \
-extensions server -days 735 \
-outform PEM -out ./configs/certs/tls-auth.dontesta.it.cer


Creation of private keys

$ openssl genrsa -out ./configs/certs/tls-client.dontesta.it.key 4096
$ openssl genrsa -out ./configs/certs/mrossi.dontesta.it.key 4096


Creation of CSRs

$ openssl req -config ./configs/openssl/openssl.cnf \
-new -key ./configs/certs/tls-client.dontesta.it.key \
-out ./configs/certs/tls-client.dontesta.it.req

$ openssl req -config ./configs/openssl/openssl.cnf \
-new -key ./configs/certs/mrossi.dontesta.it.key \
-out ./configs/certs/mrossi.dontesta.it.req


Signature of client certificates by the CA

$ openssl x509 -req -in ./configs/certs/tls-client.dontesta.it.req -sha512 \
-CA ./configs/certs/blog.dontesta.it.ca.cer \
-CAkey ./configs/certs/blog.dontesta.it.ca.key \
-set_serial 200 -extfile ./configs/openssl/openssl.cnf \
-extensions client -days 365 \
-outform PEM -out ./configs/certs/tls-client.dontesta.it.cer

$ openssl x509 -req -in ./configs/certs/mrossi.dontesta.it.req -sha512 \
-CA ./configs/certs/blog.dontesta.it.ca.cer \
-CAkey ./configs/certs/blog.dontesta.it.ca.key \
-set_serial 400 -extfile ./configs/openssl/openssl.cnf \
-extensions client -days 365 -outform PEM \
-out ./configs/certs/mrossi.dontesta.it.cer


Export of the pair of keys in PKCS#12 format

$ openssl pkcs12 -export -inkey ./configs/certs/tls-client.dontesta.it.key \
-in ./configs/certs/tls-client.dontesta.it.cer \
-out ./configs/certs/tls-client.dontesta.it.p12

$ openssl pkcs12 -export -inkey ./configs/certs/mrossi.dontesta.it.key \
-in ./configs/certs/mrossi.dontesta.it.cer \
-out ./configs/certs/mrossi.dontesta.it.p12


8. How to Enable the HTTP2 Protocol

From version 1.1.0 of the project, it is possible to activate the HTTP/2 protocol (RFC 7540).

Activating the HTTP/2 protocol (H2 SSL/TLS) is really simple, just run the image by setting the following two variables with the values shown below.

  1.  APACHE_SSL_VERIFY_CLIENT=require

  2.  
  3.  APACHE_HTTP_PROTOCOLS=h2 http / 1.1

  4.  

The command shown executes the image run setting the two environment variables that allow the activation of the HTTP/2 protocol.

docker run -i -t -d -p 10443:10443 \
-e APACHE_SSL_VERIFY_CLIENT='require' \
-e APACHE_HTTP_PROTOCOLS='h2 http/1.1' \
--name=apache-ssl-tls-mutual-authentication \
amusarra/apache-ssl-tls-mutual-authentication:1.1.0


The figure below illustrates the use of the HTTP/2 protocol (H2-TLS) instead of the HTTP/1.1 protocol (TLS).

Abilitazione protocollo HTTP/2 su Apache 2.4Figure 7 - Enable HTTP / 2 protocol on Apache 2.4


9. Integration of the httpbin Project

From the 1.2.0 version of the project httpbin, a project by Kenneth Reitz, was introduced. httpbin is a project that implements a simple request and response service based on the HTTP protocol.

I decided to integrate this project in order to facilitate access tests through mutual authentication to REST services.

The  API_BASE_PATH  environment variable defines the base path of the REST services offered by the httpbin project which can only be accessed through mutual authentication.

Pointing your browser at https://tls-auth.dontesta.it:10443/secure/api and after performing the authentication, we should get the Swagger interface which shows the list of available services and which we can call up directly from the browser. The figure below shows the Swagger interface.

Visualizzazione Swagger UIFigure 8 - Display of Swagger UI and REST services exposed by httpbin


To obtain the descriptor of the REST services in Swagger 2.0 format, simply download the json spec.json document. The descriptor can for example be imported to Postman and then test the REST services in silent authentication.

Esempio di chiamata a servizio REST via Swagger UIFigure 9 - Example of a call to REST service via Swagger UI


Before you can run the test with Postman:

  1. The server certificate is self-signed, therefore it is necessary to set the SSL/TLS certificate check to off (see figure 10). This avoids getting the error when connecting to the service;

  2. Import the client certificate (see figure 11). You can use the sample certificate tls-client.dontesta.it.cer supplied with the project.

Disattivazione verifica SSLFigure 10 - SSL verification deactivation


Postman requires the cer and key file in order to be able to add the client certificate. The password to use is: secret

Aggiunta del certificato client per la muta autenticazioneFigure 11 - Adding the client certificate for mutual authentication


The figure below shows an example of a call to the /secure/api/get service. Note the headers X-Client-Dn and X-Client-Verify, which respectively show the DN (Distinguished Name) of the client certificate presented in the authentication phase and the outcome of the authentication process, in this case, SUCCESS.


Esempio di chiamata ad uno dei servizi di httpbinFigure 12 - Example of a call to one of httpbin's services


10. Conclusions

I believe that this project can be useful to those who need to create a SSL/TLS mutual authentication service and don't even know where to start. 

Any suggestions and bug reports are welcome; I advise possibly to open an issue.

 

 

 

 

Top