From 3e1e94817a5f616638445d54b8b99e1119bbea82 Mon Sep 17 00:00:00 2001 From: Adrien Poupa Date: Fri, 30 Dec 2022 19:05:22 -0500 Subject: [PATCH] feat: add Let's Encrypt SSL certificates and Tailscale instructions --- .env.example | 5 ++++ .gitignore | 2 ++ CONFIGURATION.md | 59 +++++++++++++++++++++++++++++++++++++++++++- README.md | 4 +-- docker-compose.yml | 31 +++++++++++++++++------ letsencrypt/.gitkeep | 0 6 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 letsencrypt/.gitkeep diff --git a/.env.example b/.env.example index 18baaee..3dae50c 100644 --- a/.env.example +++ b/.env.example @@ -7,3 +7,8 @@ PIA_LOCATION=ca PIA_USER= PIA_PASS= PIA_LOCAL_NETWORK="192.168.0.0/16" +HOSTNAME= +LETS_ENCRYPT_EMAIL= +CLOUDFLARE_EMAIL= +CLOUDFLARE_DNS_API_TOKEN= +CLOUDFLARE_ZONE_API_TOKEN= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 75254f8..4b5f84f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ !/pia/.gitkeep /pia-shared !/pia-shared/.gitkeep +/letsencrypt +!/letsencrypt/.gitkeep \ No newline at end of file diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 16066e9..d7affe8 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -69,7 +69,7 @@ place in the VPN container, the hostname for qBittorrent is the hostname of the The indexers are configured through Prowlarr. They synchronize automatically to Radarr and Sonarr. -Radarr and Sonarr may then be added via Settongs > Apps. The Prowlarr server is `http://prowlarr:9696/prowlarr`, the Radarr server +Radarr and Sonarr may then be added via Settings > Apps. The Prowlarr server is `http://prowlarr:9696/prowlarr`, the Radarr server is `http://radarr:7878/radarr` and Sonarr `http://sonarr:8989/sonarr`: ![](https://cdn.poupa.net/uploads/2022/03/sonarr.png) @@ -100,3 +100,60 @@ Applications can be added in Items > Add. The URLs should be the static IP, ie: for example. ![](https://cdn.poupa.net/uploads/2022/03/homepage.png) + +## Traefik and SSL Certificates + +While you can use the private IP to access your NAS, how cool would it be for it to be accessible through a subdomain +with a valid SSL certificate? + +Traefik makes this trivial by using Let's Encrypt and one of its +[supported ACME challenge providers](https://doc.traefik.io/traefik/https/acme/). + +Let's assume we are using `nas.domain.com` as custom subdomain. + +The idea is to create an A record pointing to the private IP of the NAS, `192.168.0.10` for example: +``` +nas.domain.com. 1 IN A 192.168.0.10 +``` + +The record will be publicly exposed but not resolve given this is a private IP. + +Given the NAS is not accessible from the internet, we need to do a dnsChallenge. +Here we will be using CloudFlare, but the mechanism will be the same for all DNS providers +baring environment variable changes, see the Traefik documentation above and [Lego's documentation](https://go-acme.github.io/lego/dns/). + +Then, we need to fill the `.env` entries: + +- `HOSTNAME`: the subdomain used, `nas.domain.com` for example +- `LETS_ENCRYPT_EMAIL`: e-mail address used to send expiration notifications +- `CLOUDFLARE_EMAIL`: Account email +- `CLOUDFLARE_DNS_API_TOKEN`: API token with DNS:Edit permission +- `CLOUDFLARE_ZONE_API_TOKEN`: API token with Zone:Read permission + +If you want to test your configuration first, use the Let's Encrypt staging server by uncommenting this: +``` +#- --certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory +``` + +If it worked, you will see the staging certificate at https://nas.domain.com. +You may remove the `./letsencrypt/acme.json` file and restart the services to issue the real certificate. + +### Accessing from the outside + +If we want to make it reachable from outside the network without opening ports or exposing it to the internet, I found +[Tailscale](https://tailscale.com/) to be a great solution: create a network, run the client on both the NAS and the device +you are connecting from, and they will see each other. + +In this case, the A record should point to the IP Tailscale assigned to the NAS, eg `100.xxx.xxx.xxx`: +``` +nas.domain.com. 1 IN A 100.xxx.xxx.xxx +``` + +See [here](https://tailscale.com/kb/installation/) for installation instructions. + +However, this means you will always need to be connected to Tailscale to access your NAS, even locally. +This can be remedied by overriding the DNS entry for the NAS domain like `192.168.0.10 nas.domain.com` +in your local DNS resolver such as Pi-Hole. + +This way, when connected to the local network, the NAS is accessible directly from the private IP, +and from the outside you need to connect to Tailscale first, then the NAS domain will be accessible. \ No newline at end of file diff --git a/README.md b/README.md index fd7bd49..2cdf2ad 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ After searching for the perfect NAS solution, I realized what I wanted could be achieved with some Docker containers on a vanilla Linux box. The result is an opinionated Docker Compose configuration capable of browsing indexers to retrieve media resources and downloading them through a Wireguard VPN with port forwarding. +SSL certificates and remote access through Tailscale are supported. ## Applications @@ -54,6 +55,5 @@ for some indexers in Prowlarr - [Jackett](https://github.com/Jackett/Jackett): API Support for your favorite torrent trackers, as a Prowlarr replacement - [Plex](https://www.plex.tv/): Plex Media Server - [Pi-hole](https://pi-hole.net/): DNS that blocks ads -- Use a domain name and Let's Encrypt certificate to get SSL -- Expose services with CloudFlare Tunnel +- Expose services with CloudFlare Tunnel if Tailscale is not enough - you tell me! diff --git a/docker-compose.yml b/docker-compose.yml index f880200..b5954dc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,11 @@ services: image: traefik:v2.9 container_name: traefik restart: always + environment: + - CLOUDFLARE_EMAIL=${CLOUDFLARE_EMAIL} + - CLOUDFLARE_DNS_API_TOKEN=${CLOUDFLARE_DNS_API_TOKEN} + - CLOUDFLARE_ZONE_API_TOKEN=${CLOUDFLARE_ZONE_API_TOKEN} + - LETS_ENCRYPT_EMAIL=${LETS_ENCRYPT_EMAIL} command: - --providers.docker=true - --providers.docker.exposedbydefault=false @@ -12,10 +17,17 @@ services: - --entrypoints.web.http.redirections.entryPoint.to=web-secure - --entrypoints.web.http.redirections.entryPoint.scheme=https - --entrypoints.web.http.redirections.entrypoint.permanent=true + - --certificatesresolvers.myresolver.acme.dnschallenge=true + - --certificatesresolvers.myresolver.acme.dnschallenge.provider=cloudflare + # Uncomment to test your configuration by using Let's Encrypt staging certificates + #- --certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory + - --certificatesresolvers.myresolver.acme.email=${LETS_ENCRYPT_EMAIL} + - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json ports: - "80:80" - "443:443" volumes: + - ./letsencrypt:/letsencrypt - "/var/run/docker.sock:/var/run/docker.sock:ro" sonarr: image: lscr.io/linuxserver/sonarr @@ -29,9 +41,10 @@ services: restart: always labels: - traefik.enable=true - - traefik.http.routers.sonarr.rule=PathPrefix(`/sonarr`) - - traefik.http.services.sonarr.loadbalancer.server.port=8989 + - traefik.http.routers.sonarr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/sonarr`) || PathPrefix(`/sonarr`)) - traefik.http.routers.sonarr.tls=true + - traefik.http.routers.sonarr.tls.certresolver=myresolver + - traefik.http.services.sonarr.loadbalancer.server.port=8989 radarr: image: lscr.io/linuxserver/radarr container_name: radarr @@ -44,8 +57,9 @@ services: restart: always labels: - traefik.enable=true - - traefik.http.routers.radarr.rule=PathPrefix(`/radarr`) + - traefik.http.routers.radarr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/radarr`) || PathPrefix(`/radarr`)) - traefik.http.routers.radarr.tls=true + - traefik.http.routers.radarr.tls.certresolver=myresolver - traefik.http.services.radarr.loadbalancer.server.port=7878 prowlarr: image: lscr.io/linuxserver/prowlarr:develop @@ -58,8 +72,9 @@ services: restart: always labels: - traefik.enable=true - - traefik.http.routers.prowlarr.rule=PathPrefix(`/prowlarr`) + - traefik.http.routers.prowlarr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/prowlarr`) || PathPrefix(`/prowlarr`)) - traefik.http.routers.prowlarr.tls=true + - traefik.http.routers.prowlarr.tls.certresolver=myresolver - traefik.http.services.prowlarr.loadbalancer.server.port=9696 qbittorrent: image: lscr.io/linuxserver/qbittorrent:4.5.0-libtorrentv1 @@ -78,8 +93,9 @@ services: - vpn labels: - traefik.enable=true - - traefik.http.routers.qbittorrent.rule=PathPrefix(`/qbittorrent`) + - traefik.http.routers.qbittorrent.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/qbittorrent`) || PathPrefix(`/qbittorrent`)) - traefik.http.routers.qbittorrent.tls=true + - traefik.http.routers.qbittorrent.tls.certresolver=myresolver - traefik.http.services.qbittorrent.loadbalancer.server.port=8080 - traefik.http.routers.qbittorrent.middlewares=qbittorrent-strip-slash,qbittorrent-stripprefix # https://github.com/qbittorrent/qBittorrent/issues/5693#issuecomment-552146296 @@ -130,9 +146,10 @@ services: restart: always labels: - traefik.enable=true - - traefik.http.routers.heimdall.rule=PathPrefix(`/`) - - traefik.http.services.heimdall.loadbalancer.server.port=80 + - traefik.http.routers.heimdall.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/`) || PathPrefix(`/`)) - traefik.http.routers.heimdall.tls=true + - traefik.http.routers.heimdall.tls.certresolver=myresolver + - traefik.http.services.heimdall.loadbalancer.server.port=80 watchtower: image: containrrr/watchtower container_name: watchtower diff --git a/letsencrypt/.gitkeep b/letsencrypt/.gitkeep new file mode 100644 index 0000000..e69de29