-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make auth system more flexible & convenient and add "callbacks" #1032
Make auth system more flexible & convenient and add "callbacks" #1032
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
79a0834
to
536b7fa
Compare
536b7fa
to
f6c0edd
Compare
f6c0edd
to
5725b2c
Compare
5725b2c
to
a72c512
Compare
{auth/login}-callback
auth modes
The caching of the callback results per node needs to be revised with a distributed setup. This can have funny side effects if the cached data is inconsistent. |
Fair point. However, I would note that usually, your load balancer would always forward the same user (as identified by IP address or whatever) to the same node. And more importantly: with this kind of caching, that can hold stale data, there can always be "funny" side effects. Making this caching shared between all Tobira nodes basically defeats the purpose, i.e. performance, if the cache. It also adds more complexity. If this caching is not acceptable in your situation, it should be disabled in Tobira. And you can instead have your own cache in the auth callback, for example. All I'm saying is: I had this in mind when deciding for an in-memory/in-process cache. Thanks for taking a look at this PR! |
But I suppose adding a note to the docs about this is a good idea 🤔 |
Sticky sessions should be avoided if possible; they have some drawbacks. If both nodes have the same stale data, that's not so problematic, but if one node allows you to edit pages and the other doesn't, it results in weird behavior with a bad user experience. |
Ah, I didn't know.
Certainly. So as I said, then I would disable the Tobira cache to avoid these problems. I don't think it's a good idea to adjust the Tobira cache to be shared between nodes, as it adds complexity and defeats the purpose of the cache as it's likely not much faster than just calling the callback. If you use a multi-node setup and your callback performs expensive operations, then the callback should cache those operations itself. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't looked at the code, yet, but went through the docs rather thoroughly. Overall they are very nice docs. I still added quite a few more or less nitpicky comments. Specifically the "writing advice" ones are probably debatable. I stand by them, but none of them need to block this PR, I guess. 🤷♀️
@@ -66,25 +99,22 @@ server { | |||
If you use Tobira's login page, you have to properly intercept and react to the `POST /~login` requests sent by it. | |||
Alternatively, you can use your own login page of course. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe cross-link to the relevant section about intercepting those?
This comment was marked as resolved.
This comment was marked as resolved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I wanted to review this initially, mostly because I was also slightly involved in the conceptual design of this. Unfortunately I don't really have the time at the moment to do a proper in-depth code review. 😕
I did let myself do a rather thorough read of the docs, giving the whole design a final sanity check in the process. After that review (see above) I also got to do some basic testing with a local Opencast. Everything seemed to be working as expected.
I also skimmed large chunks of the code, but not everything. FWIW I didn't find any glaring mistakes doing that. The few small nitpicks I had might also just be missing some minor detail, so I don't think they'd add any value here. 🤷♀️
Just moving code around.
This serves two purposes, one of which is already implemented in this commit: - If none of these headers is set in the incoming request, Tobira won't contact the callback, which saves time and resources. - Later, the cache will use these headers as key. We need some kind of key for the cache and using all headers won't work as there are too many common headers that permanently change, rendering the cache completely useless.
This turned out to be a lot more complicated than anticipated. But even though there are lots of `clone`s scattered around, I'm pretty sure this is still faster than sending an HTTP request, even to localhost.
This commit changes how most things related to user auth are configured. `mode` is replaced by three values that allow more fine-grained control: - `source`: controls how incoming requests are authenticated. - `session.from_login_credentials`: configures how built-in sessions are created from login attempts. - `session.from_session_endpoint`: configures how `/~session` works. Having the admin control each part separately instead of having a fixed list of modes has a lot more flexibility. And I believe this is also easier to understand and easier to write docs for. I will see about that in the coming commits.
The original idea behind these were that some service, like Shibboleth, already sets the correct data in some other header, so you can just configure Tobira to check a different header. However, our auth headers by now have quite some requirements (like being base64 encoded) so that it's incredibly unlikely that the exact correct data is already in a header set by another application. I'm positive that all users of Tobira using `auth-proxy` will need to have their own code logic already, so there is no reason why they can't use specific names. I also have not seen any installation out there that actually changes these header names.
This was a big undertaking. While there is still lots of complexity and this is certainly not "simple", I think the new docs and new system make more sense. There is still one example missing, and some adjustments will be made in later commits.
Once the callback needs to read cookies, specifying `cookie` as relevant header is likely not great. Usually there are tons of cookies a browser sends unfortunately. And it's not unlikely for cookies to change with every request, which would destroy caching. So for the special cookie header, we have this extra config.
We have the requirement that each user has a single unique user role. Putting that in the API explicitly seems like a good idea.
This leaves the non-trivial ones or those I disagree with.
I helped Waldemar a bit setting this up for the ETH today, which made us realize that my idea was not quite working due to logouts.
Before, the request to `logout_link` was sent, but cancelled once the `DELETE` request returned, due to the `window.location.href = "/"`. But we don't actually want to redirect to `/` if the logout link is set. That poses a new problem: making sure our `DELETE` request is actually getting through. Luckily, that's easy with `keepalive`. One could reduce the amount of code by just using `logout_link ?? "/"`, but I think the current behavior is good: - If logout_link is not set, a spinner is shown until the DELETE request returned, then the user is redirect. - If it is set, the user immediately is sent there, with the DELETE request being send in the background.
Couldn't find any other instances of that in a quick search, but a lint would be nice...
1736e95
to
5f6b4db
Compare
So, then I think this is good to merge! Multiple people had a look at this and this is deployed on an ETH test system for a while now, working as expected. |
This PR changes Tobira's auth system to be more flexible and hopefully easier to understand, rewriting the auth docs in the process. It also adds a new "callback" feature where Tobira can ask an HTTP endpoint for auth information, instead of only getting them from "auth headers".
Callbacks
The callback solution is preferred over auth headers now as HTTP headers are usually limited in size, which is a problem for users with many roles. It should also simplify setting up custom auth logic a lot. Further, Tobira only calls the callback when necessary, preventing auth logic to run uselessly, and allows you to remove special cases like
/~assets
from your reverse proxy config. Finally, Tobira can cache callback responses for some time to make everything even faster.There are login callbacks and auth callbacks. The former gets login credentials, the latter gets a forwarded HTTP request with headers. Both need to return a JSON describing the authorization outcome. See the docs for the details.
Auth system changes
These are some breaking changes in term of configuration file, but all previous "modes" are still supported. You just have to change your configuration slightly. See "Migration" section.
Previously we had different "modes" in which Tobira could operate. These modes would determine what Tobira would do in a small number of different cases (e.g. when receiving login data, or an incoming request, ...). In a previous version, this PR added two more modes. But due to additional requirements, we rethought everything and figured that we can let admins configure the behavior in those cases individually. That way, they can mix & match.
So instead of the
auth.mode
parameter, we haveauth.source
parameter which controls what Tobira does for any normal incoming request that needs authorization. Possible values arenone
,tobira-session
,trust-auth-headers
andcallback:...
. When usingtobira-session
, two additional behaviors can be configured, which control how Tobira's sessions are created:auth.session.from_login_credentials
andauth.session.from_session_endpoint
.With this change, a lot more systems can be configured. Like using Tobira session with Shibboleth.
Migration
(this will be copied to the changelog of the next release)
You currently have
auth.mode = ...
"opencast"
"login-proxy"
"full-auth-proxy"
Other changes
Some other auth-related changes are part of this PR. Some other breaking config changes were included:
auth.roles
.How to review
Good question.
The commits are atomic and you should certainly read the commit messages. Reviewing commit by commit could be done: to understand the history and skip some of the very refactor/moving code around commits. But on the other hand, most of the code of the first commits has been moved and rewritten later again.
It might make sense to read all the rendered auth docs first. And then look at the changed files in the
auth
module in their current form, and look at the remaining changes in diff view?Things that can be done in follow up PRs:
{ "outcome": "redirect", "location": "..." }
and Tobira then answers with 302?ROLE_ANONYMOUS
andROLE_USER
to users.Closes #666