-
Notifications
You must be signed in to change notification settings - Fork 492
SQL-64: Create OIDC authenticator #34690
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?
SQL-64: Create OIDC authenticator #34690
Conversation
ffdddbc to
63f323e
Compare
bb5861a to
90002f0
Compare
b59884a to
8eb54d2
Compare
src/authenticator/src/oidc.rs
Outdated
| async fn expired(&mut self) { | ||
| // This session never expires - wait forever | ||
| // TODO (SangJunBak): Implement expiration. | ||
| std::future::pending().await |
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 need expiration support, right?
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.
Yes! Same motivation for this TODO here #34690 (comment). I figured I'd batch it with the refresh token work
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 have tickets for the follow up work? I just don't want us to forget this stuff.
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.
Not github issues but I have them recorded in Linear as tasks/subtasks https://linear.app/materializeinc/project/oidc-for-self-managed-3f1f7c14cfb5/issues
src/authenticator/src/oidc.rs
Outdated
| // TODO (SangJunBak): Add a configuration variable for the JWKS set and | ||
| // a boolean jwksFetchFromIssuer. | ||
| let jwks_uri = issuer_url | ||
| .join(".well-known/jwks.json") |
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 path, while commonly used, is not part of the OIDC specification. However, .well-known/openid-configuration IS actually part of the specification, and contains a required jwks_uri field. We should use the URI from that field instead.
https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
src/authenticator/src/oidc.rs
Outdated
| let mut validation = Validation::new(header.alg); | ||
| validation.set_issuer(&[&self.issuer]); | ||
| // TODO (SangJunBak): Validate audience based on configuration. | ||
| validation.validate_aud = false; |
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 it isn't configurable, we should default to validating the audience.
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.
Added audience as an input here b59f394!
src/authenticator/Cargo.toml
Outdated
| async-trait = "0.1" | ||
| jsonwebtoken = "9.3.1" | ||
| mz-adapter = { path = "../adapter", default-features = false } | ||
| mz-authenticator-types = { path = "../authenticator-types" } |
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.
Please always set default-features = false for all mz crates. This is needed to avoid pulling in workspace-hack into cloud.
src/environmentd/src/http.rs
Outdated
| Authenticator::Password(adapter_client) => match creds { | ||
| Some(Credentials::Password { username, password }) => { | ||
| let auth_response = adapter_client | ||
| adapter_client |
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 how much it matters, but there was some safety in us using the response from the authenticate call. Now that it isn't being used, there isn't anything preventing us from accidentally not calling it.
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.
Ah do you have more context on this? i.e. who was involved.
there isn't anything preventing us from accidentally not calling it.
What is the "it" you're referring to?
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.
adapter_client.authenticate
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.
Ah yeah, there is a risk there. @SangJunBak I think alexs point is well taken, though not urgently something you need to change. His point is that before when we had to return data from the auth response, it meant we couldn't forget to call adapter_client.authenticate. You could have authenticate return some sentinel type that we need to return or we could just be very careful.
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'd really like a sentinel type added, so we can't forget to call it. It's way to easy to get this wrong.
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.
sgtm!
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.
Should be addressed!
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 this is great, thank you.
| // TODO (SangJunBak): Use oidc_auth_enabled boolean and Authenticator::OIDC | ||
| // to decide whether or not we want to use OIDC authentication or password | ||
| // authentication. | ||
| let (_oidc_auth_enabled, options) = extract_oidc_auth_enabled_from_options(options); |
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.
Is this just a placeholder for later? Changing this behavior later is a breaking change.
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.
It's a placeholder for later! More specifically, I don't plan on releasing this to customers in its current state. I wanted to get eyes on it sooner than later and I figured we're effectively gating this feature until the orchestratord changes.
ef75f70 to
b59f394
Compare
|
@alex-hunt-materialize Feedback should all be addressed! |
| _conn_id: &ConnectionId, | ||
| client_ip: &Option<IpAddr>, | ||
| ) -> Result<(RoleId, BTreeMap<String, OwnedVarInput>), AdapterError> { | ||
| ) -> Result<(RoleId, Option<bool>, BTreeMap<String, OwnedVarInput>), AdapterError> { |
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.
could wrap this in a struct or something but 🤷🏼
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.
Ah I was thinking returning a struct like Result<AuthedStartupMessage> to make it a bit easier to read but this also does the trick by making it clearer rather than magic bools.
c306647 to
3bd127a
Compare
DAlperin
left a comment
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.
The specific commits I have been asked to review LGTM
3bd127a to
02f83ca
Compare
02f83ca to
953bad0
Compare
We extract - authenticate - validate_access_token Two methods used to authenticate HTTP and pgwire sessions out of the Frontegg authenticator. The goal is we can reuse these methods for a generic OIDC authenticator, used for self managed SSO
- Create an OIDC authenticator kind and a minimal set of config variables using CLI args - Implement JWK fetch on validate and also cache by the JWK key id.
I noticed that we were doing this weird round trip of getting internal user metadata from the catalog during authentication, then passing it back when initializing the session. By just doing this on startup, we: - Remove extraneous code - Open up ease of creating a unified interface for OIDC clients
- Introduced a new `mz-oidc-mock` package - Implemented tests for OIDC authentication
- Add functionality to extract `oidc_auth_enabled` from startup options, allowing us to use the password authenticator in the future
Before we were mistakenly fetching from jwks.json instead of getting it from the configuration endpoint
…in authentication - Updated comments to use backticks for OIDC issuer URL - Changed password handling in oidc http/ws auth to include the username when validating access tokens. - Simplified OIDC mock server structure by removing the base URL field - Remove unneeded assertion on role existence
- Add aud claim - Introduced `oidc_audience` field in OidcConfig to validate JWT's `aud` claim. This follows the spec in the design doc - Added OIDC mock server audience claims support - Added tests for audience validation and when we don't need to
Didn't realize we already had a shared crate!
Due to changed restrictions after a discussion with an external advisor, we decided we no longer need to implement the refresh token flow. However, this also means we no longer have the need for a shared OidcAuthenticator trait.
Before when we had to return internal user metadata data from the auth response, it meant we couldn't forget to call adapter_client.authenticate. By introducing a sentinel type, we make it harder for a developer to. We also combine `validate_access_token` into `authenticate` for GenericOidcAuthenticator.
953bad0 to
8462470
Compare
See commit messages for details.
Decided against using a system parameter for now to gate this feature, mainly because:
OidcMotivation
For an overview of what still needs to be implemented, a summary can be found on Linear (link)
To try:
demo.mov
Tips for reviewer
Some open questions I had regarding my implementation:
internal_user_metadatachange, it felt cleaner to combine it withrole_metadatainsession.rs. However, I decided against it since it would move the source of truth of the superuser status from theSession > SessionVars > UsertoSession, something adapter: move user field from Session to SessionVars #17961 was trying to avoid. Furthermore,role_metadataholds information regarding not only its role, but other roles too.Checklist
$T ⇔ Proto$Tmapping (possibly in a backwards-incompatible way), then it is tagged with aT-protolabel.