Closed rcludwick closed 2 years ago
sure, this seems reasonable and has been requested before:
I'm not sure how to implement this - have you any experience on configuring django in such a fashion?
We'd also need to clearly document this :)
Yes. (Sadly)
So yeah... in 2021 it's fugly. It should be simple, but it's not. There are companies making billions of dollars on this.
So what I'm suggesting is probably the simplest case possible for inventree.
Authentication is either handled by django's login page, or via the X-Forwarded-User header. Then if you want something more complex than the simple login page, it's up to user to implement it and make sure the logged in user is put into the X-Forwarded-User header (or whatever header is configured above).
Fortunately Django has support for this built in though the RemoteUserMiddleware and RemoteUserBackend classes. YAY!
This is worth a careful read through.
https://docs.djangoproject.com/en/3.2/howto/auth-remote-user/
@rcludwick is this feature something you'd be willing to spend some time on? I'm happy to support it but this is really not my area of expertise!
I'll look at it in my spare time. It's mostly "configuration" thankfully.
@rcludwick did you find time to do this and would you be willing to share your findings for the docs?
No, I didn't get time to do this.
Having dealt with auth before, it comes down to two different ways to do authentication in Django.
First is the middleware. This looks at the request coming in, figures out who the user is and then attaches the user to request.user and passes the request on. This is the way Django web login works.
The second is through authentication and authorization classes on DRF for the apis. This is usually through an http header.
In my case I have inventree sitting behind traefik-forward-auth which handles the auth for me, and can add a header "proxy-forwarded-for" which holds the name/email of the authenticated user.
So all requests from a browser have a cookie from traefik-forward-auth which it intercepts and validates. So any requests that make it through to inventree should be considered authenticated.
One reason to do this is that I don't have to expose the inventree login pages to the internet directly, but instead are redirected to google's oauth2 service.
So in my configuration I need a middleware in Django that handles the "proxy-forwarded-for" header properly. This is the part I was going to look at.
Many companies will have some kind of custom auth proxy in this configuration that spits out a cookie or a header that Django can look at. That should probably suffice for a good chunk of people.
So all requests that come from a browser will have a cookie attached to it that provides auth. You can either use the django login system or roll your own that provides a header for inventree.
Great! Now what about the API?
So this gets more complex, because much of this doesn't necessarily involve a human in the loop, nor a web browser capable of storing and forwarding cookies.
But on the other hand, DRF can use the default Django session cookies to auth web browser api calls.
Yet you can also specify custom authentication and authorization classes in a DRF view that are different than middleware auth.
Some people can put their API behind a proxy that will handle auth and provide throttling and metrics. Others may just not care and expect everyone to use a web browser when using the API.
In my case traefik-forward-auth won't authenticate machine traffic, because I have to login through Google via a web page. Any apis I want to use with a machine client will need to be whitelisted with traefik-forward-auth, and then some kind of authorization header needs to be used and handled by inventree directly.
Often people use JWTs (python-jose e.g.) that are generated by another server. but this also could be done with a random string generated on inventree passed to the machine client by the user.
So the key takeaway is that auth needs to be configurable. And API auth could be different than the browser.
You don't have to go crazy, but just provide some simple schemes for the majority of use cases, and then if some company wants you to integrate with their homegrown auth server, then charge them $200/hr.
On Thu, Aug 19, 2021, 3:56 PM Matthias Mair @.***> wrote:
@rcludwick https://github.com/rcludwick did you find time to do this and would you be willing to share your findings for the docs?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/inventree/InvenTree/issues/1693#issuecomment-902276071, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAL67I6ULUY3KWY3ZN3QC33T5V42BANCNFSM47APK6ZA . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&utm_campaign=notification-email .
@rcludwick thanks for the input, I will try to implement something if I find time. Your input would be appreciated if you find time 👼.
In summary, you just need two things.
A way to authenticate browser requests in middleware using a http header (proxy_forwarded_for=user_or_email).
And... Some kind of token auth for API machine access, this could be a token in the db with a foreign key back to the user. Also default permissions for unauthed API users should be configurable. Many people might want read only, others might want a 403.
Authlib is a good library to look at, particularly their Django implementations for tokens .
This stuff is a 🐇🕳️. I would avoid anything more complex than the simplest API auth mechanism for now which is really just a shared secret. Do jwt later until after you get a pluggable auth layer built.
On Fri, Aug 20, 2021, 2:22 PM Matthias Mair @.***> wrote:
@rcludwick https://github.com/rcludwick thanks for the input, I will try to implement something if I find time. Your input would be appreciated if you find time 👼.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/inventree/InvenTree/issues/1693#issuecomment-902936473, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAL67I5DPDEYLGP7BUQOYCTT522OVANCNFSM47APK6ZA . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&utm_campaign=notification-email .
This is similar to #625 but more specific about how it could be implemented.
I played around with this because a client needed it. It is very easy to do with the authentication backend and middleware settings in the config.yaml. It was ~40 LoC but really depenendet on how the infra is built out. Could be very dangerous if the admin setting it up does not know their stuff. @SchrodingersGat should we still implement a solution for this? Maybe a configurable header that is used as a user reference?
I'm happy to provide a reference example but not implement it by default. A clear warning should be added to the documentation that "all care should be taken" but the implementing admin.
So what should I sketch out? A simple proxy set auth-header, LDAP or something else? Google etc. are already implemented in the SSO package.
@matmair I'm happy for you to make a suggestion based on what you think is sensible. This is straying very far from my area of knowledge.
My key requirements would be:
@matmair happy to bump this one to 0.7.0?
@rcludwick are you still interested in this?
@matmair are you still keen to implement?
@SchrodingersGat yes. I planned with a longer release window and wanted to do it in the Easter break
@rcludwick if you have time feel free to look at the PR
I'm having some trouble getting the proxy auth working and I was wondering if I'm missing a setting somewhere. I'm using traefik + traefik-forward-auth for SSO, which passes the X-Forwarded-User header to an nginx container (for serving static files) running in front of inventree.
I have
remote_login_enabled: True
remote_login_header: X-Forwarded-User
in my inventree.yml
file, and the nginx config was copied from here. I've also tried the INVENTREE_REMOTE_LOGIN=true
and INVENTREE_REMOTE_LOGIN_HEADER=X-Forwarded-User
, but in both cases I keep getting redirected to the login screen.
Do I need to add to the authentication_backends
and middlewares
in the configuration yaml as well, or is there another configuration setting I'm missing?
I have traefik running as a reverse proxy in front of inventree. The reverse proxy handles the AUTHENTICATION (and maybe AUTHORIZATION) before handing it off to any server running behind it.
In this case I have
https://inventree.{mydomain}.com
pointing to the reverse proxy. The reverse proxy authenticates with google, and then sets a cookie for auth. The proxy then checks the presence of a valid auth cookie before handing the traffic off to inventree running in a docker container. (This is all done with traefik, btw)In this scenario I have two login pages. First for the google login page, and then the second for the inventree login page.
So the reverse proxy will add a header to the request, in my case "X-Forwarded-User: me@gmail.com".
It would be nice to have something along the lines in the config:
INVENTREE_REVERSE_PROXY_LOGIN=True INVENTREE_REVERSE_PROXY_HEADER="X-Forwarded-User"
This would allow easier login for people/corporations that already have a reverse proxy system setup using LDAP/SSO/etc. And Inventree wouldn't have to deal with complex auth mechanisms directly at all.
Lastly Inventree would need something like this:
INVENTREE_REVERSE_PROXY_AUTO_CREATE_USER=True/False
If True, an unknown user would be granted access (because both AUTHENTICATION of the user and AUTHORIZATION of the user accessing Inventree have been handled elsewhere) and Inventree should just add the new user into the User's table.
If False, AUTHENTICATION is handled by the reverse proxy, but AUTHORIZATION of the user accessing Inventree is controlled by the Django User table. If a user exists in table and is_active=True then the user is granted access. Otherwise the user gets a 403 Forbidden Page.
This way you can do fine grained control of user AUTHORIZATION in Inventree, yet still rely upon external AUTHENTICATION to validate who the person is.