-
Notifications
You must be signed in to change notification settings - Fork 622
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
Oauth flow for Google, GitHub and GitLab #272
Conversation
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.
Awesome! I left a couple of comments, most of them are just nits – please take a look.
pkg/config/config.go
Outdated
GoogleClientID string `def:"<yourClientID>" desc:"client ID generated for Google API"` | ||
GoogleClientSecret string `def:"<yourClientSecret>" desc:"client secret generated for Google API"` | ||
GoogleRedirectURL string `def:"<pyroscope.mycompany.org/google/callback>" desc:"url that google will redirect to after logging in. Has to be in form <pathToPyroscopeServer/google/callback>"` | ||
GoogleScopes string `def:"https://www.googleapis.com/auth/userinfo.email" desc:"scopes for Google API"` |
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.
Do we actually need scopes to be configurable?
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.
At this point probably no, I left it in just in case someone could think of a use-case, would be easy to remove if you don't think there's anything useful we could offer?
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.
Yeah, I'd say let's remove them. Less flags is always better.
pkg/server/controller.go
Outdated
|
||
func (ctrl *Controller) oauthLoginHandler(oauthConf *oauth2.Config) http.HandlerFunc { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
URL, err := url.Parse(oauthConf.Endpoint.AuthURL) |
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.
We should perform all the validation steps before creating oauth2.Config
instance. Consider use of shallow copy (golang/go#41733).
URL
would be authURL
(url
collides with the package name).
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'm not sure I understand what should I use shallow copy for? Also if we were to call url.Parse before building oauth2.Config we would have to either pass some sort of identifier and the create it in a switch or remove url parsing to above function which would triple the amount of code. URL parsing should rarely fail so it won't influence performance that much that we created instance before if it fails.
Or did I misunderstand something?
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 agree. My point is that when we create *oauth2.Config
we should validate user input (fail fast).
BTW, am I right that scopes are passed as a space delimited string? I'm wondering if it works - CLI may not interpret that in the right way.
Next are just my subjective thoughts :) Given that we already have parsed URL at validation, should we do that again for every request? For oauthLoginHandler
we use *oauth2.Config
as a data container - instead we could introduce our own type to bear prepared data. But it's up to you ;)
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.
Removed scope as config flag so that won't be a problem :)
I really like the idea. Will include this.
Codecov Report
@@ Coverage Diff @@
## main #272 +/- ##
==========================================
- Coverage 53.77% 50.22% -3.55%
==========================================
Files 89 92 +3
Lines 3638 3981 +343
==========================================
+ Hits 1956 1999 +43
- Misses 1482 1775 +293
- Partials 200 207 +7
Continue to review full report at Codecov.
|
pkg/server/controller.go
Outdated
if err != nil { | ||
logrus.Errorf("failed to read file at %s", extraMetadataPath) | ||
ctrl.log.Errorf("Error parsing jwt token: %v", err) |
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.
ctrl.log.Errorf("Error parsing jwt token: %v", err) | |
ctrl.log.WithError(err).Error("parsing jwt token") |
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.
Totally forgot about logrus WithError
, will add in next commit
pkg/server/handler.go
Outdated
|
||
token, err := oauthConf.Exchange(r.Context(), code) | ||
if err != nil { | ||
ctrl.logAndRedirect(w, r, "Exchanging auth code for token failed with "+err.Error(), true) |
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.
Well, we either can use fmt.Sprintf
, or slightly modify the signature:
func (ctrl *Controller) logAndRedirect(w http.ResponseWriter, r *http.Request, format string, args ...interface{})
I think it's okay to invalidate state cookies on error regardless of anything (even if those are missing).
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.
In general only error can be passed so I'll remove this boolean parameter, pass error when it's available and log it with WithError
pkg/config/config.go
Outdated
GoogleEnabled bool `def:"false" desc:"enables Google Oauth"` | ||
GoogleClientID string `def:"<yourClientID>" desc:"client ID generated for Google API"` | ||
GoogleClientSecret string `def:"<yourClientSecret>" desc:"client secret generated for Google API"` | ||
GoogleRedirectURL string `def:"<pyroscope.mycompany.org/google/callback>" desc:"url that google will redirect to after logging in. Has to be in form <pathToPyroscopeServer/google/callback>"` |
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.
Can we have a way of determining this automatically from request Host header?
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 ideally:
- it should still be configurable
- by default just be an empty string
- and if it's an empty string the server just guesses it from host header and current request scheme (http / https)
- and we could add something to
desc
mentioning that you should only set it if pyroscope is under some fancy load balancer
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.
Hm interesting idea ... In general Host header shouldn't be trusted as they can be spoofed, but in this case I can't think of a problem that would cause us. Worst case scenario I can think of is spoofing the Host header which leads to wrong redirect URL and "400" error page from google. I'll look into doing this.
…n and url parsing to controller so that it's only done once. Fixed error handling. Added redirect url generation based on host header. Other PR fixes
GoogleTokenURL string `skip:"true" def:"https://accounts.google.com/o/oauth2/token" desc:"token url for Google API (usually present in credentials.json file)"` | ||
|
||
GitlabEnabled bool `skip:"true" def:"false" desc:"enables Gitlab Oauth"` | ||
// TODO: why is this one ApplicationID and not ClientID ? |
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.
GithubAuthURL string `skip:"true" def:"https://github.com/login/oauth/authorize" desc:"auth url for Github API"` | ||
GithubTokenURL string `skip:"true" def:"https://github.com/login/oauth/access_token" desc:"token url for Github API"` | ||
|
||
// TODO: can we generate these automatically if it's empty? |
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.
We can, we would just have to write it in some kind of persistent storage so that users don't get logged out if pyroscope is restarted. It's either that or making users explicitly generate it, which I think should be a good security practice (but it does make less of the things out-of-the-box)
Datasource: Prevent error on empty query
This version:
Google parameter forallowed_domain
was not included as it doesn't provide much more security but can be returned back to improve UX for GSuite users