diff --git a/packages/aws-cdk-lib/aws-iam/lib/index.ts b/packages/aws-cdk-lib/aws-iam/lib/index.ts index 32908963a7e17..0545547561684 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/index.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/index.ts @@ -11,6 +11,7 @@ export * from './identity-base'; export * from './grant'; export * from './unknown-principal'; export * from './oidc-provider'; +export * from './oidc-provider2'; export * from './permissions-boundary'; export * from './saml-provider'; export * from './access-key'; diff --git a/packages/aws-cdk-lib/aws-iam/lib/oidc-provider.ts b/packages/aws-cdk-lib/aws-iam/lib/oidc-provider.ts index b8c5494038a24..b2eb16e3d4148 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/oidc-provider.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/oidc-provider.ts @@ -98,6 +98,7 @@ export interface OpenIdConnectProviderProps { * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc.html * * @resource AWS::CloudFormation::CustomResource + * @deprecated Use { @link OpenIdConnectProvider2 } instead. */ export class OpenIdConnectProvider extends Resource implements IOpenIdConnectProvider { /** diff --git a/packages/aws-cdk-lib/aws-iam/lib/oidc-provider2.ts b/packages/aws-cdk-lib/aws-iam/lib/oidc-provider2.ts new file mode 100644 index 0000000000000..9736648003d78 --- /dev/null +++ b/packages/aws-cdk-lib/aws-iam/lib/oidc-provider2.ts @@ -0,0 +1,161 @@ +import { Construct } from 'constructs'; +import { CfnOIDCProvider } from './iam.generated'; +import { Arn, IResource, Resource, Token } from '../../core'; + +/** + * Represents an IAM OpenID Connect provider. + * + */ +export interface IOpenIdConnectProvider2 extends IResource { + /** + * The Amazon Resource Name (ARN) of the IAM OpenID Connect provider. + */ + readonly openIdConnectProviderArn: string; + + /** + * The issuer for OIDC Provider + */ + readonly openIdConnectProviderIssuer: string; +} + +/** + * Initialization properties for `OpenIdConnectProvider`. + */ +export interface OpenIdConnectProvider2Props { + /** + * The URL of the identity provider. The URL must begin with https:// and + * should correspond to the iss claim in the provider's OpenID Connect ID + * tokens. Per the OIDC standard, path components are allowed but query + * parameters are not. Typically the URL consists of only a hostname, like + * https://server.example.org or https://example.com. + * + * You cannot register the same provider multiple times in a single AWS + * account. If you try to submit a URL that has already been used for an + * OpenID Connect provider in the AWS account, you will get an error. + */ + readonly url: string; + + /** + * A list of client IDs (also known as audiences). When a mobile or web app + * registers with an OpenID Connect provider, they establish a value that + * identifies the application. (This is the value that's sent as the client_id + * parameter on OAuth requests.) + * + * You can register multiple client IDs with the same provider. For example, + * you might have multiple applications that use the same OIDC provider. You + * cannot register more than 100 client IDs with a single IAM OIDC provider. + * + * Client IDs are up to 255 characters long. + * + * @default - no clients are allowed + */ + readonly clientIds?: string[]; + + /** + * A list of server certificate thumbprints for the OpenID Connect (OIDC) + * identity provider's server certificates. + * + * Typically this list includes only one entry. However, IAM lets you have up + * to five thumbprints for an OIDC provider. This lets you maintain multiple + * thumbprints if the identity provider is rotating certificates. + * + * The server certificate thumbprint is the hex-encoded SHA-1 hash value of + * the X.509 certificate used by the domain where the OpenID Connect provider + * makes its keys available. It is always a 40-character string. + * + * You must provide at least one thumbprint when creating an IAM OIDC + * provider. For example, assume that the OIDC provider is server.example.com + * and the provider stores its keys at + * https://keys.server.example.com/openid-connect. In that case, the + * thumbprint string would be the hex-encoded SHA-1 hash value of the + * certificate used by https://keys.server.example.com. + * + * @default - If no thumbprints are specified (an empty array or `undefined`), + * the thumbprint of the root certificate authority will be obtained from the + * provider's server as described in https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html + */ + readonly thumbprints?: string[]; +} + +/** + * IAM OIDC identity providers are entities in IAM that describe an external + * identity provider (IdP) service that supports the OpenID Connect (OIDC) + * standard, such as Google or Salesforce. You use an IAM OIDC identity provider + * when you want to establish trust between an OIDC-compatible IdP and your AWS + * account. This is useful when creating a mobile app or web application that + * requires access to AWS resources, but you don't want to create custom sign-in + * code or manage your own user identities. + * + * @see http://openid.net/connect + * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc.html + * + * @resource AWS::IAM::OIDCProvider + */ +export class OpenIdConnectProvider2 + extends Resource + implements IOpenIdConnectProvider2 { + /** + * Imports an Open ID connect provider from an ARN. + * @param scope The definition scope + * @param id ID of the construct + * @param openIdConnectProviderArn the ARN to import + */ + public static fromOpenIdConnectProviderArn( + scope: Construct, + id: string, + openIdConnectProviderArn: string, + ): IOpenIdConnectProvider2 { + const resourceName = Arn.extractResourceName( + openIdConnectProviderArn, + 'oidc-provider2', + ); + + class Import extends Resource implements IOpenIdConnectProvider2 { + public readonly openIdConnectProviderArn = openIdConnectProviderArn; + public readonly openIdConnectProviderIssuer = resourceName; + } + + return new Import(scope, id); + } + + /** + * The Amazon Resource Name (ARN) of the IAM OpenID Connect provider. + */ + public readonly openIdConnectProviderArn: string; + + public readonly openIdConnectProviderIssuer: string; + + /** + * The thumbprints configured for this provider. + */ + public readonly openIdConnectProviderthumbprints: string; + + /** + * Defines an OpenID Connect provider. + * @param scope The definition scope + * @param id Construct ID + * @param props Initialization properties + */ + public constructor( + scope: Construct, + id: string, + props: OpenIdConnectProvider2Props, + ) { + super(scope, id); + + const resource = new CfnOIDCProvider(this, 'Resource', { + url: props.url, + clientIdList: props.clientIds, + thumbprintList: props.thumbprints == undefined ? [] : props.thumbprints, + }); + + this.openIdConnectProviderArn = Token.asString(resource.ref); + this.openIdConnectProviderIssuer = Arn.extractResourceName( + this.openIdConnectProviderArn, + 'oidc-provider2', + ); + this.openIdConnectProviderthumbprints = Token.asString( + resource.getAtt('Thumbprints'), + ); + } +} diff --git a/packages/aws-cdk-lib/aws-iam/test/oidc-provider2.test.ts b/packages/aws-cdk-lib/aws-iam/test/oidc-provider2.test.ts new file mode 100644 index 0000000000000..1845e85089e39 --- /dev/null +++ b/packages/aws-cdk-lib/aws-iam/test/oidc-provider2.test.ts @@ -0,0 +1,128 @@ +import { Template } from '../../assertions'; +import { App, Stack, Token } from '../../core'; +import * as iam from '../lib'; + +const arnOfProvider = + 'arn:aws:iam::1234567:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/someid'; + +describe('OpenIdConnectProvider2 resource', () => { + test('minimal configuration (no clients and no thumbprint)', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new iam.OpenIdConnectProvider2(stack, 'MyProvider', { + url: 'https://openid-endpoint', + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IAM::OIDCProvider', { + Url: 'https://openid-endpoint', + }); + }); + + test('"openIdConnectProviderArn" resolves to the ref', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const provider = new iam.OpenIdConnectProvider2(stack, 'MyProvider', { + url: 'https://openid-endpoint', + }); + + // THEN + expect(stack.resolve(provider.openIdConnectProviderArn)).toStrictEqual({ + Ref: 'MyProvider730BA1C8', + }); + }); + + test('static fromOpenIdConnectProviderArn can be used to import a provider', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const provider = iam.OpenIdConnectProvider2.fromOpenIdConnectProviderArn( + stack, + 'MyProvider', + arnOfProvider, + ); + + // THEN + expect(stack.resolve(provider.openIdConnectProviderArn)).toStrictEqual( + arnOfProvider, + ); + }); + + test('thumbprint list and client ids can be specified', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new iam.OpenIdConnectProvider2(stack, 'MyProvider', { + url: 'https://my-url', + clientIds: ['client1', 'client2'], + thumbprints: ['thumb1'], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IAM::OIDCProvider', { + Url: 'https://my-url', + ClientIDList: ['client1', 'client2'], + ThumbprintList: ['thumb1'], + }); + }); +}); + +describe('OIDC issuer', () => { + test('extract issuer properly in the new provider', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const provider = new iam.OpenIdConnectProvider2(stack, 'MyProvider', { + url: 'https://my-issuer', + }); + + // THEN + expect(stack.resolve(provider.openIdConnectProviderIssuer)).toStrictEqual({ + 'Fn::Select': [ + 1, + { 'Fn::Split': [':oidc-provider/', { Ref: 'MyProvider730BA1C8' }] }, + ], + }); + }); + + test('extract issuer properly in a literal imported provider', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const provider = iam.OpenIdConnectProvider2.fromOpenIdConnectProviderArn( + stack, + 'MyProvider', + arnOfProvider, + ); + + // THEN + expect(stack.resolve(provider.openIdConnectProviderIssuer)).toStrictEqual( + 'oidc.eks.us-east-1.amazonaws.com/id/someid', + ); + }); + + test('extract issuer properly in a Token imported provider', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const provider = iam.OpenIdConnectProvider2.fromOpenIdConnectProviderArn( + stack, + 'MyProvider', + Token.asString({ Ref: 'ARN' }), + ); + + // THEN + expect(stack.resolve(provider.openIdConnectProviderIssuer)).toStrictEqual({ + 'Fn::Select': [1, { 'Fn::Split': [':oidc-provider/', { Ref: 'ARN' }] }], + }); + }); +});