OIDC multiple redirect URLs#12054
Conversation
fab5b67 to
475df06
Compare
0ca0ec6 to
c960157
Compare
0622fe2 to
7c0492f
Compare
smallinsky
left a comment
There was a problem hiding this comment.
Left some comments.
@Tener Could you also take a look ?
| string RedirectURL = 4 [ (gogoproto.jsontag) = "redirect_url" ]; | ||
| // | ||
| // DELETE IN 11.0.0 in favor of RedirectURLs | ||
| string RedirectURL = 4 [ (gogoproto.jsontag) = "redirect_url_deprecated,omitempty" ]; |
There was a problem hiding this comment.
As per other comment: can we deprecate this field explicitly, but leave the old tag in place?
Also, for older clients, we could simply fill this field with the first value of RedirectURLs. This wouldn't be optimal (performance-wise), but would maintain backwards compatibility.
There was a problem hiding this comment.
For the second part, this is already what's being done with CheckAndSetRedirectURL.
There was a problem hiding this comment.
I actually agree that changing this json tag is a bit confusing. Is there a reason we can't keep the old tag, not introduce any new fields but change the type of this field to wrapper.Strings so it can be unmarshaled from both a string or an array? That would be backwards compatible, no?
There was a problem hiding this comment.
Yeah, the issue is that the gRPC message will be changed, so when old clients/auth servers send String RedirectURL, the new clients/servers will return an error since they expect wrappers.Strings RedirectURL. And vice versa. So this would break backwards and forwards compatibility.
There was a problem hiding this comment.
Ah yes, you're right, I mixed it up with json unmarshalling for some reason. In that case I think it makes sense. Another option we have is to just introduce a proper new RedirectURLs with tag redirect_urls and deprecate the old one.
So it looks like we have 3 options:
- Try to make existing
redirect_urlsupport both strings and arrays of strings which is challenging to do in backwards compatible fashion with GRPC. - Intro new OIDC connector
redirect_urlsfield that accepts array, and deprecate singularredirect_urlin backwards compatible fashion (which means we'll likely keep it forever but update the docs to use theredirect_urls). - Let users set
proxy_redirect_addrin the proxy config instead, in which case we won't need to touch the OIDC connector spec at all.
@Joerger Is that accurate? Let's check with the product team (@klizhentas @xinding33) on which they'd prefer we implement.
There was a problem hiding this comment.
Correct. Note that with option 1, after the deprecation cycle is complete in v11.0.0, we can actually apply this backwards compatiblity approach again to match the json tags:
message OIDCConnectorSpecV3 {
...
strings RedirectURL = 4 [ (gogoproto.jsontag) = "redirect_url_deprecated" ];
...
wrappers.Strings RedirectURLs = 14 [ (gogoproto.jsontag) = "redirect_url" ];
}
---->
message OIDCConnectorSpecV3 {
...
wrappers.Strings RedirectURL = 4 [ (gogoproto.jsontag) = "redirect_url" ];
...
wrappers.Strings RedirectURLs = 14 [ (gogoproto.jsontag) = "redirect_url_deprecated" ];
}
There was a problem hiding this comment.
- Try to make existing
redirect_urlsupport both strings and arrays of strings which is challenging to do in backwards compatible fashion with GRPC.- Intro new OIDC connector
redirect_urlsfield that accepts array, and deprecate singularredirect_urlin backwards compatible fashion (which means we'll likely keep it forever but update the docs to use theredirect_urls).
I also should mention that the backwards compatibility steps taken for (1) and (2) will be the same - Set oidc.RedirectURL = oidc.RedirectURLs[0] when sending to old clients/servers, and read oidc.RedirectURLs[0] = oidc.RedirectURL when receiving from old clients/servers.
The only difference is that with (1), the json tags will not match the field names:
RedirectURL `json:"redirect_url_deprecated"`
RedirectURLs `json:"redirect_url"`
and with (2), they will match:
RedirectURL `json:"redirect_url"`
RedirectURLs `json:"redirect_urls"`
| // GetRedirectURL gets a redirect URL for the given connector. If the connector | ||
| // has a redirect URL which matches the host of the given Proxy address, then | ||
| // that one will be returned. Otherwise, the first URL in the list will be returned. | ||
| func GetRedirectURL(conn types.OIDCConnector, proxyAddr string) string { |
There was a problem hiding this comment.
I think it is critical to test this particular function.
Also, marshalling tests in teleport.e would be a good idea too, as well as any necessary updates to tctl sso oidc configure and tctl sso test.
There was a problem hiding this comment.
I've added tests for GetRedirectURL and UnmarshalOIDCConnector. It looks like tctl sso oidc configure and tctl sso test are not implemented yet, but they should not need adjustments with this PR.
|
Thinking about the feature in general: would it make sense to leave the old field in place (which would solve most compatibility issues) and add an additional "redirect override mapping" field? The single redirect URL would serve as a sensible default, while the mapping would allow explicit control over redirect URLs for particular proxies. This would have several advantages:
|
To clarify, do you mean something like the following: And for this to work, the value on the left would need to match the proxy public addr set in the proxy's teleport.yaml exactly. I don't think that having an override mapping field would make this any less complex or easy to configure, but the explicitly of your approach might be worth it. But first let me explain the current approach and why I did it. My goal was to make the change to oidc yaml configuration as simple/small as possible. Since the core of the change is to enable multiple RedirectURLs, it would make sense to make IMO this creates the easiest configuration experience. All you have to do is provide the Redirect URLs that are set in your OIDC provider, and you'll get the functionality for free: and other users can continue to use the old functionality: The only backwards compatibility crutch is on the gRPC layer. Other than that, we can now use the redirect url list in place of the single string used previously. Hopefully this approach makes more sense now, but let me know if you still think it's the wrong approach. @r0mant What do you think? |
Thank you, something along these lines. The explicit design would allow us to handle setup with, say, the majority of company in U.S. but also a handful of branch offices in London and Tokyo: I'm not sure this case would be handled correctly in current code: Lines 96 to 111 in be0fef7
We could also extend the automatic configuration code (see pending PR: https://github.com/gravitational/teleport.e/blob/d837922073dbf4ec22ec79e707f122bd8ba46962/tool/tctl/sso_configure_command/oidc.go#L314) to automatically discover all URLs. |
|
@xinding33 @klizhentas Can I get your guys' UX opinion on this change? The goal of this PR is to make it possible to choose an oidc redirect url based on the calling proxy during the oidc request. These are the two options in the running after talking with @r0mant. I've listed a couple pros and cons as well. 1) match redirect url with proxy from a listFirst, we enable providing multiple redirect urls to the oidc connector for each proxy which you want to redirect to: redirect_url:
- https://proxy.example.com:3080/v1/webapi/oidc/callback
- https://eu.remote.example.com:3080/v1/webapi/oidc/callback
- https://au.proxy.example.com:3080/v1/webapi/oidc/callbackThen, when a user makes an oidc request - Pros:
Cons:
2) configure the redirect url in the proxyWe can add a proxy configuration field - If it isn't set, we use Pros:
Cons:
|
16c0f88 to
fc273a2
Compare
|
@Joerger Hey, thanks for the explanation and summary of pros and cons. Correct me if I'm wrong, but couldn't the first approach also we also extend this functionality to SAMl or other SSO providers? Personally, I prefer the first approach as there'd be a single place users can configure all proxies and there remains a single place users can configure redirect URIs. Provided that the backwards compatibility crutches aren't too severe, this seems like the better approach for users. One big question regarding this approach, potentially related to security, is how can we help users remove unused/deprecated proxies? |
In the future we could apply approach 1 to make However like you said, the downside of approach 2 is that you are now setting up your sso configuration across (potentially) every proxy, rather than a single resource.
I agree, keeping it in one place should make it simpler to configure. @xinding33 If we are going with the first approach, can you take a look at this question as well? - #12054 (comment)
First, users can remove the proxy's redirect url directly from their service provider to prevent any auth attempts from succeeding from that proxy. Second, they can remove the proxy addr from In both approaches, the proxy would default to |
1070adb to
c1c778a
Compare
| return clientPack.client, nil | ||
| cachedClient, ok := a.oidcClients[clientMapKey] | ||
| if ok { | ||
| if cmp.Equal(cachedClient.connector, conn) && cachedClient.syncCtx.Err() == nil { |
There was a problem hiding this comment.
I see how comparing connectors instead of configs makes it possible to refactor the code more heavily, but this will also cause the clients to be recreated more often than otherwise needed. For example, any change in ClaimToRoles will cause a new client to be created needlessly: the config between old and new clients are the same, yet we force the rotation due to connector changes.
In practice, this is unlikely to cause problems, but I'm wary of introducing changes with the potential for subtle bugs.
There was a problem hiding this comment.
Hmm I see what you mean, part of the reason I did this was that the provider syncing uses oidc.GetIssuerURL which isn't covered by the config, but does require a client refresh.
In general clearing the cache on any connector change seems like the safest approach to me, and should cover future changes more broadly. However I'm not opposed to making the comparison more specific to avoid needless cache clearing on changes to fields like ClaimsToRoles.
01b4908 to
ddd08d8
Compare
e5f316e to
979222c
Compare
Core changes
This PR adds support for adding multiple redirect URLs. The redirect used for a given OIDC auth request will be chosen based on the calling proxy.
If there is no redirect URL with a matching host to the proxy, the first redirect URL in the list will be used. If there is no redirect URL with a matching host AND port, then the first redirect URL with a match host will be used.
Other changes
oidcConnector.CheckAndSetDefautlts. Before some of the validation was inservices.ValidateOIDCConnecotor(oidcConnector), but every call path would end up calling both.Backwards compatibility
This PR adds a new field
RedirectURLsto the oidc connector resource, and deprecates theRedirectURLfield.gRPC
In order to communicate with old clients and servers, we must do two things:
oidc.RedirectURLs[0] = oidc.RedirectURLwhen receiving an oidc connector from an old server/clientoidc.RedirectURL = ""so that the value doesn't get marshaled into the backend or client output.oidc.RedirectURL = oidc.RedirectURLs[0]when sending an oidc connector to an old server/clientyaml
We can convert
oidc.redirect_urlto awrapper.Stringsin order to marshal from either a string or list of strings and make backwards compatibility simple from a configuration perspective.However, since
redirect_urlis unmarshalled from the proto message fieldOIDCConnectorV3.Spec.RedirectURL, we can't simply changeRedirectURLfrom a string to awrapper.Strings- this would break backwards compatibility of gRPC requests.However, we can hack around this by changing the json tags of
RedirectURLandRedirectURLs:Unfortunately this approach results in non-matching json tags, but it also results in a desirable configuration experience. In the future, we can deprecate
string RedirectURL, and take another deprecation cycle to fix the tag/field name. This is a bit overkill, but at least it's possible.e PR - https://github.com/gravitational/teleport.e/pull/444
Closes #7042