-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[oidc] encode and validate state params #16317
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
Conversation
started the job as gitpod-build-at-state-jwt.1 because the annotations in the pull request description changed |
6312adb
to
077843f
Compare
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.
LGTM in general. Let's find a better way to avoid the skipVerification
, as that pushes test code into our main implementation.
cipher db.Cipher | ||
dbConn *gorm.DB | ||
cipher db.Cipher | ||
stateJWT *StateJWT |
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 some way, the fact that it's a JWT is an implementation detail. And as such, here we could go with an interface rather than the fact that it is JWT
You could express this as
type State interface {
Encode(..)
Decode(..)
}
While this creates indirection, it does help future readers to not have to know the details of how it's encoded, just that it is.
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.
Up to you.
func newTestService(sessionServiceAddress string, dbConn *gorm.DB, cipher db.Cipher, stateJWT *StateJWT) *Service { | ||
service := NewService(sessionServiceAddress, dbConn, cipher, stateJWT) | ||
service.skipVerifyIdToken = true | ||
return service | ||
} |
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.
Either move this to a test package, or make it a first class constructor which doens't mention Test. It's an anti-pattern in Go to have test code alongisde the non-test implementation.
But I think we shouldn't have the skipVerifyIdToken
as a thing on this service at all. We should either abstract the verification behind an interface, such that we can mock/stub it in tests, or actually run the verification check.
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.
Alternatively, if you want to provide this as an option for the caller, encode it into the request such that the caller can choose to have the verification skipped.
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.
Let me know if you'd like to pair on this.
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 newTestService
.
skipVerifyIdToken
is passed to a constructor from a library. It was a recommended way to use this shortcut and remove skipVerifyIdToken
in a separate PR.
With the added golang-jwt/jwt
, it's now easier to extend the mocked service to actually to the checks instead of skipping verification.
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.
Let's find a better way to avoid the skipVerification, as that pushes test code into our main implementation.
skipVerification
is deemed to be removed in following PR.
Using JWT tokens for encoding/decoding/validation of state params carried throughout the OIDC/OAuth2 flow. Validating of integrity is crucial, as this piece of information contains the ID of the OIDC client to continue with when Gitpod receives the callback from a 3rd party. Tests should show that expiration time is checked and signature validation is effective.
077843f
to
901dcd2
Compare
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.
/hold for final Qs
@@ -152,7 +150,7 @@ func (s *Service) GetClientConfigFromCallbackRequest(r *http.Request) (*ClientCo | |||
return nil, fmt.Errorf("missing state parameter") | |||
} | |||
|
|||
state, err := decodeStateParam(stateParam) | |||
state, err := s.decodeStateParam(stateParam) | |||
if err != nil { | |||
return nil, fmt.Errorf("bad state param") |
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.
How do we communicate that this is a User Error, and not a system error?
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.
same as #16331 (comment)
That needs improvements.
@@ -75,11 +75,13 @@ func Start(logger *logrus.Entry, version string, cfg *config.Configuration) erro | |||
} | |||
} | |||
|
|||
var stateJWT *oidc.StateJWT |
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.
If there's no secret, the stateJWT
can now be nil. We're passing it into
oidc.NewService(cfg.SessionServiceAddress, dbConn, cipherSet, stateJWT)
Where it will panic when we try to access it on the calls. Should we hard fail if we don't have the secret? How do we handle this case?
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.
Was about to raise this question as well.
First of, adding config for staging and prod in ops.
Should we hard fail if we don't have the secret? How do we handle this case?
Failing the whole component is really bad, but doesn't seem to be wrong as this as crucial bits from security perspective, i.e. falling back to a default is a "no go".
OTOH, a second option might be to check for stateJWT == nil
in handlers and fail late. That comes at the cost of monitoring, as we'd need to cover that independently.
How was that discussed with PATs and Stripe integration?
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.
@easyCZ, I'm opting for not failing hard for now. We can change later on. Reason: for now this is all experimental and behind the scene, so let's keep risk at minimum.
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.
Done. Now it should not panic, but any attempt to call the endpoints while the signing key is not provided will be cancelled with an error.
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.
IMO, it's better to hard-fail in this case, I'm happy to do a follow-up change. With Stripe, we didn't hard fail because we had the use-case that we couldn't control the stripe configuration but needed the rest of the server to work. But here, we get a better rollout signal when we rollout and miss 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.
@easyCZ, let's do in follow-ups, please!
What else is required to release the breaks on this?
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.
PR is approved, feel free to land
/hold cancel |
After this PR is merged, the state param of the OIDC/OAuth2 gets signed and verified. Validating of integrity is crucial, as this piece of information contains the ID of the OIDC client to continue with when Gitpod receives the callback from a 3rd party. Tests should show that expiration time is checked and signature validation is effective.
Related Issue(s)
Part of #15956
How to test
See internal post for test credentials.
Login
from actions menu (three dot button)https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?
) from browser's location bar.@gitpod.io
account if you proceed.state
query param, alter the last segment (they are separated by.
), then paste it into a new tab. You should seeclient config not found
. The logs will contain thebad state param
as reason.Release Notes
Documentation
Build Options:
Experimental feature to run the build with GitHub Actions (and not in Werft).
leeway-target=components:all
Run Leeway with
--dont-test
Publish Options
Installer Options
Add desired feature flags to the end of the line above, space separated
Preview Environment Options:
If enabled this will build
install/preview
If enabled this will create the environment on GCE infra
Valid options are
all
,workspace
,webapp
,ide
,jetbrains
,vscode
,ssh