From a0e63e2e2ba59335287b6dee14bda9b424015c58 Mon Sep 17 00:00:00 2001 From: aki Date: Sat, 26 Apr 2025 01:58:42 +0800 Subject: [PATCH] feat(auth): Implement conditional authentication middleware for services in docker-compose.yml --- README.md | 71 +++++++++++++++++++++++++++++++++++++++------- docker-compose.yml | 52 +++++++++++++++++++++++---------- 2 files changed, 98 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 72bc8da..ed1d094 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,10 @@ The core idea is to manage media libraries (movies, TV shows, music), automate d - [(Optional) VPN Configuration](#optional-vpn-configuration) - [(Optional) Traefik DNS Challenge](#optional-traefik-dns-challenge) - [Service Access](#service-access) + - [Configuring Authentication Per Service](#configuring-authentication-per-service) - [Optional Services](#optional-services) - [Troubleshooting](#troubleshooting) + - [Middleware Not Found Errors](#middleware-not-found-errors) - [SELinux Socket Permissions (Docker)](#selinux-socket-permissions-docker) - [Authelia v4.38+ Configuration](#authelia-v438-configuration) - [Tailscale Issues](#tailscale-issues) @@ -39,7 +41,7 @@ This stack uses a combination of key services for routing, access, and security: * **[Tailscale](https://tailscale.com):** Provides a secure overlay network (WireGuard-based VPN) connecting your devices. It allows access to the NAS services from anywhere without opening firewall ports and handles HTTPS termination via its built-in `tailscale serve` or `tailscale funnel` features. All other services run within Tailscale's network namespace. * **[Traefik](https://traefik.io):** Acts as a reverse proxy *within* the Tailscale network. It discovers services via Docker labels and routes incoming requests (from Tailscale) to the appropriate container based on paths (e.g., `/sonarr`, `/radarr`). -* **[Authelia](https://www.authelia.com):** Serves as the authentication gateway. Traefik forwards requests to Authelia for verification. If a user isn't logged in, they are redirected to the Authelia portal (`/`). Once authenticated, Authelia sets a session cookie (stored in Redis), and Traefik allows access to the requested service. +* **[Authelia](https://www.authelia.com):** Serves as the authentication gateway. Traefik forwards requests to Authelia for verification. If a user isn't logged in, they are redirected to the Authelia portal (`/`). Once authenticated, Authelia sets a session cookie (stored in Redis), and Traefik allows access to the requested service. You can configure which services require authentication via environment variables. ## Features @@ -126,6 +128,14 @@ These steps are **mandatory** for a working installation. Without properly compl ``` - Replace the example hash in `authelia/users_database.yml` with your generated hash +6. **⚠️ Optional: Authentication Configuration** + - Choose which services require authentication by setting the corresponding variables in your `.env` file: + ``` + AUTH_JELLYFIN=false # Example: Allow Jellyfin access without authentication + AUTH_SONARR=true # Example: Require authentication for Sonarr + ``` + - By default, all services require authentication if not specified otherwise + ## Quick Start Guide After completing all [Required Setup Steps](#required-setup-steps) above, follow these steps to get up and running: @@ -216,8 +226,20 @@ This file controls essential settings. Copy `.env.example` to `.env` and modify | **`AUTHELIA_SESSION_SECRET`** | **Required.** Random secret for session cookies. **Generate your own!** | *(None - Example in file)* | | **`AUTHELIA_STORAGE_ENCRYPTION_KEY`** | **Required.** Random secret for encrypting data at rest (e.g., SQLite DB). **Generate your own!** | *(None - Example in file)* | | **`AUTHELIA_REDIS_PASSWORD`** | **Required.** Password for the Redis database (used for session storage). **Generate your own!** | *(None - Example in file)* | -| `AUTHELIA_SESSION_DOMAIN` | *Deprecated.* Domain for session cookies. Should match `APP_HOSTNAME`. (Handled within `authelia/configuration.yml` in v4.38+) | `${APP_HOSTNAME}` | -| `AUTHELIA_DEFAULT_REDIRECT_URL` | *Deprecated.* Where users land after login. (Handled within `authelia/configuration.yml` in v4.38+) | `https://${APP_HOSTNAME}/home` | +| `AUTH_SONARR` | Control whether Sonarr requires authentication (`true`/`false`). | `true` | +| `AUTH_RADARR` | Control whether Radarr requires authentication (`true`/`false`). | `true` | +| `AUTH_BAZARR` | Control whether Bazarr requires authentication (`true`/`false`). | `true` | +| `AUTH_PROWLARR` | Control whether Prowlarr requires authentication (`true`/`false`). | `true` | +| `AUTH_JELLYSEERR` | Control whether Jellyseerr requires authentication (`true`/`false`). | `true` | +| `AUTH_QBITTORRENT` | Control whether qBittorrent requires authentication (`true`/`false`). | `true` | +| `AUTH_LIDARR` | Control whether Lidarr requires authentication (`true`/`false`). | `true` | +| `AUTH_JELLYFIN` | Control whether Jellyfin requires authentication (`true`/`false`). | `false` | +| `AUTH_HOMEPAGE` | Control whether Homepage requires authentication (`true`/`false`). | `true` | +| `AUTH_FLARESOLVERR` | Control whether FlareSolverr requires authentication (`true`/`false`). | `true` | +| `AUTH_SABNZBD` | Control whether SABnzbd requires authentication (`true`/`false`). | `true` | +| `AUTH_CALIBRE` | Control whether Calibre-Web requires authentication (`true`/`false`). | `true` | + +> **Note:** Authentication variables were introduced to give you fine-grained control over which services require login. Set to `false` for services you want to access without authentication. #### Service Credentials @@ -340,20 +362,35 @@ Authelia uses the `authelia/users_database.yml` file to manage users. ## Service Access -With the default Tailscale setup and Authelia enabled, services are securely accessible via HTTPS using your Tailscale node's name or IP. Access requires authentication via Authelia. +With the default Tailscale setup and Authelia enabled, services are securely accessible via HTTPS using your Tailscale node's name or IP. Authentication is controlled by the `AUTH_*` environment variables. -* **Login Portal:** `https:///` (Redirects unauthenticated users here) -* **Homepage Dashboard:** `https:///home` (Accessible after login) -* **Sonarr:** `https:///sonarr` (Requires login) -* **Radarr:** `https:///radarr` (Requires login) -* **qBittorrent:** `https:///qbittorrent` -* **Jellyfin:** `https:///jellyfin` +* **Login Portal:** `https:///` (Redirects unauthenticated users here for secured services) +* **Homepage Dashboard:** `https:///home` (Requires login if `AUTH_HOMEPAGE=true`) +* **Sonarr:** `https:///sonarr` (Requires login if `AUTH_SONARR=true`) +* **Radarr:** `https:///radarr` (Requires login if `AUTH_RADARR=true`) +* **qBittorrent:** `https:///qbittorrent` (Requires login if `AUTH_QBITTORRENT=true`) +* **Jellyfin:** `https:///jellyfin` (Requires login if `AUTH_JELLYFIN=true`, default is `false`) * ...and so on. Replace `` with your Tailscale device name (e.g., `tailscale-nas.your-tailnet.ts.net`) or its Tailscale IP address. If you configure DNS for your `APP_HOSTNAME` variable to point to the Tailscale IP, you can use `https:///`. +### Configuring Authentication Per Service + +You can control which services require authentication by setting the appropriate variables in your `.env` file: + +```bash +# Example: Allow Jellyfin and qBittorrent without authentication, require it for others +AUTH_JELLYFIN=false +AUTH_QBITTORRENT=false +AUTH_SONARR=true +AUTH_RADARR=true +# ...and so on +``` + +If a variable is not explicitly set, authentication defaults to `true` for that service (except for Jellyfin, which defaults to `false`). + ## Optional Services Several services are included but disabled by default. Enable them by adding their profile name to the `COMPOSE_PROFILES` variable in your `.env` file (separate multiple profiles with commas). @@ -377,6 +414,20 @@ Available Profiles: ## Troubleshooting +### Middleware Not Found Errors + +If you see error messages like `middleware "authelia-auth@docker" does not exist` in the Traefik logs, it could be due to one of these issues: + +1. **Docker Network Issue:** The Traefik configuration has been updated to fix network discovery issues when running in Tailscale's network namespace. If you're still seeing this error, try restarting the stack with: + ```bash + docker compose down + docker compose up -d + ``` + +2. **Authentication Variable Missing:** Ensure you have properly configured the `AUTH_*` variables in your `.env` file for the services you want to control. If not specified, most services default to requiring authentication. + +3. **Docker Socket Permissions:** Make sure Traefik can access the Docker socket. See the [SELinux Socket Permissions](#selinux-socket-permissions-docker) section below for more details. + ### SELinux Socket Permissions (Docker) If you are running Docker on a host with SELinux enabled (like Fedora, CentOS, RHEL) and services like Traefik, Watchtower, or Autoheal fail with "permission denied" errors when trying to access `/var/run/docker.sock`: diff --git a/docker-compose.yml b/docker-compose.yml index 993798c..c030ac1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,24 @@ services: + middlewares: + # This is a "no-op" service just to hold middleware definitions + image: traefik/whoami:latest + container_name: middlewares + restart: "no" + labels: + # Authentication middleware - used when AUTH_SERVICE=true + - traefik.http.middlewares.auth-required.forwardAuth.address=http://authelia:9091/api/verify?rd=https://${APP_HOSTNAME}/ + - traefik.http.middlewares.auth-required.forwardAuth.trustForwardHeader=true + - traefik.http.middlewares.auth-required.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email + + # No authentication middleware - used when AUTH_SERVICE=false + - traefik.http.middlewares.auth-bypass.headers.customResponseHeaders.X-Auth-Skip=true + + # Map true/false to the actual middleware + - traefik.http.middlewares.true.chain.middlewares=auth-required + - traefik.http.middlewares.false.chain.middlewares=auth-bypass + profiles: + - disabled # This service never actually starts + traefik: image: ghcr.io/traefik/traefik:3.3 container_name: traefik @@ -83,11 +103,13 @@ services: - traefik.enable=true - traefik.http.routers.sonarr.rule=PathPrefix(`/sonarr`) - traefik.http.routers.sonarr.entrypoints=web - - traefik.http.routers.sonarr.middlewares=${AUTH_SONARR:-true} + - traefik.http.routers.sonarr.middlewares=${AUTH_SONARR:-true}@docker - traefik.http.services.sonarr.loadbalancer.server.port=8989 - # Add conditional middlewares - - traefik.http.middlewares.true.chain.middlewares=authelia-auth@docker - - traefik.http.middlewares.false.chain.middlewares= + # Define middleware chains for auth control - these are global definitions + - traefik.http.middlewares.true.forwardAuth.address=http://authelia:9091/api/verify?rd=https://${APP_HOSTNAME}/ + - traefik.http.middlewares.true.forwardAuth.trustForwardHeader=true + - traefik.http.middlewares.true.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email + - traefik.http.middlewares.false.headers.customResponseHeaders.X-Auth-Skip=true - homepage.group=Media - homepage.name=Sonarr - homepage.icon=sonarr.png @@ -116,7 +138,7 @@ services: - traefik.enable=true - traefik.http.routers.radarr.rule=PathPrefix(`/radarr`) - traefik.http.routers.radarr.entrypoints=web - - traefik.http.routers.radarr.middlewares=${AUTH_RADARR:-true} + - traefik.http.routers.radarr.middlewares=${AUTH_RADARR:-true}@docker - traefik.http.services.radarr.loadbalancer.server.port=7878 - homepage.group=Media - homepage.name=Radarr @@ -146,7 +168,7 @@ services: - traefik.enable=true - traefik.http.routers.lidarr.rule=PathPrefix(`/lidarr`) - traefik.http.routers.lidarr.entrypoints=web - - traefik.http.routers.lidarr.middlewares=authelia-auth@docker + - traefik.http.routers.lidarr.middlewares=${AUTH_LIDARR:-true}@docker - traefik.http.services.lidarr.loadbalancer.server.port=8686 - homepage.group=Media - homepage.name=Lidarr @@ -178,7 +200,7 @@ services: - traefik.enable=true - traefik.http.routers.bazarr.rule=PathPrefix(`/bazarr`) - traefik.http.routers.bazarr.entrypoints=web - - traefik.http.routers.bazarr.middlewares=${AUTH_BAZARR:-true} + - traefik.http.routers.bazarr.middlewares=${AUTH_BAZARR:-true}@docker - traefik.http.services.bazarr.loadbalancer.server.port=6767 - homepage.group=Download - homepage.name=Bazarr @@ -214,7 +236,7 @@ services: - traefik.http.routers.jellyseerr.rule=PathPrefix(`/jellyseerr`) - traefik.http.routers.jellyseerr.entrypoints=web - traefik.http.services.jellyseerr.loadbalancer.server.port=5055 - - traefik.http.routers.jellyseerr.middlewares=jellyseerr-stripprefix,jellyseerr-rewrite,jellyseerr-rewriteHeaders,authelia-auth@docker + - traefik.http.routers.jellyseerr.middlewares=jellyseerr-stripprefix,jellyseerr-rewrite,jellyseerr-rewriteHeaders,${AUTH_JELLYSEERR:-true}@docker - traefik.http.middlewares.jellyseerr-stripprefix.stripPrefix.prefixes=/jellyseerr - traefik.http.middlewares.jellyseerr-rewriteHeaders.plugin.rewriteHeaders.rewrites[0].header=location - traefik.http.middlewares.jellyseerr-rewriteHeaders.plugin.rewriteHeaders.rewrites[0].regex=^/(.+)$ @@ -287,7 +309,7 @@ services: - traefik.enable=true - traefik.http.routers.prowlarr.rule=PathPrefix(`/prowlarr`) - traefik.http.routers.prowlarr.entrypoints=web - - traefik.http.routers.prowlarr.middlewares=authelia-auth@docker + - traefik.http.routers.prowlarr.middlewares=${AUTH_PROWLARR:-true}@docker - traefik.http.services.prowlarr.loadbalancer.server.port=9696 - homepage.group=Download - homepage.name=Prowlarr @@ -311,7 +333,7 @@ services: - traefik.enable=true - traefik.http.routers.flaresolverr.rule=PathPrefix(`/flaresolverr`) - traefik.http.routers.flaresolverr.entrypoints=web - - traefik.http.routers.flaresolverr.middlewares=authelia-auth@docker + - traefik.http.routers.flaresolverr.middlewares=${AUTH_FLARESOLVERR:-true}@docker - traefik.http.services.flaresolverr.loadbalancer.server.port=8191 profiles: - flaresolverr @@ -338,7 +360,7 @@ services: - traefik.http.routers.qbittorrent.rule=PathPrefix(`/qbittorrent`) - traefik.http.routers.qbittorrent.entrypoints=web - traefik.http.services.qbittorrent.loadbalancer.server.port=8080 - - traefik.http.routers.qbittorrent.middlewares=qbittorrent-strip-slash,qbittorrent-stripprefix,authelia-auth@docker + - traefik.http.routers.qbittorrent.middlewares=qbittorrent-strip-slash,qbittorrent-stripprefix,${AUTH_QBITTORRENT:-true}@docker - traefik.http.middlewares.qbittorrent-stripprefix.stripPrefix.prefixes=/qbittorrent - traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.regex=(^.*\/qbittorrent$$) - traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.replacement=$$1/ @@ -383,7 +405,7 @@ services: - traefik.enable=true - traefik.http.routers.sabnzbd.rule=PathPrefix(`/sabnzbd`) - traefik.http.routers.sabnzbd.entrypoints=web - - traefik.http.routers.sabnzbd.middlewares=authelia-auth@docker + - traefik.http.routers.sabnzbd.middlewares=${AUTH_SABNZBD:-true}@docker - traefik.http.services.sabnzbd.loadbalancer.server.port=8080 - homepage.group=Download - homepage.name=Sabnzbd @@ -419,7 +441,7 @@ services: - traefik.enable=true - traefik.http.routers.jellyfin.rule=PathPrefix(`/jellyfin`) - traefik.http.routers.jellyfin.entrypoints=web - - traefik.http.routers.jellyfin.middlewares=authelia-auth@docker + - traefik.http.routers.jellyfin.middlewares=${AUTH_JELLYFIN:-false}@docker - traefik.http.services.jellyfin.loadbalancer.server.port=8096 - homepage.group=Media - homepage.name=Jellyfin @@ -448,7 +470,7 @@ services: - traefik.http.middlewares.calibre-headers.headers.customRequestHeaders.X-Scheme=https - traefik.http.middlewares.calibre-headers.headers.customRequestHeaders.X-Script-Name=/calibre - traefik.http.middlewares.calibre-stripprefixregex.stripPrefixRegex.regex=/calibre - - traefik.http.routers.calibre.middlewares=calibre-headers,calibre-stripprefixregex,authelia-auth@docker + - traefik.http.routers.calibre.middlewares=calibre-headers,calibre-stripprefixregex,${AUTH_CALIBRE:-true}@docker - traefik.http.routers.calibre.rule=PathPrefix(`/calibre`) - traefik.http.routers.calibre.entrypoints=web - traefik.http.services.calibre.loadbalancer.server.port=8083 @@ -526,7 +548,7 @@ services: - traefik.http.routers.homepage.entrypoints=web - traefik.http.routers.homepage.priority=10 - traefik.http.middlewares.homepage-stripprefix.stripPrefix.prefixes=/home - - traefik.http.routers.homepage.middlewares=homepage-stripprefix,authelia-auth@docker + - traefik.http.routers.homepage.middlewares=homepage-stripprefix,${AUTH_HOMEPAGE:-true}@docker - homepage.group=Dashboard - homepage.name=Homepage - homepage.icon=homepage.png