Skip to content
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

feat: improve support for IAS tokens #4799

Merged
merged 35 commits into from
Jul 31, 2024
Merged

feat: improve support for IAS tokens #4799

merged 35 commits into from
Jul 31, 2024

Conversation

marikaner
Copy link
Contributor

@marikaner marikaner commented Jun 28, 2024

This includes generalized handling of tokens so that XSUAA, IAS and other custom tokens.

I fixed some places to make sure the difference between subdomain (aka. tenant, aka. tenant name) and tenant ID (aka. zone ID, aka. app_tid) is hopefully more obvious and in some cases more correct.
Especially the subdomain needs to be handled differently between XSUAA and IAS.

Closes SAP/cloud-sdk-backlog#1219.
Closes SAP/cloud-sdk-backlog#1217.

@marikaner marikaner changed the title Ias2 feat: improve support for IAS tokens Jun 28, 2024
@marikaner marikaner requested review from MatKuhr and deekshas8 June 28, 2024 14:19
Comment on lines +244 to +245
getSubdomain(this.subscriberToken?.serviceJwt?.decoded) ||
getSubdomain(this.subscriberToken?.userJwt?.decoded);
Copy link
Contributor Author

@marikaner marikaner Jun 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously we only looked at the subdomain from the user JWT. However, if the user passed a separate iss property this should be the actual subdomain and it should take precedence (right?).

If the iss property was passed, the service JWT was fetched with that subdomain and should have the same subdomain as it is an XSUAA token. If it was not possible to fetch the service token with this subdomain, I assume the subdomain isn't valid anyways.

Copy link
Member

@MatKuhr MatKuhr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please take my comments with a grain of salt, since the code is so complicated and I don't understand much if any of it. Comments are only based on the diff, not much else 😅

DestinationFromServiceRetriever.checkDestinationForCustomJwt(destination);
}

// Case 1 Destination in provider and JWT issued for provider account, but no custom JWT given -> no extra x-user-token header needed
// Case 1: subscriber account is the provider accound, user JWT is from XSUAA
// x-user-token header not needed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not? I can't think of a reason why subscriber/provider should have anything to do with the question if we should forward the token or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just FYI: I did not change the logic, only improved the comment here.
I would also assume we just send the header in all the cases, however according to this documentation: https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/exchanging-user-jwts-via-oauth2usertokenexchange-destinations

In this exact case it doesn't happen and we have to do the exchange on our own and send it as authentication header. I remember we tried this once in the E2E tests and passing it in the header in fact didn't work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any restrictions in the documentation why sending the header should not work for this auth type specifically. In fact, the Java SDK always sends the header, including for this auth type. So at least I am not aware of any reason, why this shouldn't work (assuming the OAuth service trusts and accepts the given user JWT).

If we have some test, where an XSUAA based token is not accepted in this manner, I'd like to look at it and potentially report that to the destination service.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am really not sure what the problem was, but I remember, that something did not work, I just don't know what. I would be willing to try this out, but definitely on a separate PR and probably after my vacation, if priorities allow it.

);
} catch (err) {
logger.warn(
'Failed to fetch subscriber service token for destination. This is only relevant if you are using subscriber destinations.'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently this discards the error, maybe print it to debug to give users some options to get to the root cause, in case they need to? At least that's what we typically do in Java..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I'm wondering what the reason for this change is, considering that this threw before. What changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently this discards the error, maybe print it to debug to give users some options to get to the root cause, in case they need to? At least that's what we typically do in Java..

I think it is a nice idea to add the error. Debug would hide it even more. I could just add it to the warning. What do you think?

Also, I'm wondering what the reason for this change is, considering that this threw before. What changed?

This is not obvious from the code at first glance, but previously this request was only executed (and thereby potentially throwing) when there was an iss property set or the given token was an XSUAA token. Now it would be executed for all tokens, if given.
There might be cases where I don't care about subscriber destinations, therefore I wouldn't need a subscriber token and wouldn't care about it failing.
I considered rethrowing the error in case the iss property was set or the token was an XSUAA token instead. Do you think that would be better?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't understand this "subscriber" aspect, so I don't know what this call is supposed to do, and in which cases this call failing can have a reasonable recovery..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is called to fetch a token for the subscriber. As we currently ALWAYS retrieve a subscriber token, when a user JWT is available, it is possible that we call this, although it isn't needed, because users are actually in the provider case. This is a very obvious flaw, but I don't dare touching this at the moment.

@marikaner
Copy link
Contributor Author

Please take my comments with a grain of salt, since the code is so complicated and I don't understand much if any of it. Comments are only based on the diff, not much else 😅

No worries! Thanks for the review! 😊

@@ -70,7 +70,7 @@ describe('getSubscriberToken()', () => {
expect(serviceTokenSpy).toHaveBeenCalledWith(
'destination',
expect.objectContaining({
jwt: { iss: onlyIssuerXsuaaUrl }
jwt: { ext_attr: { zdn: 'subscriber-only-iss' } }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously we mimicked a token with an issuer here. I changed this to be reflected in the zdn property, because the issuer should only be checked in the case we have an XSUAA token. If it is an IAS token, the subdomain of the tenant cannot be parsed from the issuer. Therefore we now first look at the zdn property and only if this is not there look at the issuer, if this is an XSUAA token.

@@ -13,7 +16,8 @@ export async function exchangeToken(
options: DestinationOptions
): Promise<string> {
const xsuaaService = getXsuaaService({
disableCache: !options.cacheVerificationKeys
disableCache: !options.cacheVerificationKeys,
credentials: getDestinationServiceCredentials()
Copy link
Member

@tomfrenken tomfrenken Jul 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since my knowledge of IAS is rather limited I don't know why we do this, why exactly do we pass the destination service credentials as XSUAA service credentials here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because we are retrieving this token only for the destination service, so this should be enough. Honestly, we need to clarify, whether there are cases where we would need to do this for connectivity. I am even thinking splitting this off this PR.

marikaner and others added 6 commits July 3, 2024 16:17
# Conflicts:
#	packages/connectivity/src/scp-cf/jwt.ts
#	packages/connectivity/src/scp-cf/tenant.ts
#	packages/connectivity/src/scp-cf/xsuaa-service.ts
@marikaner marikaner enabled auto-merge (squash) July 30, 2024 15:16
@marikaner marikaner merged commit 06e5c72 into main Jul 31, 2024
15 checks passed
@marikaner marikaner deleted the ias2 branch July 31, 2024 08:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants