Skip to content

Latest commit

 

History

History
188 lines (132 loc) · 9.26 KB

File metadata and controls

188 lines (132 loc) · 9.26 KB

Gateway Auth Gate

Muximux can optionally act as a security gate in front of your gateway sites. When enabled per-site, requests to that subdomain run through Muximux's session check before reaching the backend. Anonymous visitors are redirected to Muximux's login page; authenticated visitors are forwarded based on their role and group membership.

The gate sits in front of the backend - it does not replace the backend's own authentication if it has one. It's a layered defence: log in to Muximux once, gain access to every gated subdomain you have permission for.

Browser -> sonarr.example.com (Caddy)
             |
             v
           forward_auth /api/auth/forward (Muximux Go server)
             |
             | session cookie valid + permitted ?
             |       no  -> 302 to /login -> back to original URL after sign-in
             |       yes -> 200 with X-Muximux-User / X-Muximux-Role headers
             v
           reverse_proxy http://192.168.1.5:8989  (the backend)

When to enable

✅ You want a single sign-on layer in front of multiple homelab apps ✅ One or more apps lack their own auth (Plex, Grafana, custom Flask app) ✅ You're already using Muximux as your dashboard with built-in / OIDC / forward-auth users ✅ All gated subdomains share a parent domain (*.example.com)

❌ You're hosting public-facing sites that need to be reachable anonymously ❌ Each backend already has battle-tested SSO you want to keep authoritative ❌ Your gated subdomains span different parent domains (app.alpha.com + app.beta.org)


Topology requirement

The gate uses Muximux's session cookie as the auth source. For the cookie to be visible at every gated subdomain, you need:

  1. Muximux's dashboard and every gated site to share a parent domain. Example: muximux.example.com, sonarr.example.com, grafana.example.com all share .example.com.
  2. A consistent transport. Either all HTTPS (recommended) or all plain HTTP for local-only setups - browsers reject mixed-Secure cookies across the same domain.
  3. server.session_cookie_domain configured so Muximux issues the cookie with the right scope.

The validator at config load enforces this. If a site has require_auth: true but isn't a subdomain of session_cookie_domain, Muximux exits at startup with a specific error pointing at the offending site.


Setup

1. Set the cookie scope

In config.yaml:

server:
  session_cookie_domain: ".example.com"   # or "example.com", browser normalises
  tls:
    domain: "muximux.example.com"

The leading dot is the browser-canonical form but optional - browsers treat both equally.

2. Enable Require Auth per site

In Settings → Gateway, edit a site and tick Require Muximux login:

server:
  gateway_sites:
    - domain: sonarr.example.com
      backend_url: http://192.168.1.5:8989
      tls: auto
      require_auth: true          # gate this site
      min_role: user              # optional; "user" / "power-user" / "admin"
      allowed_groups:             # optional; case-insensitive; admins bypass
        - family
        - admins

3. Reload

Restart Muximux (or, for an in-flight gateway-site mutation, the Gateway tab's save does this for you). Caddy's reload picks up the new forward_auth directive on every gated site.


Permission model

Identical to the App access rules used by the dashboard:

Setting Meaning
require_auth: false No gate; anonymous traffic reaches the backend.
require_auth: true, no min_role, no allowed_groups Any authenticated Muximux user can reach the backend.
require_auth: true, min_role: power-user Users at power-user level or higher pass. Admins always pass.
require_auth: true, allowed_groups: [family] Users in the family group pass. Admins bypass the group check.
require_auth: true, both set User must satisfy both: meet the role bar AND be in at least one allowed group. Admins still bypass both.

Group matching is case-insensitive (Family and family are the same group).


Behaviour by response

When Caddy calls /api/auth/forward for a gated site:

Muximux returns Caddy serves User sees
200 OK + X-Muximux-User + X-Muximux-Role Reverse-proxies to the backend with those headers attached The app, normally
302 Found/login?next=<url> Redirects Muximux login page; after sign-in, lands back at the original URL
403 Forbidden Returns body verbatim Small "you're signed in but lack permission" page with a sign-out link
500 / 503 Returns body "gateway misconfigured" message (rare; surfaces operator config errors)

Backends receive X-Muximux-User and X-Muximux-Role headers on every forwarded request. Apps that honour these (rare but increasing) can use them as an external-auth source.


Sign-out

/logout on Muximux invalidates the session cookie. Because the cookie is scoped to .example.com, this clears it for every gated subdomain at once. Next request to any gated site triggers the login redirect again.

There is intentionally no per-site logout: the gate is a centralised SSO layer, and "log out of one app but not the others" doesn't compose cleanly with that model. If your backend has its own logout, that's separate - the gate doesn't propagate.


Hardening at the gate endpoint

A couple of defence-in-depth measures sit on the gate endpoint:

  • Only reachable from inside Muximux. /api/auth/forward is the receive side of Caddy's forward_auth call, so it only ever gets reached over the loopback interface during normal operation. The endpoint refuses non-loopback callers, which means a public visitor can't probe session validity or gateway-site configuration by hitting it directly.
  • Login-redirect target is locked to your configured sites. When the gate sends an anonymous visitor to /login, the post-login bounce-back URL is built from the configured gateway_sites domain rather than a header value the caller controls. Off-site redirect attacks via header injection are prevented by construction.
  • Audit log on every decision. Allows, denials, and permission-denied responses each leave an audit-log line with the host and the session's user/role.

How it interacts with the other auth modes

Muximux auth method Gate behaviour
auth.method: builtin Native users; session check is straightforward.
auth.method: oidc Sessions backed by OIDC tokens; gate uses the same session check. Operators get OIDC users surfaced to gated apps via X-Muximux-User.
auth.method: forwardauth (Authelia / Authentik) Muximux trusts an upstream Remote-User header to create a session, then the gate reads that session. Daisy-chained forward-auth works but adds a network hop; consider whether the upstream is already gating the same set of apps.

Troubleshooting

Symptom Likely cause
Browser loops between gated site and login Cookie isn't reaching the gated host. Verify session_cookie_domain covers both. Check the browser's cookie inspector.
503 "configure server.tls.domain" on the gate The gate doesn't know where to send the login redirect. Set server.tls.domain to your dashboard host.
Startup error "session_cookie_domain is required when any gateway site has require_auth=true" You enabled require_auth without setting the cookie scope. Either set session_cookie_domain or unset the auth flag.
Startup error "gateway site X is not under server.session_cookie_domain" A gated subdomain doesn't share the parent domain. Move the site or widen the cookie scope.
403 on a gated site you should have access to Your account's role or groups don't satisfy the site's rules. The forbidden page names the reason (role_insufficient or group_mismatch).
Backend doesn't recognise X-Muximux-User Most backends ignore the header. The gate still enforces; the header is purely informational for backends that opt in.
WebSocket connection drops the gate Caddy's forward_auth runs on the initial HTTP Upgrade request; once the WebSocket is up, the connection bypasses forward_auth (standard Caddy behaviour).

Configuration reference

server:
  # Required when any site below has require_auth=true.
  # The leading dot is optional; ".example.com" and "example.com" are equivalent.
  session_cookie_domain: ".example.com"

  gateway_sites:
    - domain: sonarr.example.com
      backend_url: http://192.168.1.5:8989
      tls: auto
      require_auth: true             # default false
      min_role: user                 # default ""; "user" / "power-user" / "admin"
      allowed_groups: [family]       # default []; case-insensitive; admins bypass

Audit log

Every forward-auth result is recorded with source=audit:

forward-auth allowed source=audit host=sonarr.example.com user=alice role=user
forward-auth denied  source=audit host=sonarr.example.com user="" result=no_session
forward-auth denied  source=audit host=admin.example.com user=alice role=user result=role_insufficient
forward-auth denied  source=audit host=family.example.com user=bob role=user result=group_mismatch

Use this for both observability and post-incident review. Failed access attempts are first-class events, not just operational noise.