Adding HTTP Basic Auth for Traefik and Nginx-Proxy Routes

Security ·

Updated on

There are many ways to restrict access to certain places behind a reverse proxy and many services have their own auth systems so they might not need any extra protection. Unfortunately, some pages might not have their own auth systems so we may instruct our reverse proxies to restrict acess to some HTTP endpoints. HTTP has many authentication schemes but the simpliest one is RFC7617 ‘Basic’ auth.

Key (post illustration)

Photo by CMDR Shane

Example of a Basic HTTP Auth Flow

Here is an example of how such a scheme may protect a certain endpoint. Let’s try to query Traefik dashbord of my Nextcloud deployment:

Request URL: https://traefik.cloud.bubelov.com/
Request method: GET
Remote address: 51.158.167.126:443
Status code: 401 Unauthorized
Version: HTTP/2.0

Request headers:

Host: traefik.cloud.bubelov.com
User-Agent: Mozilla
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
DNT: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1

Response headers:

HTTP/2.0 401 Unauthorized
content-type: text/plain
www-authenticate: Basic realm="traefik"
content-length: 17
date: Fri, 25 Oct 2019 07:30:57 GMT
X-Firefox-Spdy: h2

The most important fields here are status code and www-authenticate header. They tell us that people might access this page but only if they can provide a valid username and password combination so my browser asked me to provide this information so it can try to access this endpoint again using the user provided username and password pair.

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 it’s 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 of all, it’s important not to use any htpasswd generators available on various web sites. 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 straighforward: username:hashedPassword

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

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 consitered secure. Cost factor of 5 results in 2^5=32 iterations and cost factor of 10 resuts 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 of all, 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.

security  ·  reverse proxy  ·  nginx proxy  ·  traefik  ·  http  ·  authentication  ·  bcrypt

This blog doesn't show ads and the reasons are simple:

  • Most people don't want to see ads, that's not what they're after when they open web pages
  • Ad scripts can track visitors, exposing their private data to third parties

If you found this post valuable and you wish to leave a tip, you can do it with Bitcoin: