Lissy93 / dashy

🚀 A self-hostable personal dashboard built for you. Includes status-checking, widgets, themes, icon packs, a UI editor and tons more!
https://dashy.to
MIT License
16.78k stars 1.28k forks source link

[FEATURE_REQUEST] Add Header Authentication #981

Open FieldofClay opened 1 year ago

FieldofClay commented 1 year ago

Is your feature request related to a problem? If so, please describe.

Currently the only authentication options are Keycloak and inbuilt auth. I use Authelia to manage my SSO, which supports passing user/group info through to the reverse proxied app. There are also other apps out there that can pass in user/group info in a similar way.

Describe the solution you'd like

I see this providing the same functionality as the current Keycloak auth, just grabbing the user/group info from headers passed via the proxy instead of interrogating Keycloak.

appConfig.auth (optional)

Field Type Required Description
enableHeaderAuth boolean Optional If set to true enable Header Authentication. See appConfig.auth.headerAuth.

appConfig.auth.headerAuth (optional)

Field Type Required Description
userHeader string Optional Header name which contains username (default: X-Forwarded-User)
groupHeader string Optional Header name which contains group info (default: X-Forwarded-Groups)
proxyWhitelist array Required Upstream proxy servers to expect authenticated requests from.

From here it could use the already existing hideForUsers and showForUsers config, but given that Keycloak has them split out I'm not sure how feasible it is. Could also extend the displayData options to have hideForGroup and showForGroup for extra configurability.

Priority

Medium (Would be very useful)

Is this something you would be keen to implement

No

liss-bot commented 1 year ago

This issue has gone 6 weeks without an update. To keep the ticket open, please indicate that it is still relevant in a comment below. Otherwise it will be closed in 5 working days.

FieldofClay commented 1 year ago

I believe this is still relevant.

toddejohnson commented 9 months ago

I started to try to hack this in as it is just process.env.REMOTE_USER or similar but the client side nature Vuejs and the Axios is a bit more than I can wrap my head around.

toddejohnson commented 9 months ago

I found your 2.1.2 and it had a good start at the config race condition in main.js that I was running into with implementing headerAuth. I will say I'm not that good at async coding so there might be some big issues. I have it working for my use case which isn't what a maintainer likes to hear.

  1. It is currently hardcoded for my REMOTE-USER mostly because I don't know how to access getAppConfig().headerAuth.userHeader in the nodejs services/get-user.js which is one of the few things I need to make this configurable.
  2. There is no documentation on how to use this. I'm using Authelia but this would work for Apache/NGINX basic/ldap auth too. Even Apache and mod_auth_mellon(SAML) would allow adding a username header.
  3. I'm using the built in users 'database' to get the hash and fake like a normal user instead of the bolt on that is Keycloak with it's own auth level.

I'll try to work on a WIP PR in a bit.

toddejohnson commented 4 months ago

It looks like this missed 2.1.2. Should I try again? Lots of auth/permissions config could use server side help to limit information leak.

Lissy93 commented 4 months ago

It looks like this missed 2.1.2. Should I try again? Lots of auth/permissions config could use server side help to limit information leak.

Sorry @toddejohnson, this slipped my mind. I'll look into this this week, and see if we can get something working :)


I started to try to hack this in as it is just process.env.REMOTE_USER or similar but the client side nature Vuejs and the Axios is a bit more than I can wrap my head around.

Yeah, it's annoying. If you need to read an env var client-side, prefix it with VUE_APP_ and then it can be read in Vue.

rxunique commented 4 months ago

Edit: Got Authentik proxy working with Dashy 3.0 behind traefik with config below

   users:
      - user: user1
        hash: removed
        type: admin
      - user: user2
        hash: removed
   enableHeaderAuth: true
   headerAuth:
      userHeader: X-Authentik-Username  # the tag containing username according whoami
      proxyWhitelist:
        - "treafik.docker.network.ip"  # 
rxunique commented 4 months ago

Edit: found root cause of reload loop

proxyWhitelist has to have traefik docker network ip since my dashy is on the same network

Otherwise response.data.user.toLowerCase() undefined in HeaderAuth.js causing .catch(() => window.location.reload()); to infinite loop

Food for thought @Lissy93

  1. Maybe replace .catch(() => window.location.reload()); at the end of main.js with CoolConsole, at least won't be a reload loop

  2. proxyWhitelist seems to be cached until container restart or there might be some other cache, which made it seem like intermittent issue, maybe add some info to documentation.

  3. perhaps make proxyWhitelist check against "172.16.0.0/12" notation rather than single IP in array?

  4. up on saving to conf.yml via GUI, all previous manual #comment are deleted, it'd be nice if they can be kept

One more thing, Dashy's log out doesn't really work anymore with headerAuth via Authentik proxy. I don't think there is a good solution for this one. As long as signed into Authentik, the username header will always be there

Lissy93 commented 4 months ago

@rxunique - You're absolutely correct, shouldn't be triggering a page reload there (not sure why I put that?!). I've updated.

I'm just looking into the header auth feature at the moment, and if I can get something working this evening, will merge as part of 3.0.1. But I'm struggling to understand what exactly needs implementing in Dashy to get this working. I've not really used Authelia, quickly tested it out with NGINX, and it seemed to work alright.

So any tips on what specifically isn't working or what needs to be added/amended would be appreciated :)

rxunique commented 4 months ago

Oh pretty much nothing you need to implement, existing code works, documentation would be nice. It was quite head against the wall trying to figure it out, but with benefit of hindsight, it's so obvious.

The flow is like this

That's it, if authentik username (or alternative fields) matches dashy users, you are in with per user authentication.

The difficulty I ran into, which may be improved on a back burner

In my case, since all behind Traefik on the same docker network, it was my Traefik container ip 172.x.x.x.

Because I restarted different containers while trying to figure it out, their IP changed which made it quite a guess work with proxyWhitelist, now I've got key containers on static ip.

A generic OIDC would be superior than authetik proxy auth + dashy headerauth, but it works now.

rxunique commented 4 months ago

Hey, just another idea to deal with whitelist.

Currently matching by string is just one line of code, matching by range would need quite a few extra line of code but still need some guess work.

Different people setup docker network and proxy differently, so proxy IP as appeared to dashy will always be different.

What about add another match condition like (proxySecret === X-Dashy-ProxySecret) || proxyWhitelist.includes() , its very easy to manually add a header 'X-Dashy-ProxySecret = MySecretHash' with proxy, certainly with treafik. That way no guess work with

toddejohnson commented 4 months ago

The idea is to list the trusted IP of the reverse proxy that is trusted. If a request from another IP was made saying it was someone else logged in it shouldn't be trusted.