How I expose services both to Internet and to LAN - SSL enabled

Hi,
I want to share with you a solution I adopted, in order to expose services both to the Internet and to LAN, terminating https connections with the same let’sencrypt certificate via two reverse proxies.

It’s not perfect but I found it fun to implement.

                 from the Internet
                        │
                        │
                        ▼
      svc.example.com(public IP)              NETHSERVER
     ────┬───────────────┬─────┬────────────┬────────────
         │reverse-proxy  │     │ LE certbot │
         └───────┬───────┘     └───────┬────┘
                 │                     │
                 │                     │
                 │         pushes certs to the backend
                 │               on every renewal
                 │                     │ (via scp)
                 │   ┌─────────────────┘
                 │   │
                 │   │
                 ▼   ▼     ┌────────────┐
           ┌───────────┐   │Traefik     │
           │  service  │ ◄─┤rev.proxy   │
       ────┴───────────┴───┴────────────┴────────────────
      LINUX-SERVER          svc.example.com (private IP)
                                 ▲
                                 │
                                 │
                           from the LAN

The concept is based on split horizon DNS in which there are two different addresses to resolve “svc.example.com”:one public and one private. The two long horizontal lines represent the two “front” one public front of the nethserver and one private front of the linux-server that hosts the service.
Clients from the internet resolve svc.example.com via public DNS to a IP address on the RED interface of my nethserver. Clients from the LAN resolve the same svc.example.com via internal DNS server to the private IP of the linux-server.

On Linux-server I run two containers: one contains the service itself that listens to port TCP8001 for example, and the other one contains an instance of traefik reverse proxy that forwards https requests to port TCP8001.

The public reverse proxy is one httpd instance that runs on nethserver and corresponds to the “webserver application” and terminates https connections that forwards to port tcp8001 of the linux-server.

Details:
In order to configure all this mess you have to follow these steps:

  1. Register on your public DNS zone a new A record that pointa at you public IP on the nethserver.
  2. Register on your private DNS zone a new A record that points at the private IP of a linux-server in your intranet.
  3. Gain a let’sencrypt certificate configurin nethserver this way:
    under system->certificates->let’s encrypt certificates → +add a domain (add the A record you have registered in step 1&2)
  4. Create a user on the linux server, with the only purpose to send it the updated LE certificate (cert + priv key) at every renewal. (on linux server useradd -md /home/certbot certbot; mkdir /home/certbot/certs )
  5. On the nethserver console: ssh-copyid certbot@ in order to issue, lather on, automatic scp commands without asking for any password.
  6. On the nethserver: create a script under /etc/letsencrypt/renewal-hooks/deploy/15scpcerts with the following commands:
#!/bin/bash

/usr/bin/scp /etc/letsencrypt/live/<your-nethserver-hostname>/{fullchain.pem,privkey.pem} certbot@<linux-server>:/home/certbot/certs

This way, every time…ok you got it.
7) on the linux-server place this traefik docker-compose.yaml:

version: "3"

networks:
 traefik:
   external: true

services:

  traefik:
    image: "traefik:v2.10"
    container_name: "traefik"
    command:
      #- "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.network=traefik"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.file.directory=/etc/traefik/"
      - "--providers.file.watch"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.address=:80"
      - "--entryPoints.websecure.forwardedHeaders.insecure=true"
    networks:
      - "traefik"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./traefik-dynamic-config/:/etc/traefik/"
      - "/home/certbot/certs:/certs"
    labels:
      traefik.http.routers.http-catchall.rule: hostregexp(`{host:.+}`)
      traefik.http.routers.http-catchall.entrypoints: web
      traefik.http.routers.http-catchall.middlewares: redirect-to-https@docker
      traefik.http.middlewares.redirect-to-https.redirectscheme.scheme: https
    restart: always

and this for the dynamic configuratin part about certificates:

traefik-dynamic-config/certs-traefik.yml:

---
tls:
  certificates:
    - certFile: /certs/fullchain.pem
      keyFile: /certs/privkey.pem

this way traefik will use cert and key placed into /home/certbot/certs

  1. deploy a service, one example in the yaml file:
version: "3"

networks:
  traefik:
    external: True

services:
  test:
    image: nginx:latest
    container_name: test
    ports:
      - 8001:80
    volumes:
      - "./html:/usr/share/nginx/html"
    restart: always
    networks:
      - "traefik"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.webservertest.rule=Host(`svc.example.com`)"
      - "traefik.http.routers.webservertest.entrypoints=websecure"
      - "traefik.http.routers.webservertest.tls=true"
  1. then you have to configure reverse proxy on the nethserver “webserver application”
    under: webserver->create a reverse proxy → name: “https://srv.example.com” destination URL: “http://linux-server:8001

and that’s all.
Another simpler way shoud be to bypass traefik and private name resolution. You then have to resolve the service with the public ip and configure air-pin-nat on the nethserver, in order to reach the svc.exmple.com from the LAN… but I think the first solution is more elegant and efficient.

thank you for your attention, any comment are appreciated.
bye.

Fabio

5 Likes

Hi @fabio8ne

A good How-To!

I’ve been using split horizon DNS (Also called split-brain DNS…) for over 20 years now, although I mostly use this via a single reverse Proxy (NS7).

So called Air-Pin-NAT is a crux, and should be avoided. It just shows that someone doesn’t really understand the possibilities of DNS… :slight_smile:

Generally:

  • Internal DNS can correctly resolve ALL internal hosts, and all relevant external hosts (Internet).
  • External DNS only resolves what the Internet “needs” to know, eg: your Mail server, your webpage.

External DNS do not need to resolve internal NAS, Printers, Servers, etc. Internal must be able to do so, or you’re going to get false stats when monitoring.

My 2 cents
Andy

PS: Check monitoring with Zabbix!

My 2 cents
Andy

1 Like

Of course, IPv6 would avoid the problem entirely…

Fully agree, but fact is, the global migration to IPv6 has taken so far over 20 years, and isn’t close to completing the Job. IPv4 will still be around for a while…

Until then we have to cope with both, and use the most elegant solutions available! :slight_smile:

And even with IPv6, one has to take care (due dilligence in planning / implementing!) not to expose the internal architecture to the Internet…

My 2 cents
Andy

Unfortunately, until in Italy IPv4 will be not be a payed option from ISPs, I doubt Nethesis will solve the rubik’s cube of IPv6.
CentOS 6 supports it, Windows XP had support in it.
And on the other hand… A lot of other open source projects are not up to speed, unless it’s simply “associate that ip with our hostname”, more or less, leaving all the fuss for the underlying OS and OS admins.

My bet is the first usable IPv6 dashboard implementation will be massively copied along the projects.