janeczku / calibre-web

:books: Web app for browsing, reading and downloading eBooks stored in a Calibre database
GNU General Public License v3.0
13.07k stars 1.39k forks source link

Improvements for Reverse Proxy Authentication #2623

Closed muellerj closed 3 months ago

muellerj commented 1 year ago

Describe the bug/problem When using the specified header to log in to calibre-web, the resulting session does not always reflect the actual state of the logged in user. In particular, if the header changes (the user on this machine has changed), the session still remains with the old one.

To Reproduce Steps to reproduce the behavior:

  1. Log in as user Adam via reverse proxy authentication. Reverse proxy correctly sets X-Auth-User: Adam and calibre-web logs him in.
  2. Do stuff, then only log out from reverse proxy auth mechanism (Authelia etc.), not from calibre-web.
  3. Log in as user Betty via reverse proxy authentication. Reverse proxy correctly sets X-Auth-User: Betty but calibre-web still finds the session belonging to Adam and proceeds with with Adam logged in. Betty can now impersonate Adam.
  4. If Betty is nice, she clicks on "Logout" in calibre-web, which clears Adams session and (because X-Auth-User: Betty is present on the subsequent request) she gets logged in as herself.

Expected behavior For every request inside a session:

In the presence of a reverse proxy handling the authentication, the "Logout" link has no user-facing functionality - It could instead link to a user-defined logout page, which logs him/her out of the reverse proxy auth mechanism. This could live next to the other "Reverse Proxy" settings:

Screenshot 2022-12-14 at 08 12 44

Let me know what you think - if you give me a pointer where to start I can take a stab at an implementation.

nchietala commented 1 year ago

I was able to resolve this by rewriting the redirect in my nginx configuration:

# Calibre-web ebooks and audiobooks
server {
  server_name books.example.com;

  include /etc/nginx/sites-available/authelia_mixin.conf;
  location / {
    proxy_pass http://books;
    include /etc/nginx/sites-available/authelia_location_mixin.conf;

    location /upload {
      client_max_body_size 10000M;
    }

    location /logout {
      proxy_pass http://books/logout;
      proxy_hide_header Location;
      add_header Location https://auth.example.com/logout;
    }
  }

  listen 443 ssl; # managed by Certbot
  # ssl stuff redacted
}

Alternatively you could exempt the /login page from your reverse proxy authentication so it doesn't get the header. That way you could also log in with alternative users without signing out of your reverse proxy.

scrapix commented 1 year ago

@nchietala thank you for this inspiration!

I'm using traefik as reverse proxy and I'm struggling a bit to mimic this behaviour as there seem to be no "proxy_pass" equivalent... Does anyone know how we could make this work for traefik? The redirection to authelia logout page is a no-brainer. But when doing so the user does not get logged out in calibreweb...

Here is what I can share for redirection via traefik to authelia logout

http:
  routers:
    calibre:
      service: calibre-web-calibre@docker
      rule: "Host(`library.domain`)"
      tls:
        certResolver: "yourCertResolver"
        domains:
          - main: "domain"
          - sans: "library.domain"
      middlewares:
        - authelia
        - calibre-logout

middlewares:
    calibre-logout:
      redirectRegex:
        regex: "https://library.domain/logout"
        replacement: "https://auth.domain/logout?rd=https%3A%2F%2Flibrary.domain%2F&rm=GET"
nchietala commented 1 year ago

@scrapix

middlewares:
    calibre-logout:
      redirectRegex:
        regex: "https://library.domain/login"
        replacement: "https://auth.domain/logout?rd=https%3A%2F%2Flibrary.domain%2F&rm=GET"

You've rewritten the redirect to the logout page, so the user agent never actually hits the logout page and gets straight to authelia. Instead rewrite the redirect to the login page so that it sends the user agent to logout of authelia after they've logged out of calibre-web.

It may be wise to add some logic to only do such a rewrite when the user url is the logout page but I don't know how to do that with traefik.

scrapix commented 1 year ago

@nchietala your suggestion worked like a charm. thank you! For everyone looking for a solution with traefik and authelia, here it is:

http:
  routers:
    calibre:
      service: calibre-web-calibre@docker
      rule: "Host(`library.domain`)"
      tls:
        certResolver: "yourCertResolver"
        domains:
          - main: "domain"
          - sans: "library.domain"
      middlewares:
        - authelia
        - calibre-logout

middlewares:
    calibre-logout:
      redirectRegex:
        regex: "https://library.domain/login"
        replacement: "https://auth.domain/logout?rd=https%3A%2F%2Flibrary.domain%2F&rm=GET"
mcmaxl commented 8 months ago

@scrapix

middlewares:
    calibre-logout:
      redirectRegex:
        regex: "https://library.domain/login"
        replacement: "https://auth.domain/logout?rd=https%3A%2F%2Flibrary.domain%2F&rm=GET"

You've rewritten the redirect to the logout page, so the user agent never actually hits the logout page and gets straight to authelia. Instead rewrite the redirect to the login page so that it sends the user agent to logout of authelia after they've logged out of calibre-web.

It may be wise to add some logic to only do such a rewrite when the user url is the logout page but I don't know how to do that with traefik.

I do get redirected but since I am still logged in to authelia I end up getting logged in again to Calibre as well.

I have configured Traefik and Authelia via docker compose labels:

      - "traefik.enable=true"
      # HTTP Routers
      - "traefik.http.routers.calibre-rtr.entrypoints=websecure"
      - "traefik.http.routers.calibre-rtr.rule=Host(`calibre.$DOMAINNAME`)"
      # Middlewares
      - "traefik.http.routers.calibre-rtr.middlewares=chain-authelia@file"
      # Middleware for logout rerouting to Authelia
     # - "traefik.http.middlewares.calibre-logout.redirectregex.regex=^https://calibre.$DOMAINNAME/login"
     # - "traefik.http.middlewares.calibre-logout.redirectregex.replacement=https://authelia.$DOMAINNAME/logout?rd=https%3A%2F%2Fcalibre.$DOMAINNAME%2F&rm=GET"
      ## HTTP Services
      - "traefik.http.routers.calibre-rtr.service=calibre-svc"
      - "traefik.http.services.calibre-svc.loadbalancer.server.port=8083"

and this is the authelia middleware:

http:
  middlewares:
    middlewares-authelia:
      forwardAuth:
        address: "http://authelia:9091/api/verify?rd=https://authelia.{{env "DOMAINNAME"}}"
        trustForwardHeader: true
        authResponseHeaders:
          - "Remote-User"
          - "Remote-Groups"

any ideas?

nchietala commented 7 months ago

Looks right to me (apart from the fact that you've commented out the labels that would configure the middleware). Are you sure you aren't having any session persistence issues where the logout url in authelia doesn't actually log you out, or maybe your web browser is caching too aggressively, or is storing credentials and automatically logging you back in?

Edit: also docker-traefik is sometimes finicky, and I've had to restart the traefik container on occasion to make it pick up new labels from other containers.

OzzieIsaacs commented 3 months ago

It should now work without additonal configs (no cookie is saved on the client anymore and therefore no "accidentally" login is possible