-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add possibility for 'user-auth' authorization #812
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
base: main
Are you sure you want to change the base?
Conversation
This looks great, thank you. Given that it is an auth change, I will test this thoroughly myself, but I would highly appreciate some unit tests around this. Check out |
I would expect nothing less ;-)
I will definitely give this a try, but as I said, this will at least take some days. Maybe one question about performance: |
server/server.yml
Outdated
# Otherwise the authentication can be bypassed by a crafted header. | ||
# | ||
# user-header: x-forwarded-user | ||
|
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.
I think this header should only be allowed if behind-proxy: true
is set.
server/server.go
Outdated
|
||
// extractUserHader pulls the username of an already authenticated user from the configured header | ||
func extractUserHeader(r *http.Request, h string) (username string) { | ||
return readParam(r, h) |
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.
Took me a while, but this is a giant security flaw, becausereadParam
also accepts query params and not just headers. So this must be just r.Header.Get()
I refined your code in #816. Note that your commit are not in there, because the ntfy binary (30MB) would be in the history. If you'd like your commit to be in there, feel free to force-push an updated branch, or open a new PR. You and the original author will get credit in the changelog either way though. Please review #816 to see if this would work for you. I think it's a nice addition. Question: What auth system are you using? |
My code will only check the value if the config value is set. It's a single if statement. It'll be fine. |
I would go for a client certificate, as the number of clients is very small (just me ;-P) |
Fair warning: The ntfy Android client does not work with custom certs though. |
@Nanowires I am a little confused now. I thought that this would enable the use of Authelia or Keycloak. Am I wrong? |
As far as I understand, this allows any kind of authentication, as long as the proxy sends the user defined header with the associated user. |
e932d68
to
5f0c30a
Compare
I tested this with Authelia and Caddy tonight. Here's the config: Caddy
ntfy
Here are some things I found:
There might be more issues, but for at least these reasons, I don't think this is ready to merge right away. I did not test anything from the Android app. I'm not even sure how I could sign into Authelia from the ntfy app... |
This would be very useful - wish this could be implemented!
Yes it would enable the use of any custom auth gateway, Authelia and Keycloak included. Enabling the auth-user-header would let a auth proxy handle auth, and then forward the request with the header set. It could be used with for example https://dexidp.io/docs/connectors/authproxy/ (would let users use LDAP, OIDC, or whatever they like) For this PR to be implemented, @wunter8 has some good observations.
No, this is not expected. ntfy should accept the username, even if there is no ntfy account with that name. A benefit of this is that the auth-proxy that sets the
As expected - it uses webpush and vapid to subscribe to topics.
This seems related to 1 - auth-header authentication should have first priority if enabled.
Unrelated. Login should work without the auth-header set, even if enabled. This allows for multiple login options, so token auth and user auth can still work even if the auth header option is enabled.
As expected.
It really shouldn't either, it's not managed by ntfy in this use case.
When the app starts, it should check if it is authenticated. If it receives a HTTP 3XX Redirect response, it should open a webview to follow the redirect. This would let the auth proxy do it's auth thing and then close the webview window with javascript using @binwiederhier any chance these comments could help get this implemented? It would be extremely useful for large-scale deployments that need authentication with a centralized user management system like LDAP or OIDC. |
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.
This needs to be changed so it supports authentication even if the user does not exist (that would enable the oidc auth proxy use-case).
// authenticateViaUserDefinedHeader tries to authenticate the user via the header defined in the "auth-user-header" | ||
// configuration value if it is set. The value of the passed username is used to lookup the user in the database. | ||
// If it exists, authentication is successful. | ||
// | ||
// This function will ALWAYS return a visitor, even if an error occurs (e.g. unauthorized), so | ||
// that subsequent logging calls still have a visitor context. | ||
func (s *Server) authenticateViaUserDefinedHeader(r *http.Request, vip *visitor, username string) (*visitor, error) { | ||
// Check the rate limiter first | ||
if !vip.AuthAllowed() { | ||
return vip, errHTTPTooManyRequestsLimitAuthFailure // Always return visitor, even when error occurs! | ||
} | ||
// Retrieve user from database; if found, we have a successful authentication | ||
u, err := s.userManager.User(username) | ||
if err != nil || u.Deleted { | ||
vip.AuthFailed() | ||
logr(r).Err(err).Debug("Authentication failed") | ||
return vip, errHTTPUnauthorized | ||
} | ||
// User was found, meaning that auth was successful | ||
return s.visitor(vip.ip, u), nil | ||
} |
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.
This code should also create the user if it doesn't exist. It could assign a random password if needed. That's all there needs to be done to make it work properly as far as I can tell.
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.
or at least have an option to allow automatic user registration through the http auth header.
Another PR for #601
Mostly copied from #602
Same as the original author: I'm not particularly familiar with go, or the ntfy code base, I haven't tried to run this code.
If I have time I will try to add tests in the next few days.
I'm also not sure about the way to trace here (e.g. in case that the user-header is set and the client sent one, but the user is not found)