-
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
| const tokenOptions: IdTokenFetchOptions = { | ||
| token_format: 'jwt' | ||
| }; |
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.
In the e2e multi-tenant testing with user token exchange this was required because otherwise IAS returns an opaque token.
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.
Added few more requests to be worked on.
| interface IasOptionsBase { | ||
| /** | ||
| * The target URL of the destination that the IAS token is requested for. | ||
| * @default to the (identity service) URL from the service binding. | ||
| */ | ||
| targetUrl?: string; | ||
| /** | ||
| * The application resource(s) for which the token is requested. | ||
| * The token will only be usable to call the requested application(s). | ||
| * Either provide the app name (common case) or the provider client ID | ||
| * and tenant ID (optional). | ||
| */ | ||
| resource?: IasResource | IasResource[]; | ||
| /** | ||
| * The consumer (BTP) tenant ID of the application. | ||
| * May be required for multi-tenant communication. | ||
| */ | ||
| appTid?: string; | ||
| /** | ||
| * Additional parameters to be sent along with the token request. | ||
| */ | ||
| extraParams?: Record<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.
For the App2App case, both targetUrl and resource are required.
Even if they are optional here, we have to document this. Consider adding to JSDoc and to the documentation portal.
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/client-credentials-token-cache.ts
Outdated
Show resolved
Hide resolved
| /** | ||
| * 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.
[q] Should this be an array? Given we only pass one resource name?
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).
| * @param options.iasOptions - Options to change IAS token fetching (see {@link IasOptions}). | ||
| * @returns A JWT bearer token. | ||
| */ | ||
| export async function jwtBearerToken( |
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] The changes to this function are to support using an IAS token to fetch destinations using the destination service, and I would keep this out of this PR and do it in the the dedicated PR instead.
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 the changes to serviceToken also be reverted? It is responsible for (some of) the caching.
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. I think we can also revert the changes to serviceToken function for now as it also is mostly used with functions calling a service using an access token. Other usages might require more checks.
The transformServiceBindingToClientCredentialsDestination seems to be an exception, but I think we can add IAS support to this function later.
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 moved the IAS-specific parts of serviceToken to serviceTokenIas for now and marked it as experimental to keep the client credentials cache for IAS around.
- 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.
| * @param options.iasOptions - Options to change IAS token fetching (see {@link IasOptions}). | ||
| * @returns A JWT bearer token. | ||
| */ | ||
| export async function jwtBearerToken( |
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 the changes to serviceToken also be reverted? It is responsible for (some of) the caching.
| 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.
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.
| * @param options.iasOptions - Options to change IAS token fetching (see {@link IasOptions}). | ||
| * @returns A JWT bearer token. | ||
| */ | ||
| export async function jwtBearerToken( |
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. I think we can also revert the changes to serviceToken function for now as it also is mostly used with functions calling a service using an access token. Other usages might require more checks.
The transformServiceBindingToClientCredentialsDestination seems to be an exception, but I think we can add IAS support to this function later.
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>
Closes SAP/ai-sdk-js-backlog#347.