Interface Module

How do we interface our services to the web using https all over one port 443 - Nginx Proxy Manager. How do we secure our server - Authelia and 2FA. This is the Interface Module.

Interface Module
Photo by Killian Cartignies / Unsplash

How we self host many services over 1 open port with a Proxy Manager

We will build the self hosting module - interface

Problem statement

We have only ports 80 and 443 open to the web. We have/will have many services we want to host for friends and family or even open them to the public. We want to feel safe.

  • We want to stop people entering our local LAN and its machines
  • We want to control who can access a service
  • It needs to he HTTPS and use trusted certificates

The Solution

You could go mane routs to solve this. There is a multitude of apps to use but we will start with this on. Because this is a journey of learning and choices we will change this solution. I will for sure and you will probably do so.

  • We setup firewalls on the router, node and the VM it self
  • We run a Reverse Proxy to guide users to the right container
  • We use Authelia to secure access into our services and 2FA (two factor authentication
  • We do not open port in the containers if we do not need to

The install we will do will use your VM, all prepared and including Fail2ban and root access blocked for ssh. We will run, in containers, Nginx Proxy Manager, Authelia, Dozzle and Watchtower. We install a script interface.sh. When we run it you choose if you need that container or mot.
The only must have is the NPM (a openresty based reverse proxy).
Or use this: Authelia does authentications, Dozzle reads the logs, WachTower keeps the images at newest (on good and bad). The Fail2ban in the VM is providing protection against attempts to enter by brute force.

Ngingx Proxy Manager

NPM Expose web services on your network · Free SSL with Let's Encrypt · Designed with security in mind · Perfect for home networks. Expose your private network Web services and get connected anywhere.

GUI based on Tabler, the interface is a pleasure to use. Free SSL by built in Let’s Encrypt support allows you to secure your Web services. The certificates even renew themselves.

Built as a Docker Image, Nginx Proxy Manager only requires a database.

Multiple Users can be configured to either view or manage their own hosts. Full access permissions are available.

Authelia

Authelia is an open-source full-featured authentication server providing a login portal and treating authentication requests in cooperation with NGINX. Authelia is build to Protect your applications with Single Sign-On and 2 Factor.

  • Authelia offers a login portal to allow your users to login once and access everything.
  • Authelia supports time-based one-time passwords generated by Google Authenticator.
  • Per-resource authorizations. Authelia let you define fine-grained authorizations for every resources served by your various subdomains.
  • Authelia regulates the number of login attempts made by a user to avoid brute force attacks.

Dozzle log reader

Dizzle is a real-time log viewer for docker containers. Made with ❤️ by Amir Raminfar.

Installation of the Interface module

We start by downloading the script 3-interface.sh into ~/docker-stack (home/user/docker-stack) and make it executable `chmod +x 3-interface.sh` , read the code, change the code and then we run the script. Then we re-check and we start the containers. The log is called interface.log and is in the users home directory.

Initial stage

  1. Download the script 3-interface.sh to your VM by wget:
    wget https://raw.githubusercontent.com/nallej/MyJourney/main/interface/3-interface.sh
  2. Edit the scripts code section
  3. Make the script executable
  4. Run the script
  5. Re-check the containers
  6. Set redirects on the router (80, 443)
  7. Start the containers
wget .../3-interface.sh
nano 3-interface.sh
chmod +x 3-interface.sh
./3-interface.sh
read the ~/interface.log
goto router
edit the other files 
👉
Our setup is using SQLight3. If needed you can add a MariaDB.

Start using NPM in basic mode

  1. Assign, in your router, port 80 and 443 to be directed to the vm
  2. Log in to NPM as '[email protected]' and psw 'changeme'
  3. Change the psw
  4. add user: your email and psw
  5. add services
👉
If you need more security and love 2FA then Authelia is a good choice

Edit Authelia's configuration

  1. Edit authelias configuration.yml
  2. Edit authelias users.yml
  3. add the cfg's to the sites advanced tab in NPM
  4. test, test and test

Now you should have the Interface up and running

Remember! You need to configure the firewall and Fail2ban

Next episode is the Web module with Add Blocking, DNS

🤓
Next episode is the Web module with Add Blocking, DNS

Gateway left open should you choose to enter.
Photo by Saish Menon / Unsplash

Below is the members section

Reverse Proxy Managers

To host many exposed web sites/apps in your HomeLab

You have many reverse proxies to choose from. Traefic, pfSense, Nginx, NPM ...

You need some screening of users and a lot ot protection. A good Firewall is a must, and pfSense is a robust one. It can also perform many other functions. Cloudflair and their apps, Authelia, Fail2ban, CrowdSec ...

  • NPM doesn’t use nginx.
  • it's using openresty - a heavily modified version of nginx
  • which CrowdSec sadly doesn’t support.

Nginx Proxy Manager - NPM

NPM is quick to install and easy to set up. It works well with Authelia.

docker-compose.yml

---
services:
# Nginx Proxy Manager
  npm:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: npm
    restart: unless-stopped #always
    ports:
      - '80:80'
      - '81:81'
      - '443:443'
    # Add any other Stream port you want to expose
    # - '21:21' # FTP
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
#      - ./letsencrypt.ini:/etc/letsencrypt.ini:rw
    networks:
      - frontend

# MariaDB if needed for heavy use goes here

# Networks for the services
networks:
  frontend:
    external: true
#  backend: # used only for MariaDB
#    external: true

auth.example.com cfg in advanced tab

location / {
        set $upstream_authelia http://192.168.###.###:9091; # http://<serverip:port> e.g. http://1>
        proxy_pass $upstream_authelia;
        client_body_buffer_size 128k;

        #Timeout if the real server is dead
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;

        # Advanced Proxy Config
        send_timeout 5m;
        proxy_read_timeout 360;
        
        proxy_send_timeout 360;
        proxy_connect_timeout 360;

        # Basic Proxy Config
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-Uri $request_uri;
        proxy_set_header X-Forwarded-Ssl on;
        proxy_redirect  http://  $scheme://;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_cache_bypass $cookie_session;
        proxy_no_cache $cookie_session;
        proxy_buffers 64 256k;

        # If behind reverse proxy, forwards the correct IP
        set_real_ip_from 10.0.0.0/8;
        set_real_ip_from 172.0.0.0/8;
        set_real_ip_from 192.168.0.0/16;
        set_real_ip_from fc00::/7;
        real_ip_header X-Forwarded-For;
        real_ip_recursive on;
    }

site by site of CNAME.example.com

location /authelia {
    internal;
    #set $upstream_authelia http://<your-authelia-server-ip-and:port>/api/verify; #ADD YOUR IP AND PORT OF AUTHELIA - same for all your sites
    set $upstream_authelia http://192.168.1.101:9091/api/verify;
    proxy_pass_request_body off;
    proxy_pass $upstream_authelia;    
    proxy_set_header Content-Length "";
 
    # Timeout if the real server is dead
    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
    client_body_buffer_size 128k;
    proxy_set_header Host $host;
    proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr; 
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-Uri $request_uri;
    proxy_set_header X-Forwarded-Ssl on;
    proxy_redirect  http://  $scheme://;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_cache_bypass $cookie_session;
    proxy_no_cache $cookie_session;
    proxy_buffers 4 32k;
 
    send_timeout 5m;
    proxy_read_timeout 240;
    proxy_send_timeout 240;
    proxy_connect_timeout 240;
}
 
    location / {
        #set $upstream_<app-site-name> http://<app-site-ip:port>;  #ADD IP AND PORT OF SERVICE
        #proxy_pass $upstream_<app-site-name>;  #change name of the service
        set $upstream_whoogle http://192.168.1.101:5000; 
        proxy_pass $upstream_whoogle>;

        auth_request /authelia;
        auth_request_set $target_url $scheme://$http_host$request_uri;
        auth_request_set $user $upstream_http_remote_user;
        auth_request_set $groups $upstream_http_remote_groups;
        proxy_set_header Remote-User $user;
        proxy_set_header Remote-Groups $groups;
        #error_page 401 =302 https://auth.<example.com>/?rd=$target_url;
        error_page 401 =302 https://auth.example.com/?rd=$target_url;
 
        client_body_buffer_size 128k;
 
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
 
        send_timeout 5m;
        proxy_read_timeout 360;
        proxy_send_timeout 360;
        proxy_connect_timeout 360;
 
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-Uri $request_uri;
        proxy_set_header X-Forwarded-Ssl on;
        proxy_redirect  http://  $scheme://;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_cache_bypass $cookie_session;
        proxy_no_cache $cookie_session;
        proxy_buffers 64 256k;
 
        # add your ip range here, and remove this comment!
        set_real_ip_from 192.168.1.0/16;
        set_real_ip_from 172.0.0.0/8;
        real_ip_header X-Forwarded-For;
        real_ip_recursive on;
    }

Authelia

You can use Authelia for securing your web apps running in the Docker-Stack(s). An other alternative is to use Cloudflairs in-built features.

You need NPM and edge serfiticate installed for secur communication. Authelia uses 4 typs of configs.

  1. cofiguration.yml
  2. Users database users.yml or for corporates MariaDB
  3. auth host setting - NPM's sites advanced tab (cname.example.com)
  4. site based settings - NPM's advanced tab auth.example.com

Passwords

Do your passwords online or run:* docker run authelia/authelia:latest hash-password [password]. Default argon2 tags are: iterations: 1, memory: 64, parallelism: 8, salt_lenght: 16. Others are: key_lenght 32 and you can set a salt_string:

Comments

Authelia is not the easiest to set up and to keep running but for skilled users it's no problem. One way to help is to validate: authelia validate-config configuration.yml.

Your life is some times easier with the install of phpMyAdin or Adminer for DB editing and creation.

You have bypass, one-factor and two-factor authentication to use for different site needs. You also have the NPM setting to add one more layer if you like to make it complicated. NPM vm can also use fail2ban but it needs a feedback loop to Cloudflare for getting the real IPs to block.

You could use CrowdSec but NPM is not directly supported for now. You could change to a Nginx and configure it as a reverse proxy.


docker-compose.yml

version: '3.3'

services:
#Authelia will give authentication support to NPM
  authelia:
    image: authelia/authelia
    container_name: authelia
    volumes:
      - ./config:/config
# Ports will only be used if not in the same VM
#    ports:
#      - 9091:9091
    restart: unless-stopped #allways
    healthcheck:
      disable: true
    #healthcheck:
    #  test: ["CMD", "curl", "--fail", "http://auth.example.com"]
    #  interval: 1m30s
    #  timeout: 10s
    #  retries: 3  
    environment:
      - TZ=Europe/Helsinki
    depends_on:
      - redis
      - db
    networks:
      - kadulla
      - pihalla
# Redis is needed to run Authelia
  redis:
    image: redis:alpine
    command: redis-server --requirepass "4u7x!A%D*G-KaPdSgVkXp2s5v8y/B?E("
    container_name: authelia_redis
    volumes:
      - ./redis:/data
    expose:
      - 6379
    restart: unless-stopped #always
    environment:
      - TZ=Europe/Helsinki
    networks:
      - pihalla
# MariaDB is needed to run Authelia
  db:
    image: mariadb
    container_name: authelia_db
    restart: unless-stopped #always
    environment:
      MYSQL_RANDOM_ROOT_PASSWORD: 55 # We do not edit the db
      #MYSQL_ROOT_PASSWORD: 'LongComplicatedPa$$word' # replace-with-secure-password ${MYSQL_ROOT_>
      #MYSQL_ROOT_PASSWORD__FILE: /run/secrets/DB_ROOT_PWD
      MYSQL_DATABASE: 'authelia'ThisIsA_VeryLongAndSuper
      MYSQL_USER: 'authelia'
      MYSQL_PASSWORD: 'A_ComplicatedPa$$word' # replace-with-secure-password ${MYSQL_PASSWORD}
      #MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD
    volumes:
      - ./mysql:/var/lib/mysql
    networks:
      - pihalla
    
# networks the services will use
networks:
  kadulla:          #frontend
    external: true
  pihalla:          #backend
    external: true
...

-

users.yml

#---------------------------------------------------------------------------#
#                         Users Database List                               #
#                                                                           #
# This file can be used if you do not have an LDAP set up.                  #
#                                                                           #
# docker run authelia/authelia:latest authelia hash-password 'password'     #
#                                                                           #
#---------------------------------------------------------------------------#

# List of users
users:
  pomo:
    displayname: "Authelia User"
    # Password is authelia
    password: "$argon2id$v=19$m=65536,t=1,p=8$VDhpQnRsN2Jjbm05bVpKRw$l/d/aDI8N8DqIM/lQLstnDvxgvaWLGW2Rm8UAM9XXtc"
    email: [email protected]
    groups:
      - admins
      - dev

  user1:
    displayname: "User of power"
    password: "$argon2id$v=19$m=65536,t=1,p=8$VDhpQnRsN2Jjbm05bVpKRw$l/d/aDI8N8DqIM/lQLstnDvxgvaWLGW2Rm8UAM9XXtc"
    email: [email protected]
    groups:
      - admins
      - superusers
      - users
      - dev

#---------------------------------------------------------------------------#
# Use a password site with argon2 (see filesfor settings)                   #
# docker run authelia/authelia:latest authelia hash-password 'password'     #
#---------------------------------------------------------------------------#

configutaion.yml

###############################################################
#  Authelia configuration | example.com ** EDIT befor use     #
###############################################################

server:
  host: 0.0.0.0
  port: 9091 # change to port in docker-compose.yml
#  read_buffer_size: 4096
#  write_buffer_size: 4096
# For using your logo.png and favicon.ico
#  asset_path: /config/assets/

jwt_secret: miro6Roswejapr9mlt0eveb9WrLtR1

default_redirection_url: https://ws.example.com

totp:
  issuer: example.com
  period: 30
  skew: 1

#duo_api:     ## You can use this api if you want push notifications of auth attempts
#  hostname: api-123456789.example.com
#  integration_key: ABCDEF
#  secret_key: your syper secret and super string (characters and numbers and symbols)

access_control:
  default_policy: deny # NginX domains are denied unless added below
  #networks:
  #  - name: internal
  #    networks:
  #      - 192.168.1.0/24
  #      - 172.17.0.0/24
  #      - 172.18.0.0/24
  #      - 172.19.0.0/24
  rules:
    # Rules applied to everyone
    - domain: 
      - auth.example.com
      - www.example.com
      - whoogle.example.com
      - techblog.example.com
      policy: bypass

    - domain:
      - test.example.com
      - hakuna.example.com
      - stargate.example.com
      policy: one_factor

    - domain:
      - heimdall.example.com
      - ntop.example.com
      - nextcloud.example.com
      - ghost.example.com
      policy: two_factor

authentication_backend:
  disable_reset_password: false
  file:
    path: /config/users.yml
    password:
      algorithm: argon2id
      #iterations: 1
      ##key_lenght 32
      #memory: 64
      #parallelism: 8
      ##salt_string: 
      #salt_lenght: 16

session:
  name: authelia_session
  # This secret can also be set using the env variables AUTHELIA_SESSION_SECRET_FILE
  secret: aComplicatedString30CharactersAndNumpers
  expiration: 1h # 3600# 1h or  600 10m
  inactivity: 7200 # 2h or 300  5m
  domain: example.com # your root protected domain

  redis:
    host: authelia_redis
    port: 6379
    # This secret can also be set using the env variables AUTHELIA_SESSION_REDIS_PASSWORD_FILE
    # The service will run without a password
    password: "SetYourPa$$wordForRedis" #authelia

regulation:
  max_retries: 3
  find_time: 120 #2m
  ban_time: 600 #10m

#storage:
#  encryption_key: aSecret666StringUsedForEncryptionSeed
#  local:
#    path: /config/db.sqlite3
storage:
  encryption_key: "you_must_generate_a_random_string_of_more_than_twenty_chars_and_configure_this"
  mysql:
    host: authelia_db
    database: authelia
    username: authelia
    password: "Pa$$word"

# Logging is set here
log:
  level: info
  format: text
  #file_path: /config/authelia.log
  
# Apperance of Authelia vcan be light or dark ---------------------------------------
theme: dark   #  dark / light

# Optional if your system has ntp  implemented --------------------------------------
ntp:
  address: "time.cloudflare.com:123"
  version: 3
  max_desync: 3s
  disable_startup_check: false
  disable_failure: false

# Notification by e-mail or file ----------------------------------------------------
notifier:
  disable_startup_check: false #true/false
#  filesystem:
#    filename: /config/notification.txt #if you want the psw into a file not in email
  smtp:
    username: [email protected]
    password: your-mail-passwd
    host: mail.example.com
    port: 587  # 25 non-ssl, 443 ssl, 587 tls 
    sender: [email protected]
    subject: "[Authelia] {title}"
    disable_require_tls: false # set to true if your domain uses no tls or ssl only
    disable_html_emails: false # set to true if you don't want html in your emails
    tls:
      server_name: mail.example.com
      skip_verify: false
      minimum_version: TLS1.2

Photo by Jason Dent / Unsplash