-
Notifications
You must be signed in to change notification settings - Fork 64
feat: [DO NOT MERGE] IAS App-To-App Auth #6185
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?
Conversation
c0cc0e8 to
a541e18
Compare
0165671 to
fdb3d35
Compare
packages/connectivity/src/scp-cf/destination/destination-from-vcap.ts
Outdated
Show resolved
Hide resolved
- Rename IAS resource properties for clarity (clientId → providerClientId, tenantId → providerTenantId) - Move iasOptions parameter to main function signature in getDestinationFromServiceBinding for better API ergonomics - Enhance IasClientCredentialsResponse interface with additional JWT claims (scimId, custom_iss, app_tid) - Use IdentityServiceToken class from @sap/xssec for IAS-specific token decoding - Add zone_uuid fallback for legacy tenant ID resolution - Prioritize XSUAA user_id over IAS user_uuid in userId() function (aligning with priority-handling in other parts) - Remove IAS handling in jwtBearerToken() to only handle XSUAA flow - Add comprehensive test coverage for IAS token caching and resource isolation - Improve documentation for targetUrl and resource parameters
1becf29 to
87c7a9f
Compare
| /** | ||
| * IAS API resources. Present when resource parameter is specified in the token request. | ||
| */ | ||
| ias_apis?: 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.
Even for single items it returns an array in the jwt, and when a providerClientId is given instead of name it's possible for it's supposed to return a token for everything the providerClientId applies to (possibly multiple depencies with different API permissions on the same provider).
| export function isIasToken(decodedJwt: JwtPayload): boolean { | ||
| if (!decodedJwt.iss) { | ||
| return 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.
This could potentially check for ias_apis, ias_iss or scim_id but I'm not sure if exchanged-from-IAS XSUAA tokens could have these properties.
| if (assertion) { | ||
| const decodedJwt = new IdentityServiceToken(assertion); | ||
| const issuer = decodedJwt.issuer; | ||
| const issuerUrl = tryParseUrl(issuer, 'JWT assertion issuer'); | ||
| subdomain = issuerUrl.hostname.split('.')[0]; | ||
| // Replace subdomain in the URL from the service binding | ||
| // Reason: We don't want to blindly trust the URL in the assertion | ||
| const credentialsUrl = tryParseUrl(credentials.url, 'Identity Service'); | ||
| const credentialsSplit = credentialsUrl.hostname.split('.'); | ||
| credentialsUrl.hostname = [subdomain, ...credentialsSplit.slice(1)].join( | ||
| '.' | ||
| ); | ||
| let normalizedUrl = credentialsUrl.toString(); | ||
| if (normalizedUrl.endsWith('/')) { | ||
| normalizedUrl = normalizedUrl.slice(0, -1); | ||
| } |
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.
Instead of this it might be better to use IdentityService.getSafeUrlFromTokenIssuer(token, credentials.domains) and not rewrite the URL.
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 don't understand, how does the xssec function help here? Wouldn't it just help in validation? The actual URL manipulation still needs to be done 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.
xssec uses the function to also determine the issuer for token verification, so in this case it could be enough verification to use the issuer url as is in this case. Additionally it has the advantage of catching cases of accounts400 vs accounts and any potential custom domains for ias.
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.
@davidkna-sap Are you aware of this module? => packages/connectivity/src/scp-cf/subdomain-replacer.ts
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 migrated the code to use packages/connectivity/src/scp-cf/subdomain-replacer.ts with some IAS-specific changes.
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.
xssec uses the function to also determine the issuer for token verification, so in this case it could be enough verification to use the issuer url as is in this case. Additionally it has the advantage of catching cases of
accounts400vsaccountsand any potential custom domains for ias.
Why don't we use it then? is there a downside to 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.
I didn't use it because for better consistency with the handling in XSUAA and the Java-SDK (where it seems to work well).
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.
Agreed, let's avoid scope creep. 👍
KavithaSiva
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.
Mostly looks good to me, added some minor comments.
Great work!
| * ATTENTION: The property is mandatory in the following cases: | ||
| * - User-dependent authentication flow is used, e.g., `OAuth2UserTokenExchange`, `OAuth2JWTBearer`, `OAuth2SAMLBearerAssertion`, `SAMLAssertion` or `PrincipalPropagation`. | ||
| * - Multi-tenant scenarios with destinations maintained in the subscriber account. This case is implied if the `selectionStrategy` is set to `alwaysSubscriber`. | ||
| * - IAS token exchange with `iasOptions.authenticationType` set to `'OAuth2JWTBearer'`. In this case, the JWT is automatically used as the assertion for IAS token exchange. |
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.
[q] Where is iasOptions.authenticationType set? It's not clear from the 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.
I tried to clarify the comment, though arguably the IAS-case is covered by "User-dependent authentication flow".
packages/connectivity/src/scp-cf/destination/destination-from-vcap.ts
Outdated
Show resolved
Hide resolved
packages/connectivity/src/scp-cf/destination/destination-from-vcap.ts
Outdated
Show resolved
Hide resolved
packages/connectivity/src/scp-cf/destination/destination-from-vcap.ts
Outdated
Show resolved
Hide resolved
| if (assertion) { | ||
| const decodedJwt = new IdentityServiceToken(assertion); | ||
| const issuer = decodedJwt.issuer; | ||
| const issuerUrl = tryParseUrl(issuer, 'JWT assertion issuer'); | ||
| subdomain = issuerUrl.hostname.split('.')[0]; | ||
| // Replace subdomain in the URL from the service binding | ||
| // Reason: We don't want to blindly trust the URL in the assertion | ||
| const credentialsUrl = tryParseUrl(credentials.url, 'Identity Service'); | ||
| const credentialsSplit = credentialsUrl.hostname.split('.'); | ||
| credentialsUrl.hostname = [subdomain, ...credentialsSplit.slice(1)].join( | ||
| '.' | ||
| ); | ||
| let normalizedUrl = credentialsUrl.toString(); | ||
| if (normalizedUrl.endsWith('/')) { | ||
| normalizedUrl = normalizedUrl.slice(0, -1); | ||
| } |
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 don't understand, how does the xssec function help here? Wouldn't it just help in validation? The actual URL manipulation still needs to be done right?
| `Could not fetch IAS client credentials token for service of type ${resolvedService.label}: ${err.message}` | ||
| ); | ||
| }); | ||
| return token; |
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 checked the usages of this function, and right now we use ClientCredentialsResponse base interface during caching. So, we would also have to change the caching function.
We can consider migrating to token classes available from xssec, with caching changes in a follow-up.
packages/connectivity/src/scp-cf/destination/destination-from-vcap.spec.ts
Show resolved
Hide resolved
04e7de2 to
91a9927
Compare
91a9927 to
005e875
Compare
| if (arg.appTid) { | ||
| tokenOptions.app_tid = arg.appTid; | ||
| } |
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.
[req] I would use the suggested bool flag above in IasOptionsTechnical to determine if the provider tenant id(app_tid from the ias credentials) or the tenant id(from incoming jwt) needs to be supplied here.
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, but it still needs unit-tests.
Co-authored-by: KavithaSiva <32287936+KavithaSiva@users.noreply.github.com>
marikaner
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.
LGTM API-wise, I left quite a lot of comments (many minor things) and I didn't look at the tests for now. If there is anything you would like to have reviewed specifically, let me know.
packages/connectivity/src/scp-cf/client-credentials-token-cache.ts
Outdated
Show resolved
Hide resolved
packages/connectivity/src/scp-cf/client-credentials-token-cache.ts
Outdated
Show resolved
Hide resolved
packages/connectivity/src/scp-cf/destination/destination-accessor-types.ts
Outdated
Show resolved
Hide resolved
packages/connectivity/src/scp-cf/destination/destination-from-vcap.ts
Outdated
Show resolved
Hide resolved
| if (assertion) { | ||
| const decodedJwt = new IdentityServiceToken(assertion); | ||
| const issuer = decodedJwt.issuer; | ||
| const issuerUrl = tryParseUrl(issuer, 'JWT assertion issuer'); | ||
| subdomain = issuerUrl.hostname.split('.')[0]; | ||
| // Replace subdomain in the URL from the service binding | ||
| // Reason: We don't want to blindly trust the URL in the assertion | ||
| const credentialsUrl = tryParseUrl(credentials.url, 'Identity Service'); | ||
| const credentialsSplit = credentialsUrl.hostname.split('.'); | ||
| credentialsUrl.hostname = [subdomain, ...credentialsSplit.slice(1)].join( | ||
| '.' | ||
| ); | ||
| let normalizedUrl = credentialsUrl.toString(); | ||
| if (normalizedUrl.endsWith('/')) { | ||
| normalizedUrl = normalizedUrl.slice(0, -1); | ||
| } |
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.
@davidkna-sap Are you aware of this module? => packages/connectivity/src/scp-cf/subdomain-replacer.ts
Co-authored-by: Marika Marszalkowski <marika.marszalkowski@sap.com>
86a3237 to
74e8bbf
Compare
marikaner
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.
I left one question. Good work!
| if (assertion) { | ||
| const decodedJwt = new IdentityServiceToken(assertion); | ||
| const issuer = decodedJwt.issuer; | ||
| const issuerUrl = tryParseUrl(issuer, 'JWT assertion issuer'); | ||
| subdomain = issuerUrl.hostname.split('.')[0]; | ||
| // Replace subdomain in the URL from the service binding | ||
| // Reason: We don't want to blindly trust the URL in the assertion | ||
| const credentialsUrl = tryParseUrl(credentials.url, 'Identity Service'); | ||
| const credentialsSplit = credentialsUrl.hostname.split('.'); | ||
| credentialsUrl.hostname = [subdomain, ...credentialsSplit.slice(1)].join( | ||
| '.' | ||
| ); | ||
| let normalizedUrl = credentialsUrl.toString(); | ||
| if (normalizedUrl.endsWith('/')) { | ||
| normalizedUrl = normalizedUrl.slice(0, -1); | ||
| } |
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.
xssec uses the function to also determine the issuer for token verification, so in this case it could be enough verification to use the issuer url as is in this case. Additionally it has the advantage of catching cases of
accounts400vsaccountsand any potential custom domains for ias.
Why don't we use it then? is there a downside to it?
| 'experimental', | ||
| 'defaultValue' | ||
| ], | ||
| // The other default-allowed tags are not supported by tsdoc |
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.
🥴
0cd1c55 to
9fb61ee
Compare
Closes SAP/ai-sdk-js-backlog#347.