Skip to content
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

Merged
merged 21 commits into from
Feb 22, 2024

Conversation

LukasKalbertodt
Copy link
Member

@LukasKalbertodt LukasKalbertodt commented Dec 14, 2023

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 have auth.source parameter which controls what Tobira does for any normal incoming request that needs authorization. Possible values are none, tobira-session, trust-auth-headers and callback:.... When using tobira-session, two additional behaviors can be configured, which control how Tobira's sessions are created: auth.session.from_login_credentials and auth.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"

[auth]
source = "tobira-session"
session.from_login_credentials = "opencast"

"login-proxy"

[auth]
source = "tobira-session"
session.from_session_endpoint = "trust-auth-headers"

"full-auth-proxy"

[auth]
source = "trust-auth-headers"

Other changes

Some other auth-related changes are part of this PR. Some other breaking config changes were included:

  • All role related config was moved into the section auth.roles.
  • The configs to change the auth header names was removed.

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:

  • Does the callback need the ability to redirect the user? I.e. reply { "outcome": "redirect", "location": "..." } and Tobira then answers with 302?
  • Make callbacks work with unix sockets
  • Add ability to update user data of a Tobira session regularly somehow
  • Always add ROLE_ANONYMOUS and ROLE_USER to users.
  • Maybe add some useful features to authkit? Though I really don't think it's necessary. At most we can add a type declaration for the output type.

Closes #666

@github-actions github-actions bot temporarily deployed to test-deployment-pr1032 December 14, 2023 13:11 Destroyed
@LukasKalbertodt LukasKalbertodt added the changelog:admin Changes primarily for admins label Dec 19, 2023
@github-actions github-actions bot added the status:conflicts This PR has conflicts that need to be resolved label Dec 21, 2023

This comment was marked as resolved.

@github-actions github-actions bot removed the status:conflicts This PR has conflicts that need to be resolved label Jan 11, 2024
@github-actions github-actions bot temporarily deployed to test-deployment-pr1032 January 11, 2024 17:10 Destroyed
@LukasKalbertodt LukasKalbertodt self-assigned this Jan 25, 2024
@LukasKalbertodt LukasKalbertodt marked this pull request as ready for review February 1, 2024 12:55
@github-actions github-actions bot temporarily deployed to test-deployment-pr1032 February 1, 2024 13:06 Destroyed
@github-actions github-actions bot temporarily deployed to test-deployment-pr1032 February 1, 2024 13:31 Destroyed
@LukasKalbertodt LukasKalbertodt changed the title Add experimental {auth/login}-callback auth modes Make auth system more flexible & convenient and add "callbacks" Feb 5, 2024
@geichelberger
Copy link
Contributor

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.

@LukasKalbertodt
Copy link
Member Author

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!

@LukasKalbertodt
Copy link
Member Author

But I suppose adding a note to the docs about this is a good idea 🤔

@geichelberger
Copy link
Contributor

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.

@LukasKalbertodt
Copy link
Member Author

Sticky sessions should be avoided if possible; they have some drawbacks.

Ah, I didn't know.

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.

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.

Copy link
Member

@JulianKniephoff JulianKniephoff left a 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. 🤷‍♀️

util/dev-config/config.toml Outdated Show resolved Hide resolved
docs/docs/setup/auth/index.md Show resolved Hide resolved
docs/docs/setup/auth/user/index.md Outdated Show resolved Hide resolved
docs/docs/setup/auth/user/index.md Outdated Show resolved Hide resolved
docs/docs/setup/auth/user/index.md Outdated Show resolved Hide resolved
docs/docs/setup/auth/user/callback.md Outdated Show resolved Hide resolved
docs/docs/setup/auth/user/callback.md Show resolved Hide resolved
docs/docs/setup/auth/user/trust-auth-headers.md Outdated Show resolved Hide resolved
@@ -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.
Copy link
Member

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?

@github-actions github-actions bot temporarily deployed to test-deployment-pr1032 February 12, 2024 18:12 Destroyed
@github-actions github-actions bot temporarily deployed to test-deployment-pr1032 February 13, 2024 13:28 Destroyed
@github-actions github-actions bot temporarily deployed to test-deployment-pr1032 February 15, 2024 11:16 Destroyed
@github-actions github-actions bot temporarily deployed to test-deployment-pr1032 February 15, 2024 12:01 Destroyed
@LukasKalbertodt LukasKalbertodt added this to the v2.6 milestone Feb 20, 2024
@github-actions github-actions bot added the status:conflicts This PR has conflicts that need to be resolved label Feb 20, 2024

This comment was marked as resolved.

Copy link
Member

@JulianKniephoff JulianKniephoff left a 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. 🤷‍♀️

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...
@github-actions github-actions bot removed the status:conflicts This PR has conflicts that need to be resolved label Feb 22, 2024
@LukasKalbertodt LukasKalbertodt added the changelog:breaking Breaking changes label Feb 22, 2024
@github-actions github-actions bot temporarily deployed to test-deployment-pr1032 February 22, 2024 09:08 Destroyed
@LukasKalbertodt
Copy link
Member Author

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.

@LukasKalbertodt LukasKalbertodt merged commit 7829194 into elan-ev:master Feb 22, 2024
5 checks passed
@LukasKalbertodt LukasKalbertodt deleted the new-auth-flow branch February 22, 2024 09:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog:admin Changes primarily for admins changelog:breaking Breaking changes
Projects
Status: Done ✔️
Development

Successfully merging this pull request may close these issues.

Consider making login-proxy setup simpler by passing Tobira an HTTP endpoint to call for logins
4 participants