Adding HTTP Basic Auth for Traefik and Nginx-Proxy Routes
October 25, 2019  |  

Reverse HTTP proxy is a single point of entrance to all the HTTP traffic which makes it a good place to perform some additional tasks and checks and one of them is limiting access to certain services. Some services might not have their own access control systems and some services might benefit from an additional level of protection.

Preface

The core function of a reverse proxy is to abstract away a bunch of services placed behind it. It analyzes incoming HTTP requests and forwards them to the right services. HTTP has many authentication schemes but most HTTP proxies support only RFC 7617 “Basic” auth because it’s easy to implement without deviating too much from their main task of redirecting requests.

Example of a Basic HTTP Auth Flow

Here is an example of how such a scheme may protect a certain endpoint:

Request URL: https://example.com
Request method: GET

Status code: 401 Unauthorized

Response headers:

HTTP/2.0 401 Unauthorized
content-type: text/plain
www-authenticate: Basic realm="traefik"

The most important fields here are status code and www-authenticate header. They tell us that we might access this page only if we can provide a valid username and password combination so your browser will likely ask you to provide this information if it gets such a response and once you enter it, it’ll try to access this endpoint again using the credentials you provided.

More details: HTTP Authentication

More details: WWW-Authenticate

This scheme can be used to prevent unauthorized access to any web page behind a reverse proxy, and it can also be used in conjunction with other auth systems. Let’s say that we want to use Adminer, a popular front end for managing relational databases. It has its own auth screen which asks you for your DB credentials and uses them to access your database. Your database might have a good password and Adminer might be secure enough so no one can expose the fact that it’s publicly accessible but why let strangers see it and ‘probe’ our private networks? Adding another auth layer might be a good idea in that case.

Both nginx-proxy and Traefik allow us to implement basic HTTP auth for any domain or subdomain. It can also be used to restrict access to specific URI’s. Both of those reverse proxy solutions use Apache htpasswd format when is comes to specifying the list of allowed users and their password hashes.

Adding HTTP Basic Auth for Traefik 2

First, it’s important not to use htpasswd generators available on various websites. They can’t be trusted, and it’s not hard to generate auth credentials on your own. The only tool that is necessary is htpasswd, which is usually distributed as a part of apache2-utils package. You might want to check if it’s already available on your system and, if not, just run the following command (assuming you have a Debian based system such as Ubuntu, and if you have other Linux system, you probably won’t need detailed instructions anyway):

sudo apt install apache2-utils

Now we can use the following command to generate a new username and password combination:

htpasswd -nBC 10 <username>

It will ask you to enter a password twice and then display the result in a following format:

htpasswd -nBC 10 admin

New password: admin
Re-type new password: admin

admin:$2y$10$zi5n43jq9S63gBqSJwHTH.nCai2vB0SW/ABPGg2jSGmJBVRo0A.ni

The resulting format is quite straightforward: username:password_hash

Let’s do a quick review of those -nBC arguments, because it’s not a good idea to execute a command if you don’t know what exactly it supposed to do:

-n manpage entry:

Display the results on standard output rather than updating a file. This is useful for generating password records acceptable to Apache for inclusion in non-text data stores. This option changes the syntax of the command line, since the passwdfile argument (usually the first one) is omitted. It cannot be combined with the -c option.

-B manpage entry:

Use bcrypt encryption for passwords. This is currently considered to be very secure.

-C manpage entry:

This flag is only allowed in combination with -B (bcrypt encryption). It sets the computing time used for the bcrypt algorithm (higher is more secure but slower, default: 5, valid: 4 to 31).

Please note that 10 is an arbitrary number. I think that the default cost factor of 5 is too weak and was never considered secure. Cost factor of 5 results in 2^5=32 iterations and cost factor of 10 results in 1024 iterations which makes it much harder to steal your password. You should consider increasing the cost factor if you have enough computing power to ’exchange’ for better security. It’s also important to have a strong password, because cost factor alone can’t protect you if your password is trivial to guess.

Now, since we know how to create a secure combination of login and password, let’s use it to restrict unauthorized access to Traefik endpoints using Docker Compose:

version: "3.7"
services:

  traefik:
    image: traefik:v2.0
    container_name: traefik
    labels:
      - traefik.http.middlewares.auth.basicauth.users=${TRAEFIK_USER}:${TRAEFIK_PASSWORD_HASH}

This example uses environment variables that should be located in the .env file in the same directory with docker-compose.yml:

TRAEFIK_USER=admin
TRAEFIK_PASSWORD_HASH=$2y$10$zi5n43jq9S63gBqSJwHTH.nCai2vB0SW/ABPGg2jSGmJBVRo0A.ni

Now you can add newly created “auth” middleware to any route and that route will be “protected” with basic auth.

Adding HTTP Basic Auth for nginx-proxy

In case of nginx-proxy, we need to store our usernames and passwords in a .htacess file named using a special convention.

Let’s say we want to restrict access to a DB frontend located at db.example.com and we’d like to utilize built-in capabilities of nginx-proxy in order to do that. First, let’s generate a file named db.example.com using htpasswd utility:

htpasswd -cBC 10 db.example.com admin

New password: admin
Re-type new password: admin

This should create a file called db.example.com and you can add more users to it if you wish so. I’ve already mentioned why we need BC 10 in the arguments and -c just means “write the results to a file, and I will provide the file name”.

Now you need to bind this file to a nginx-proxy container. This file should be located in the /etc/nginx/htpasswd folder in a container, here is an example:

version: "3.7"
services:

  nginx-proxy:
    image: jwilder/nginx-proxy
    container_name: nginx-proxy
    ports:
      - 80:80
      - 443:443
    volumes:
      - type: bind
        source: ./configs/htpasswd # we assume db.example.com file is in this folder on a host computer
        target: /etc/nginx/htpasswd
        read_only: true
      - type: bind
        source: /var/run/docker.sock
        target: /tmp/docker.sock
        read_only: true
    labels:
- "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy=true"

That’s it, now nginx-proxy should know that db.example.com is a restricted access domain, and it should take the list of allowed users from a file located at /etc/nginx/htpasswd/db.example.com

Conclusion

Adding basic HTTP auth is an easy and secure way to restrict access to certain endpoints located behind a reverse proxy. In this post, we covered how to create secure login and password hash combinations using htpasswd and bcrypt and how to add them to popular reverse-proxy implementations such as Traefik and nginx-proxy.