Sign inSign up

novinem/networkbot

By novinem

Updated 12 months ago

The NetworkBot is responsible for auto-managing the top-level reverse proxy for multiple webservers.

Image
Networking
Web servers
0

903

novinem/networkbot repository overview

About this image

The NetworkBot is responsible for automatically managing the top-level reverse proxy (frontend) for multiple webservers in the same network (backends). The reason that we need a top-level reverse proxy in the first place is because we want to host websites on multiple servers behind the same external IP. If we would have an option to have multiple IP's pointing to our network, then we would not need a top-level reverse proxy. However, also in that case it would provide a convenient central place for managing SSL certificates and all the traffic to the network.

The tasks that the NetworkBot automates are:

  • Deploying a new 'route' (for one or more domains) from the frontend to a backend when we deploy a new website on a backend server.
    • The NetworkBot will check if this backend is authorized to use the domain(s) (by checking a specific DNS record) before it deploys a new 'route'.
  • Generating a self-signed certificate so that we can usethe route immediately.
  • Requesting a Letsencrypt certificate to replace the self-signed certificates.
    • The NetworkBot will check if the domains in the certificate point to our network before it requests the certificate to stay within the Letsencrypt limits.
  • Renewing a Letsencrypt certificate when it reaches a certain age.
  • Removing a 'route' from the frontend to a backend when the backend when we take down a website from a backend server.
  • Removing a Letsencrypt certificate when it reaches a certain age and it is no longer in use on a 'route'.
  • Changing the IP for every domain after our dynamic IP changed (only for the domains that are using the old IP)

About: Changelog

1.0: Stable release.

1.1: Changed DNS resolver to 8.8.8.8 and 1.1.1.1 to bypass local DNS cache.

About: Known Limitations

DDNS: While the code is ready to plug'n'play different Registrars (with abstract classes), the NetworkBot's DDNS feature only support one combined Registrar at a time for all of the backends. So, backend-A can't use a different registrar then Backend-B.

Letsencrypt: The NetworkBot just uses plain old ACME (web) challenges to request new certificates, so we can't use wildcards. Also, the NetworkBot does not monitor or implement the 'rate limits' for Letsencrypt. This means that (in theory) you could hit the rate limits when you are trying to deploy a lot of new websites at once when migrating to a new server. After this, the NetworkBot keeps hitting the rate limits again and again with every new iteration of the main loop (every 10 seconds by default). This would result in some good amount of 'Spam' on the logging service (Slack).

About: Implementations

While the NetworkBot is capable of plug'n'play different types of frontends, backends, logging services and Registrars (by extending the abstract classes and re-building the image), this current version only implements the following services:

  • Frontend: Nginx
  • Backend:
    • Traefik (via the JSON API)
    • Virtualmin (via the WEB API)
  • Logging: Slack
  • Registrar: TransIP

Installation

This image is only an implementation of the NetworkBot itself. This means that the actual frontend (Nginx) needs to be run as a standalone Docker image (or on the host directly). Also, we ofcourse assume that you already have a working implementation of (at least) one backend server (Traefik or Virtualmin) and that this server is reachable by the (future) NetworkBot server.

Installation: With Plain Docker

  1. Download the NetworkBot image from the Docker Hub with the ID: novinem/networkbot.
  2. Make sure the required files/folders are mounted in the container. Based on the default config.yml settings these are:
    1. /usr/src/dynamic_config/config.yml - The config for the NetworkBot (see the config.yml.example)
    2. /usr/src/dynamic_config/*.key - All the 'private_keyfiles' for the 'registrar_meta' section inside each backend in the config.yml.
    3. /usr/src/frontend_config/sites-enabled - The 'sites-enabled' that will hold all of the config files (routes) for use by the Nginx frontend.
    4. /usr/src/frontend_config/upstream-conf/upstream.conf - The 'upstream-conf' that will hold all of the NGINX 'upstream' blocks. The names of these blocks must correspond to the names of the backends in config.yml
    5. /usr/src/frontend_config/nginx_template.conf - The template file for generating new Nginx configs.
    6. /tmp/acme_challenge - The 'acme_challenge' folder that will hold all of the temporary ACME files when a Letsencrypt challenge is performed. Because the NetworkBot does not implement a webserver directly, you need to make sure that the challenges from this folder will be served at the right ACME challenge URL when requested.
    7. /etc/letsencrypt - The folder that will store all the official Letsencrypt certificates that will be referenced from the 'sites-enabled' config files. So make sure to also mount this folder in the Nginx Container at the same location.
    8. /openssl/self-signed - The folder that will store all of the self-signed certificates that will be referenced from the 'sites-enabled' config files. So make sure to also mount this folder in the Nginx Container at the same location.
  3. Start the container with: docker run --rm novinem/networkbot -v ...
    • Like you see, this image doesn't expose any ports.

Installation: With Docker-compose

  1. Copy the docker-compose.yml file to your directory.
  2. Make sure the required files/folders exist in your working directory. See the chapter above for details about the required files and folders.
  3. Run the NetworkBot with: docker compose up

Example files

Warning! Before using these example files, first read the chapter above.

Example files: config.yaml


# |||||||||||||||||||||||||||||||||||||
# |
# | Dynamic configuration
# |
# |||||||||||||||||||||||||||||||||||||

continuous: false
continuous_delay_ms: 10000

ssl_dryrun: false
ssl_email: [email protected]
ssl_country_code: NL
ssl_state: Noord-Brabant
ssl_city: Mierlo
ssl_company: Novinem
ssl_devision: Hosting

# You can use multiple loggers at once.
# The different values for 'log_level' are: debug, info, warning, error, fatal
# The 'backends' parameter accepts an array of names for backends (name_in_frontend) to which this logger applies. If this array is empty, then this logger will only log non-backend specific errors.
loggers:
  - type: slack
    backends: []
    log_level: debug
    type_meta:
      slack_webhook_url: https://hooks.slack.com/...
  - type: slack
    backends: [backend_name1,backend_name2]
    log_level: debug
    type_meta:
      slack_webhook_url: https://hooks.slack.com/...

frontend: nginx
frontend_meta:
  sites_enabled_dir: /sites-enabled
  template_file: /nginx_template.conf

# 'name_in_frontend' is the name of the 'upstream' block in your Nginx config.
# 'dns_prefix' is the name of the DNS record that need to be checked for authorization.
# 'dns_identifier' is the value of the DNS record that need to be checked for authorization.
# 'traefik_router_exclusions' needs to be an array with the names of the routers that need to be excluded.
# 'dns_only_validate_root' needs to be a boolean which determines if the DNS validation only will occur on the root domain (true), or on all subdomains (false)
backends:
  - name_in_frontend: backend_name1
    dns_prefix: _networkbot
    dns_identifier: ABCDE
    dns_only_validate_root: true
    type: virtualmin
    type_meta:
      virtualmin_api_url: https://<host>:10000
      virtualmin_root_password: PASSWD_HERE
    registrar: transip
    registrar_meta:
      username: novinem
      private_keyfile: /keyfile.key
      
  - name_in_frontend: backend_name2
    dns_prefix: _networkbot
    dns_identifier: ABCDE
    dns_only_validate_root: false
    type: traefik
    type_meta:
      traefik_api_url: http://<host>:8080
      traefik_router_exclusions:
        - dashboard@internal
        - api@internal
    registrar: transip
    registrar_meta:
      username: novinem
      private_keyfile: /keyfile.key

# The ddns_former_ip_list is an array that can contain multiple IPv4 or IPv6 addressed that the system will check at TransIP after our IP changes.
ddns_enabled: true
ddns_run_at_startup: true
ddns_former_ip_list: []



# |||||||||||||||||||||||||||||||||||||
# |
# | Static configuration 
# | 
# | !! (IMPORTANT) These files and paths are hardcoded in the image and exist only in this config for future reference.
# | So, only change this if you know what you are doing.
# |
# |||||||||||||||||||||||||||||||||||||

public_suffix_list: /usr/src/public_suffix_list.dat
dynamic_config_dir: /usr/src/dynamic_config
frontend_config_dir: /usr/src/frontend_config
ssl_letsencrypt_acme_challenge_dir: /tmp/acme_challenge
ssl_letsencrypt_certificate_dir: /etc/letsencrypt
ssl_selfsigned_certificate_dir: /openssl/self-signed

Example files: upstream.conf

upstream backend_name1 {
    server 192.168.50.13:80;
}

upstream backend_name2 {
    server 192.168.50.206:80;
}

Example files: nginx_template.conf

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name --ALL-DOMAINS--;

    # Link to certificate
    ssl_certificate --SSL-CERTIFICATE-FILE--; # Will be replaced by Ansible variable
    ssl_certificate_key --SSL-KEY-FILE--; # Will be replaced by Ansible variable
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    # Proxy request to service
    proxy_set_header Host $http_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;

    location / {
        proxy_buffering off;
        proxy_pass http://--NGINX-BACKEND--/;

        # Websocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }

}

Example files: Docker-compose File

---
version: '3'
    
services:
    
    networkbot_autoconfig:
        image: novinem/networkbot:latest
        container_name: networkbot_autoconfig
        depends_on:
          - networkbot_nginx
        volumes:
          # Bind mounts (dynamic config)
          - ./mounts/dynamic-config:/usr/src/dynamic_config
          
          # Bind mounts (frontend config)
          - ./mounts/frontend-config:/usr/src/frontend_config
          
          # Volumes
          - certbot-acme-challenge:/tmp/acme_challenge
          - certbot-data:/etc/letsencrypt
          - ssl-self-signed:/openssl/self-signed
        restart: unless-stopped
    
    networkbot_nginx:
        image: novinem/nginx-unprivileged:latest
        container_name: networkbot_nginx
        volumes:
          # Bind mounts
          - ./mounts/frontend-config/sites-enabled:/etc/nginx/sites-enabled:ro
          - ./mounts/frontend-config/upstream-conf:/etc/nginx/upstream-conf:ro
          
          # Volumes
          - certbot-acme-challenge:/tmp/acme_challenge
          - certbot-data:/etc/letsencrypt
          - ssl-self-signed:/openssl/self-signed
        restart: unless-stopped
        ports:
          - 80:80
          - 443:443
  
volumes:
  certbot-acme-challenge:
    name: certbot-acme-challenge
  
  certbot-data:
    name: certbot-data
    
  ssl-self-signed:
    name: ssl-self-signed

Tag summary

Content type

Image

Digest

sha256:edd7a32e1

Size

147 MB

Last updated

12 months ago

docker pull novinem/networkbot