From 991287f6bdc42ccf2569148e31bac72268e1037b Mon Sep 17 00:00:00 2001 From: NAVEENKUMAR MARUPALLI Date: Wed, 29 Jul 2026 03:36:45 +0530 Subject: [PATCH 1/2] Modified interface to include clientSecretConfig | handled them across SubClasses using resolveClientSecretFragment() --- .../identity/oauth2-credential-provider.ts | 119 ++++++++++++++++-- 1 file changed, 107 insertions(+), 12 deletions(-) diff --git a/packages/aws-cdk-lib/aws-bedrockagentcore/lib/identity/oauth2-credential-provider.ts b/packages/aws-cdk-lib/aws-bedrockagentcore/lib/identity/oauth2-credential-provider.ts index 548073d784bd3..81027b8720fcd 100644 --- a/packages/aws-cdk-lib/aws-bedrockagentcore/lib/identity/oauth2-credential-provider.ts +++ b/packages/aws-cdk-lib/aws-bedrockagentcore/lib/identity/oauth2-credential-provider.ts @@ -114,6 +114,39 @@ export class OAuth2CredentialProviderVendor { } } +/** + * Source of the OAuth2 client secret. + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-bedrockagentcore-oauth2credentialprovider-customoauth2providerconfiginput.html + */ +export enum ClientSecretSource { + /** The service creates and manages the secret in Secrets Manager (default). */ + MANAGED = 'MANAGED', + /** + * You supply a pre-existing AWS Secrets Manager secret. + * Must be accompanied by `clientSecretConfig` on the provider props. + */ + EXTERNAL = 'EXTERNAL', +} + +/** + * A reference to a pre-existing AWS Secrets Manager secret that stores an OAuth2 client secret + * value, for use with `ClientSecretSource.EXTERNAL`. + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-bedrockagentcore-oauth2credentialprovider-secretreference.html + */ +export interface OAuth2ClientSecretConfig { + /** + * The ID (name or ARN) of the AWS Secrets Manager secret that stores the client secret value. + */ + readonly secretId: string; + + /** + * The JSON key used to extract the client secret value from the secret. + */ + readonly jsonKey: string; +} + /****************************************************************************** * Interface *****************************************************************************/ @@ -240,20 +273,34 @@ export interface OAuth2CredentialProviderBaseProps { /** * OAuth2 client identifier and secret registered with the identity provider (all vendors). + * + * Provide exactly one of {@link clientSecret} (`ClientSecretSource.MANAGED`, the default) or + * {@link clientSecretConfig} (`ClientSecretSource.EXTERNAL`). Both forms are supported by every + * `usingProviderName` factory on {@link OAuth2CredentialProvider}. */ export interface OAuth2ClientCredentials { /** OAuth2 client identifier. */ readonly clientId: string; /** - * OAuth2 client secret. + * OAuth2 client secret (supplied in plaintext, used when `ClientSecretSource.MANAGED`). * * **NOTE:** The client secret will be included in the CloudFormation template as part of synthesis. * The service stores the secret in Secrets Manager after creation, but the value is visible * in the template and deployment history. Use `SecretValue.unsafePlainText()` to explicitly * acknowledge plaintext, or pass a reference from another construct to avoid embedding the * literal value. + * + * @default - not specified; provide `clientSecretConfig` instead + */ + readonly clientSecret?: SecretValue; + + /** + * A reference to a pre-existing AWS Secrets Manager secret that stores the client secret + * (`ClientSecretSource.EXTERNAL`), instead of embedding the secret value in the template. + * + * @default - not specified; provide `clientSecret` instead */ - readonly clientSecret: SecretValue; + readonly clientSecretConfig?: OAuth2ClientSecretConfig; } /** @@ -602,6 +649,53 @@ function assertCustomOAuth2DiscoveryXor(scope: Construct, props: CustomOAuth2Cre } } +/** + * @internal + * Validates that exactly one of `clientSecret` (`ClientSecretSource.MANAGED`) or + * `clientSecretConfig` (`ClientSecretSource.EXTERNAL`) is provided, and returns the corresponding + * CloudFormation property fragment for whichever vendor config is being built. + * + * When `clientSecret` is used, no `clientSecretSource` is emitted — this preserves the exact + * template output CDK produced before EXTERNAL support existed (CloudFormation treats an absent + * `ClientSecretSource` as `MANAGED`). + */ +function resolveClientSecretFragment( + scope: Construct, + props: OAuth2ClientCredentials, + vendorLabel: string, +): { clientSecret?: string; clientSecretSource?: string; clientSecretConfig?: CfnOAuth2CredentialProvider.SecretReferenceProperty } { + const hasSecret = props.clientSecret !== undefined; + const hasConfig = props.clientSecretConfig !== undefined; + + if (hasSecret && hasConfig) { + throw new ValidationError( + lit`ClientSecretExclusive`, + `Provide either clientSecret or clientSecretConfig for ${vendorLabel}, not both.`, + scope, + ); + } + if (!hasSecret && !hasConfig) { + throw new ValidationError( + lit`ClientSecretRequired`, + 'Provide either clientSecret (ClientSecretSource.MANAGED) or clientSecretConfig' + + `(ClientSecretSource.EXTERNAL) for ${vendorLabel}.`, + scope, + ); + } + + if (hasConfig) { + return { + clientSecretSource: ClientSecretSource.EXTERNAL, + clientSecretConfig: { + secretId: props.clientSecretConfig!.secretId, + jsonKey: props.clientSecretConfig!.jsonKey, + }, + }; + } + + return { clientSecret: props.clientSecret!.unsafeUnwrap() }; +} + /****************************************************************************** * Abstract base *****************************************************************************/ @@ -724,7 +818,7 @@ function newOAuth2WithIncludedClientCredentialsOnly( oauth2ProviderConfigInput: { includedOauth2ProviderConfig: { clientId: props.clientId, - clientSecret: props.clientSecret.unsafeUnwrap(), + ...resolveClientSecretFragment(scope, props, `${vendor.value} OAuth2`), }, }, }); @@ -746,7 +840,7 @@ function newOAuth2WithIncludedTenant( oauth2ProviderConfigInput: { includedOauth2ProviderConfig: { clientId: props.clientId, - clientSecret: props.clientSecret.unsafeUnwrap(), + ...resolveClientSecretFragment(scope, props, `${vendor.value} OAuth2`), authorizationEndpoint: props.authorizationEndpoint, issuer: props.issuer, tokenEndpoint: props.tokenEndpoint, @@ -783,7 +877,7 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oAuth2CredentialProviderName: props.oAuth2CredentialProviderName, tags: props.tags, credentialProviderVendor: OAuth2CredentialProviderVendor.SLACK.value, - oauth2ProviderConfigInput: { slackOauth2ProviderConfig: { clientId: props.clientId, clientSecret: props.clientSecret.unsafeUnwrap() } }, + oauth2ProviderConfigInput: { slackOauth2ProviderConfig: { clientId: props.clientId, ...resolveClientSecretFragment(scope, props, 'Slack OAuth2') } }, }); } @@ -795,7 +889,7 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oAuth2CredentialProviderName: props.oAuth2CredentialProviderName, tags: props.tags, credentialProviderVendor: OAuth2CredentialProviderVendor.GITHUB.value, - oauth2ProviderConfigInput: { githubOauth2ProviderConfig: { clientId: props.clientId, clientSecret: props.clientSecret.unsafeUnwrap() } }, + oauth2ProviderConfigInput: { githubOauth2ProviderConfig: { clientId: props.clientId, ...resolveClientSecretFragment(scope, props, 'GitHub OAuth2') } }, }); } @@ -807,7 +901,7 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oAuth2CredentialProviderName: props.oAuth2CredentialProviderName, tags: props.tags, credentialProviderVendor: OAuth2CredentialProviderVendor.GOOGLE.value, - oauth2ProviderConfigInput: { googleOauth2ProviderConfig: { clientId: props.clientId, clientSecret: props.clientSecret.unsafeUnwrap() } }, + oauth2ProviderConfigInput: { googleOauth2ProviderConfig: { clientId: props.clientId, ...resolveClientSecretFragment(scope, props, 'Google OAuth2') } }, }); } @@ -819,7 +913,7 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oAuth2CredentialProviderName: props.oAuth2CredentialProviderName, tags: props.tags, credentialProviderVendor: OAuth2CredentialProviderVendor.SALESFORCE.value, - oauth2ProviderConfigInput: { salesforceOauth2ProviderConfig: { clientId: props.clientId, clientSecret: props.clientSecret.unsafeUnwrap() } }, + oauth2ProviderConfigInput: { salesforceOauth2ProviderConfig: { clientId: props.clientId, ...resolveClientSecretFragment(scope, props, 'Salesforce OAuth2') } }, }); } @@ -834,7 +928,7 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oauth2ProviderConfigInput: { microsoftOauth2ProviderConfig: { clientId: props.clientId, - clientSecret: props.clientSecret.unsafeUnwrap(), + ...resolveClientSecretFragment(scope, props, 'Microsoft OAuth2'), tenantId: props.tenantId, }, }, @@ -849,7 +943,7 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oAuth2CredentialProviderName: props.oAuth2CredentialProviderName, tags: props.tags, credentialProviderVendor: OAuth2CredentialProviderVendor.ATLASSIAN.value, - oauth2ProviderConfigInput: { atlassianOauth2ProviderConfig: { clientId: props.clientId, clientSecret: props.clientSecret.unsafeUnwrap() } }, + oauth2ProviderConfigInput: { atlassianOauth2ProviderConfig: { clientId: props.clientId, ...resolveClientSecretFragment(scope, props, 'Atlassian OAuth2') } }, }); } @@ -861,7 +955,7 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oAuth2CredentialProviderName: props.oAuth2CredentialProviderName, tags: props.tags, credentialProviderVendor: OAuth2CredentialProviderVendor.LINKEDIN.value, - oauth2ProviderConfigInput: { linkedinOauth2ProviderConfig: { clientId: props.clientId, clientSecret: props.clientSecret.unsafeUnwrap() } }, + oauth2ProviderConfigInput: { linkedinOauth2ProviderConfig: { clientId: props.clientId, ...resolveClientSecretFragment(scope, props, 'LinkedIn OAuth2') } }, }); } @@ -1023,6 +1117,7 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { */ public static usingCustom(scope: Construct, id: string, props: CustomOAuth2CredentialProviderProps): OAuth2CredentialProvider { assertCustomOAuth2DiscoveryXor(scope, props); + const clientSecretFragment = resolveClientSecretFragment(scope, props, 'custom OAuth2'); const oauthDiscovery: CfnOAuth2CredentialProvider.Oauth2DiscoveryProperty = props.discoveryUrl !== undefined @@ -1036,7 +1131,7 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oauth2ProviderConfigInput: { customOauth2ProviderConfig: { clientId: props.clientId, - clientSecret: props.clientSecret.unsafeUnwrap(), + ...clientSecretFragment, oauthDiscovery, }, }, From e42236bc1071783b997115d74efffcd9d3c6b407 Mon Sep 17 00:00:00 2001 From: NAVEENKUMAR MARUPALLI Date: Wed, 29 Jul 2026 04:26:42 +0530 Subject: [PATCH 2/2] Modified interface to include clientSecretConfig | handled them across SubClasses using resolveClientSecretFragment() across vendors --- .../identity/oauth2-credential-provider.ts | 54 ++++++++++++++----- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/packages/aws-cdk-lib/aws-bedrockagentcore/lib/identity/oauth2-credential-provider.ts b/packages/aws-cdk-lib/aws-bedrockagentcore/lib/identity/oauth2-credential-provider.ts index 81027b8720fcd..406b077291f84 100644 --- a/packages/aws-cdk-lib/aws-bedrockagentcore/lib/identity/oauth2-credential-provider.ts +++ b/packages/aws-cdk-lib/aws-bedrockagentcore/lib/identity/oauth2-credential-provider.ts @@ -662,7 +662,7 @@ function assertCustomOAuth2DiscoveryXor(scope: Construct, props: CustomOAuth2Cre function resolveClientSecretFragment( scope: Construct, props: OAuth2ClientCredentials, - vendorLabel: string, + vendorValue: string, ): { clientSecret?: string; clientSecretSource?: string; clientSecretConfig?: CfnOAuth2CredentialProvider.SecretReferenceProperty } { const hasSecret = props.clientSecret !== undefined; const hasConfig = props.clientSecretConfig !== undefined; @@ -670,7 +670,7 @@ function resolveClientSecretFragment( if (hasSecret && hasConfig) { throw new ValidationError( lit`ClientSecretExclusive`, - `Provide either clientSecret or clientSecretConfig for ${vendorLabel}, not both.`, + `Provide either clientSecret or clientSecretConfig for ${vendorValue} OAuth2, not both.`, scope, ); } @@ -678,7 +678,7 @@ function resolveClientSecretFragment( throw new ValidationError( lit`ClientSecretRequired`, 'Provide either clientSecret (ClientSecretSource.MANAGED) or clientSecretConfig' + - `(ClientSecretSource.EXTERNAL) for ${vendorLabel}.`, + `(ClientSecretSource.EXTERNAL) for ${vendorValue} OAuth2.`, scope, ); } @@ -818,7 +818,7 @@ function newOAuth2WithIncludedClientCredentialsOnly( oauth2ProviderConfigInput: { includedOauth2ProviderConfig: { clientId: props.clientId, - ...resolveClientSecretFragment(scope, props, `${vendor.value} OAuth2`), + ...resolveClientSecretFragment(scope, props, `${vendor.value}`), }, }, }); @@ -840,7 +840,7 @@ function newOAuth2WithIncludedTenant( oauth2ProviderConfigInput: { includedOauth2ProviderConfig: { clientId: props.clientId, - ...resolveClientSecretFragment(scope, props, `${vendor.value} OAuth2`), + ...resolveClientSecretFragment(scope, props, `${vendor.value}`), authorizationEndpoint: props.authorizationEndpoint, issuer: props.issuer, tokenEndpoint: props.tokenEndpoint, @@ -877,7 +877,12 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oAuth2CredentialProviderName: props.oAuth2CredentialProviderName, tags: props.tags, credentialProviderVendor: OAuth2CredentialProviderVendor.SLACK.value, - oauth2ProviderConfigInput: { slackOauth2ProviderConfig: { clientId: props.clientId, ...resolveClientSecretFragment(scope, props, 'Slack OAuth2') } }, + oauth2ProviderConfigInput: { + slackOauth2ProviderConfig: { + clientId: props.clientId, + ...resolveClientSecretFragment(scope, props, OAuth2CredentialProviderVendor.SLACK.value ), + }, + }, }); } @@ -889,7 +894,12 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oAuth2CredentialProviderName: props.oAuth2CredentialProviderName, tags: props.tags, credentialProviderVendor: OAuth2CredentialProviderVendor.GITHUB.value, - oauth2ProviderConfigInput: { githubOauth2ProviderConfig: { clientId: props.clientId, ...resolveClientSecretFragment(scope, props, 'GitHub OAuth2') } }, + oauth2ProviderConfigInput: { + githubOauth2ProviderConfig: { + clientId: props.clientId, + ...resolveClientSecretFragment(scope, props, OAuth2CredentialProviderVendor.GITHUB.value), + }, + }, }); } @@ -901,7 +911,12 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oAuth2CredentialProviderName: props.oAuth2CredentialProviderName, tags: props.tags, credentialProviderVendor: OAuth2CredentialProviderVendor.GOOGLE.value, - oauth2ProviderConfigInput: { googleOauth2ProviderConfig: { clientId: props.clientId, ...resolveClientSecretFragment(scope, props, 'Google OAuth2') } }, + oauth2ProviderConfigInput: { + googleOauth2ProviderConfig: { + clientId: props.clientId, + ...resolveClientSecretFragment(scope, props, OAuth2CredentialProviderVendor.GOOGLE.value ), + }, + }, }); } @@ -913,7 +928,12 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oAuth2CredentialProviderName: props.oAuth2CredentialProviderName, tags: props.tags, credentialProviderVendor: OAuth2CredentialProviderVendor.SALESFORCE.value, - oauth2ProviderConfigInput: { salesforceOauth2ProviderConfig: { clientId: props.clientId, ...resolveClientSecretFragment(scope, props, 'Salesforce OAuth2') } }, + oauth2ProviderConfigInput: { + salesforceOauth2ProviderConfig: { + clientId: props.clientId, + ...resolveClientSecretFragment(scope, props, OAuth2CredentialProviderVendor.SALESFORCE.value ), + }, + }, }); } @@ -928,7 +948,7 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oauth2ProviderConfigInput: { microsoftOauth2ProviderConfig: { clientId: props.clientId, - ...resolveClientSecretFragment(scope, props, 'Microsoft OAuth2'), + ...resolveClientSecretFragment(scope, props, OAuth2CredentialProviderVendor.MICROSOFT.value), tenantId: props.tenantId, }, }, @@ -943,7 +963,12 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oAuth2CredentialProviderName: props.oAuth2CredentialProviderName, tags: props.tags, credentialProviderVendor: OAuth2CredentialProviderVendor.ATLASSIAN.value, - oauth2ProviderConfigInput: { atlassianOauth2ProviderConfig: { clientId: props.clientId, ...resolveClientSecretFragment(scope, props, 'Atlassian OAuth2') } }, + oauth2ProviderConfigInput: { + atlassianOauth2ProviderConfig: { + clientId: props.clientId, + ...resolveClientSecretFragment(scope, props, OAuth2CredentialProviderVendor.ATLASSIAN.value ), + }, + }, }); } @@ -955,7 +980,12 @@ export class OAuth2CredentialProvider extends OAuth2CredentialProviderBase { oAuth2CredentialProviderName: props.oAuth2CredentialProviderName, tags: props.tags, credentialProviderVendor: OAuth2CredentialProviderVendor.LINKEDIN.value, - oauth2ProviderConfigInput: { linkedinOauth2ProviderConfig: { clientId: props.clientId, ...resolveClientSecretFragment(scope, props, 'LinkedIn OAuth2') } }, + oauth2ProviderConfigInput: { + linkedinOauth2ProviderConfig: { + clientId: props.clientId, + ...resolveClientSecretFragment(scope, props, OAuth2CredentialProviderVendor.LINKEDIN.value ), + }, + }, }); }