-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
OIDC multiple redirect URLs #12054
OIDC multiple redirect URLs #12054
Conversation
fab5b67
to
475df06
Compare
0ca0ec6
to
c960157
Compare
0622fe2
to
7c0492f
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.
Left some comments.
@Tener Could you also take a look ?
api/types/types.proto
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the second part, this is already what's being done with CheckAndSetRedirectURL
.
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 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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_url
support both strings and arrays of strings which is challenging to do in backwards compatible fashion with GRPC. - Intro new OIDC connector
redirect_urls
field that accepts array, and deprecate singularredirect_url
in backwards compatible fashion (which means we'll likely keep it forever but update the docs to use theredirect_urls
). - Let users set
proxy_redirect_addr
in 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Try to make existing
redirect_url
support both strings and arrays of strings which is challenging to do in backwards compatible fashion with GRPC.- Intro new OIDC connector
redirect_urls
field that accepts array, and deprecate singularredirect_url
in 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"`
lib/services/oidc.go
Outdated
// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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/callback Then, 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
lib/auth/oidc.go
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
RedirectURLs
to the oidc connector resource, and deprecates theRedirectURL
field.gRPC
In order to communicate with old clients and servers, we must do two things:
oidc.RedirectURLs[0] = oidc.RedirectURL
when 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_url
to awrapper.Strings
in order to marshal from either a string or list of strings and make backwards compatibility simple from a configuration perspective.However, since
redirect_url
is unmarshalled from the proto message fieldOIDCConnectorV3.Spec.RedirectURL
, we can't simply changeRedirectURL
from 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
RedirectURL
andRedirectURLs
: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