From 0ef6a95b19e6001c62bbefbdf867dadcc4ab1f89 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 6 Apr 2020 13:40:14 +0200 Subject: [PATCH 1/4] fix(ecs): default Service throws in a VPC without private subnets (#7188) People like to create VPCs without private subnets, as the NAT gateways (or NAT instances) can be expensive. The default settings of an ECS Service is to create themselves in a PRIVATE subnet, instead of the "default selection order" of Private -> Isolated -> Public. Now use the default selection (by not supplying a `subnetType` at all). Fixes #7062. --- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 2 +- .../@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts | 2 +- .../aws-ecs/lib/fargate/fargate-service.ts | 2 +- .../test/fargate/test.fargate-service.ts | 28 +++++++++++++++++++ 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index b9a0acef5d314..bc8200ef16d18 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -586,7 +586,7 @@ export abstract class BaseService extends Resource // tslint:disable-next-line:max-line-length protected configureAwsVpcNetworking(vpc: ec2.IVpc, assignPublicIp?: boolean, vpcSubnets?: ec2.SubnetSelection, securityGroup?: ec2.ISecurityGroup) { if (vpcSubnets === undefined) { - vpcSubnets = { subnetType: assignPublicIp ? ec2.SubnetType.PUBLIC : ec2.SubnetType.PRIVATE }; + vpcSubnets = assignPublicIp ? { subnetType: ec2.SubnetType.PUBLIC } : {}; } if (securityGroup === undefined) { securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc }); diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index 602bb0bb6c121..4d3f3de89a628 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -33,7 +33,7 @@ export interface Ec2ServiceProps extends BaseServiceOptions { * * This property is only used for tasks that use the awsvpc network mode. * - * @default - Private subnets. + * @default - Public subnets if `assignPublicIp` is set, otherwise the first available one of Private, Isolated, Public, in that order. */ readonly vpcSubnets?: ec2.SubnetSelection; diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts index a69f14304f637..58d72a7c1ac04 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -28,7 +28,7 @@ export interface FargateServiceProps extends BaseServiceOptions { /** * The subnets to associate with the service. * - * @default - Private subnets. + * @default - Public subnets if `assignPublicIp` is set, otherwise the first available one of Private, Isolated, Public, in that order. */ readonly vpcSubnets?: ec2.SubnetSelection; diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts b/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts index 2798e690bd9d6..7cbb4888e7766 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts @@ -82,6 +82,34 @@ export = { test.done(); }, + 'can create service with default settings if VPC only has public subnets'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', { + subnetConfiguration: [ + { + cidrMask: 28, + name: 'public-only', + subnetType: ec2.SubnetType.PUBLIC + } + ] + }); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }); + + // WHEN + new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); + + // THEN -- did not throw + test.done(); + }, + 'with custom cloudmap namespace'(test: Test) { // GIVEN const stack = new cdk.Stack(); From 09852d05242fff9ba9080df9121537f81af9d131 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Mon, 6 Apr 2020 13:33:12 +0100 Subject: [PATCH 2/4] feat(cognito): user pool - OAuth2.0 authentication (#7141) Support for OAuth2.0 properties in the UserPoolClient construct. BREAKING CHANGES: The type of the `authFlow` property in `UserPoolClient` has changed. This is no longer an enum, but instead a set of fields to toggle. The list has also been updated to reflect the change from `ADMIN_NO_SRP_AUTH` to its newer alias `ADMIN_USER_PASSWORD_AUTH`. --- packages/@aws-cdk/aws-cognito/README.md | 58 ++++- .../aws-cognito/lib/user-pool-client.ts | 216 ++++++++++++++-- .../@aws-cdk/aws-cognito/lib/user-pool.ts | 26 +- ...er-pool-client-explcit-props.expected.json | 100 ++++++++ .../integ.user-pool-client-explcit-props.ts | 34 +++ .../aws-cognito/test/user-pool-client.test.ts | 237 +++++++++++++++++- .../aws-cognito/test/user-pool.test.ts | 37 ++- 7 files changed, 681 insertions(+), 27 deletions(-) create mode 100644 packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explcit-props.expected.json create mode 100644 packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explcit-props.ts diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index ac6e0510c3dc5..6c85ce75396ce 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -341,13 +341,61 @@ Client](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-sett The following code creates an app client and retrieves the client id - ```ts -const pool = new UserPool(this, 'Pool'); +const pool = new UserPool(this, 'pool'); +const client = pool.addClient('customer-app-client'); +const clientId = client.userPoolClientId; +``` + +Existing app clients can be imported into the CDK app using the `UserPoolClient.fromUserPoolClientId()` API. For new +and imported user pools, clients can also be created via the `UserPoolClient` constructor, as so - -const client = new UserPoolClient(stack, 'Client', { - userPool: pool +```ts +const importedPool = UserPool.fromUserPoolId(this, 'imported-pool', 'us-east-1_oiuR12Abd'); +new UserPoolClient(this, 'customer-app-client', { + userPool: importedPool }); +``` -const clientId = client.userPoolClientId; +Clients can be configured with authentication flows. Authentication flows allow users on a client to be authenticated +with a user pool. Cognito user pools provide several several different types of authentication, such as, SRP (Secure +Remote Password) authentication, username-and-password authentication, etc. Learn more about this at [UserPool Authentication +Flow](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html). + +The following code configures a client to use both SRP and username-and-password authentication - + +```ts +const pool = new UserPool(this, 'pool'); +pool.addClient('app-client', { + authFlows: { + userPassword: true, + userSrp: true, + } +}); ``` -Existing app clients can be imported into the CDK app using the `UserPoolClient.fromUserPoolClientId()` API. +Custom authentication protocols can be configured by setting the `custom` property under `authFlow` and defining lambda +functions for the corresponding user pool [triggers](#lambda-triggers). Learn more at [Custom Authentication +Flow](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-custom-authentication-flow). + +In addition to these authentication mechanisms, Cognito user pools also support using OAuth 2.0 framework for +authenticating users. User pool clients can be configured with OAuth 2.0 authorization flows and scopes. Learn more +about the [OAuth 2.0 authorization framework](https://tools.ietf.org/html/rfc6749) and [Cognito user pool's +implementation of +OAuth2.0](https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-user-pool-oauth-2-0-grants/). + +The following code configures an app client with the authorization code grant flow and registers the the app's welcome +page as a callback (or redirect) URL. It also configures the access token scope to 'openid'. All of these concepts can +be found in the [OAuth 2.0 RFC](https://tools.ietf.org/html/rfc6749). + +```ts +const pool = new UserPool(this, 'Pool'); +pool.addClient('app-client', { + oAuth: { + flows: { + authorizationCodeGrant: true, + }, + scopes: [ OAuthScope.OPENID ], + callbackUrls: [ 'https://my-app-domain.com/welcome' ], + } +}); +``` diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts index c8b11ab7abc19..639de30001655 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts @@ -4,39 +4,157 @@ import { IUserPool } from './user-pool'; /** * Types of authentication flow + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html */ -export enum AuthFlow { +export interface AuthFlow { /** - * Enable flow for server-side or admin authentication (no client app) + * Enable admin based user password authentication flow + * @default false */ - ADMIN_NO_SRP = 'ADMIN_NO_SRP_AUTH', + readonly adminUserPassword?: boolean; /** * Enable custom authentication flow + * @default false */ - CUSTOM_FLOW_ONLY = 'CUSTOM_AUTH_FLOW_ONLY', + readonly custom?: boolean; /** * Enable auth using username & password + * @default false + */ + readonly userPassword?: boolean; + + /** + * Enable SRP based authentication + * @default false */ - USER_PASSWORD = 'USER_PASSWORD_AUTH' + readonly userSrp?: boolean; + + /** + * Enable authflow to refresh tokens + * @default false + */ + readonly refreshToken?: boolean; +} + +/** + * OAuth settings to configure the interaction between the app and this client. + */ +export interface OAuthSettings { + + /** + * OAuth flows that are allowed with this client. + * @see - the 'Allowed OAuth Flows' section at https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-idp-settings.html + * @default - all OAuth flows disabled + */ + readonly flows: OAuthFlows; + + /** + * List of allowed redirect URLs for the identity providers. + * @default - no callback URLs + */ + readonly callbackUrls?: string[]; + + /** + * OAuth scopes that are allowed with this client. + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-idp-settings.html + * @default - no OAuth scopes are configured. + */ + readonly scopes: OAuthScope[]; +} + +/** + * Types of OAuth grant flows + * @see - the 'Allowed OAuth Flows' section at https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-idp-settings.html + */ +export interface OAuthFlows { + /** + * Initiate an authorization code grant flow, which provides an authorization code as the response. + * @default false + */ + readonly authorizationCodeGrant?: boolean; + + /** + * The client should get the access token and ID token directly. + * @default false + */ + readonly implicitCodeGrant?: boolean; + + /** + * Client should get the access token and ID token from the token endpoint + * using a combination of client and client_secret. + * @default false + */ + readonly clientCredentials?: boolean; +} + +/** + * OAuth scopes that are allowed with this client. + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-idp-settings.html + */ +export class OAuthScope { + /** + * Grants access to the 'phone_number' and 'phone_number_verified' claims. + * Automatically includes access to `OAuthScope.OPENID`. + */ + public static readonly PHONE = new OAuthScope('phone'); + + /** + * Grants access to the 'email' and 'email_verified' claims. + * Automatically includes access to `OAuthScope.OPENID`. + */ + public static readonly EMAIL = new OAuthScope('email'); + + /** + * Returns all user attributes in the ID token that are readable by the client + */ + public static readonly OPENID = new OAuthScope('openid'); + + /** + * Grants access to all user attributes that are readable by the client + * Automatically includes access to `OAuthScope.OPENID`. + */ + public static readonly PROFILE = new OAuthScope('profile'); + + /** + * Grants access to Amazon Cognito User Pool API operations that require access tokens, + * such as UpdateUserAttributes and VerifyUserAttribute. + */ + public static readonly COGNITO_ADMIN = new OAuthScope('aws.cognito.signin.user.admin'); + + /** + * Custom scope is one that you define for your own resource server in the Resource Servers. + * The format is 'resource-server-identifier/scope'. + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-define-resource-servers.html + */ + public static custom(name: string) { + return new OAuthScope(name); + } + + // tslint:disable:max-line-length + /** + * The name of this scope as recognized by CloudFormation. + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolclient.html#cfn-cognito-userpoolclient-allowedoauthscopes + */ + // tslint:enable:max-line-length + public readonly scopeName: string; + + private constructor(scopeName: string) { + this.scopeName = scopeName; + } } /** * Properties for the UserPoolClient construct */ -export interface UserPoolClientProps { +export interface UserPoolClientOptions { /** * Name of the application client * @default - cloudformation generated name */ readonly userPoolClientName?: string; - /** - * The UserPool resource this client will have access to - */ - readonly userPool: IUserPool; - /** * Whether to generate a client secret * @default false @@ -44,10 +162,27 @@ export interface UserPoolClientProps { readonly generateSecret?: boolean; /** - * List of enabled authentication flows - * @default - no enabled flows + * The set of OAuth authentication flows to enable on the client + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html + * @default - all auth flows disabled + */ + readonly authFlows?: AuthFlow; + + /** + * OAuth settings for this to client to interact with the app. + * @default - see defaults in `OAuthSettings` + */ + readonly oAuth?: OAuthSettings; +} + +/** + * Properties for the UserPoolClient construct + */ +export interface UserPoolClientProps extends UserPoolClientOptions { + /** + * The UserPool resource this client will have access to */ - readonly enabledAuthFlows?: AuthFlow[] + readonly userPool: IUserPool; } /** @@ -94,7 +229,11 @@ export class UserPoolClient extends Resource implements IUserPoolClient { clientName: props.userPoolClientName, generateSecret: props.generateSecret, userPoolId: props.userPool.userPoolId, - explicitAuthFlows: props.enabledAuthFlows + explicitAuthFlows: this.configureAuthFlows(props), + allowedOAuthFlows: this.configureOAuthFlows(props.oAuth), + allowedOAuthScopes: this.configureOAuthScopes(props.oAuth), + callbackUrLs: (props.oAuth?.callbackUrls && props.oAuth?.callbackUrls.length > 0) ? props.oAuth?.callbackUrls : undefined, + allowedOAuthFlowsUserPoolClient: props.oAuth ? true : undefined, }); this.userPoolClientId = resource.ref; @@ -111,4 +250,51 @@ export class UserPoolClient extends Resource implements IUserPoolClient { } return this._userPoolClientName; } + + private configureAuthFlows(props: UserPoolClientProps): string[] | undefined { + const authFlows: string[] = []; + if (props.authFlows?.userPassword) { authFlows.push('ALLOW_USER_PASSWORD_AUTH'); } + if (props.authFlows?.adminUserPassword) { authFlows.push('ALLOW_ADMIN_USER_PASSWORD_AUTH'); } + if (props.authFlows?.custom) { authFlows.push('ALLOW_CUSTOM_AUTH'); } + if (props.authFlows?.userSrp) { authFlows.push('ALLOW_USER_SRP_AUTH'); } + if (props.authFlows?.refreshToken) { authFlows.push('ALLOW_REFRESH_TOKEN_AUTH'); } + + if (authFlows.length === 0) { + return undefined; + } + return authFlows; + } + + private configureOAuthFlows(oAuth?: OAuthSettings): string[] | undefined { + if (oAuth?.flows.authorizationCodeGrant || oAuth?.flows.implicitCodeGrant) { + if (oAuth?.callbackUrls === undefined || oAuth?.callbackUrls.length === 0) { + throw new Error('callbackUrl must be specified when codeGrant or implicitGrant OAuth flows are enabled.'); + } + if (oAuth?.flows.clientCredentials) { + throw new Error('clientCredentials OAuth flow cannot be selected along with codeGrant or implicitGrant.'); + } + } + + const oAuthFlows: string[] = []; + if (oAuth?.flows.clientCredentials) { oAuthFlows.push('client_credentials'); } + if (oAuth?.flows.implicitCodeGrant) { oAuthFlows.push('implicit'); } + if (oAuth?.flows.authorizationCodeGrant) { oAuthFlows.push('code'); } + + if (oAuthFlows.length === 0) { + return undefined; + } + return oAuthFlows; + } + + private configureOAuthScopes(oAuth?: OAuthSettings): string[] | undefined { + const oAuthScopes = new Set(oAuth?.scopes.map((x) => x.scopeName)); + const autoOpenIdScopes = [ OAuthScope.PHONE, OAuthScope.EMAIL, OAuthScope.PROFILE ]; + if (autoOpenIdScopes.reduce((agg, s) => agg || oAuthScopes.has(s.scopeName), false)) { + oAuthScopes.add(OAuthScope.OPENID.scopeName); + } + if (oAuthScopes.size > 0) { + return Array.from(oAuthScopes); + } + return undefined; + } } diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index 3c2b870d85d33..4185047182435 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -3,6 +3,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, Duration, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; import { CfnUserPool } from './cognito.generated'; import { ICustomAttribute, RequiredAttributes } from './user-pool-attr'; +import { IUserPoolClient, UserPoolClient, UserPoolClientOptions } from './user-pool-client'; /** * The different ways in which users of this pool can sign up or sign in. @@ -515,6 +516,11 @@ export interface IUserPool extends IResource { * @attribute */ readonly userPoolArn: string; + + /** + * Create a user pool client. + */ + addClient(id: string, options?: UserPoolClientOptions): IUserPoolClient; } /** @@ -532,6 +538,13 @@ export class UserPool extends Resource implements IUserPool { resource: 'userpool', resourceName: userPoolId, }); + + public addClient(clientId: string, options?: UserPoolClientOptions): IUserPoolClient { + return new UserPoolClient(this, clientId, { + userPool: this, + ...options, + }); + } } return new Import(scope, id); } @@ -540,11 +553,7 @@ export class UserPool extends Resource implements IUserPool { * Import an existing user pool based on its ARN. */ public static fromUserPoolArn(scope: Construct, id: string, userPoolArn: string): IUserPool { - class Import extends Resource implements IUserPool { - public readonly userPoolArn = userPoolArn; - public readonly userPoolId = Stack.of(this).parseArn(userPoolArn).resourceName!; - } - return new Import(scope, id); + return UserPool.fromUserPoolId(scope, id, Stack.of(scope).parseArn(userPoolArn).resourceName!); } /** @@ -649,6 +658,13 @@ export class UserPool extends Resource implements IUserPool { (this.triggers as any)[operation.operationName] = fn.functionArn; } + public addClient(id: string, options?: UserPoolClientOptions): IUserPoolClient { + return new UserPoolClient(this, id, { + userPool: this, + ...options + }); + } + private addLambdaPermission(fn: lambda.IFunction, name: string): void { const capitalize = name.charAt(0).toUpperCase() + name.slice(1); fn.addPermission(`${capitalize}Cognito`, { diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explcit-props.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explcit-props.expected.json new file mode 100644 index 0000000000000..f3da0535c775d --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explcit-props.expected.json @@ -0,0 +1,100 @@ +{ + "Resources": { + "myuserpoolsmsRole0E16FDD9": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "sts:ExternalId": "integuserpoolclientexplicitpropsmyuserpoolFC6541FF" + } + }, + "Effect": "Allow", + "Principal": { + "Service": "cognito-idp.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "sns-publish" + } + ] + } + }, + "myuserpool01998219": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "AdminCreateUserConfig": { + "AllowAdminCreateUserOnly": true + }, + "EmailVerificationMessage": "Hello {username}, Your verification code is {####}", + "EmailVerificationSubject": "Verify your new account", + "SmsConfiguration": { + "ExternalId": "integuserpoolclientexplicitpropsmyuserpoolFC6541FF", + "SnsCallerArn": { + "Fn::GetAtt": [ + "myuserpoolsmsRole0E16FDD9", + "Arn" + ] + } + }, + "SmsVerificationMessage": "The verification code to your new account is {####}", + "VerificationMessageTemplate": { + "DefaultEmailOption": "CONFIRM_WITH_CODE", + "EmailMessage": "Hello {username}, Your verification code is {####}", + "EmailSubject": "Verify your new account", + "SmsMessage": "The verification code to your new account is {####}" + } + } + }, + "myuserpoolmyuserpoolclientAFB2274E": { + "Type": "AWS::Cognito::UserPoolClient", + "Properties": { + "UserPoolId": { + "Ref": "myuserpool01998219" + }, + "AllowedOAuthFlows": [ + "implicit", + "code" + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": [ + "phone", + "email", + "openid", + "profile", + "aws.cognito.signin.user.admin", + "my-resource-server/my-scope" + ], + "CallbackURLs": [ + "https://redirect-here.myapp.com" + ], + "ClientName": "myuserpoolclient", + "ExplicitAuthFlows": [ + "ALLOW_USER_PASSWORD_AUTH", + "ALLOW_ADMIN_USER_PASSWORD_AUTH", + "ALLOW_CUSTOM_AUTH", + "ALLOW_USER_SRP_AUTH", + "ALLOW_REFRESH_TOKEN_AUTH" + ], + "GenerateSecret": true + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explcit-props.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explcit-props.ts new file mode 100644 index 0000000000000..4870ab2276738 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explcit-props.ts @@ -0,0 +1,34 @@ +import { App, Stack } from '@aws-cdk/core'; +import { OAuthScope, UserPool } from '../lib'; + +const app = new App(); +const stack = new Stack(app, 'integ-user-pool-client-explicit-props'); + +const userpool = new UserPool(stack, 'myuserpool'); + +userpool.addClient('myuserpoolclient', { + userPoolClientName: 'myuserpoolclient', + authFlows: { + adminUserPassword: true, + custom: true, + refreshToken: true, + userPassword: true, + userSrp: true, + }, + generateSecret: true, + oAuth: { + flows: { + implicitCodeGrant: true, + authorizationCodeGrant: true, + }, + scopes: [ + OAuthScope.PHONE, + OAuthScope.EMAIL, + OAuthScope.OPENID, + OAuthScope.PROFILE, + OAuthScope.COGNITO_ADMIN, + OAuthScope.custom('my-resource-server/my-scope'), + ], + callbackUrls: [ 'https://redirect-here.myapp.com' ], + }, +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts index 278ef92cc45a6..15ae1f90a3270 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts @@ -1,6 +1,7 @@ +import { ABSENT } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import { Stack } from '@aws-cdk/core'; -import { UserPool, UserPoolClient } from '../lib'; +import { OAuthScope, UserPool, UserPoolClient } from '../lib'; describe('User Pool Client', () => { test('default setup', () => { @@ -48,4 +49,238 @@ describe('User Pool Client', () => { // THEN expect(client.userPoolClientId).toEqual('client-id-1'); }); + + test('ExplicitAuthFlows is absent by default', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + pool.addClient('Client'); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolClient', { + ExplicitAuthFlows: ABSENT, + }); + }); + + test('ExplicitAuthFlows are correctly named', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + pool.addClient('Client', { + authFlows: { + adminUserPassword: true, + custom: true, + refreshToken: true, + userPassword: true, + userSrp: true, + } + }); + + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolClient', { + ExplicitAuthFlows: [ + 'ALLOW_USER_PASSWORD_AUTH', + 'ALLOW_ADMIN_USER_PASSWORD_AUTH', + 'ALLOW_CUSTOM_AUTH', + 'ALLOW_USER_SRP_AUTH', + 'ALLOW_REFRESH_TOKEN_AUTH', + ], + }); + }); + + test('AllowedOAuthFlows is absent by default', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + pool.addClient('Client'); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolClient', { + AllowedOAuthFlows: ABSENT, + // AllowedOAuthFlowsUserPoolClient: ABSENT, + }); + }); + + test('AllowedOAuthFlows are correctly named', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + pool.addClient('Client1', { + oAuth: { + flows: { + authorizationCodeGrant: true, + implicitCodeGrant: true, + }, + callbackUrls: [ 'redirect-url' ], + scopes: [ OAuthScope.PHONE ], + }, + }); + pool.addClient('Client2', { + oAuth: { + flows: { + clientCredentials: true, + }, + callbackUrls: [ 'redirect-url' ], + scopes: [ OAuthScope.PHONE ], + }, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolClient', { + AllowedOAuthFlows: [ 'implicit', 'code' ], + AllowedOAuthFlowsUserPoolClient: true, + }); + + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolClient', { + AllowedOAuthFlows: [ 'client_credentials' ], + AllowedOAuthFlowsUserPoolClient: true, + }); + }); + + test('fails when callbackUrls are not specified for codeGrant or implicitGrant', () => { + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + expect(() => pool.addClient('Client1', { + oAuth: { + flows: { authorizationCodeGrant: true }, + scopes: [ OAuthScope.PHONE ], + } + })).toThrow(/callbackUrl must be specified/); + + expect(() => pool.addClient('Client2', { + oAuth: { + flows: { implicitCodeGrant: true }, + scopes: [ OAuthScope.PHONE ], + }, + })).toThrow(/callbackUrl must be specified/); + + expect(() => pool.addClient('Client3', { + oAuth: { + flows: { clientCredentials: true }, + scopes: [ OAuthScope.PHONE ], + } + })).not.toThrow(); + }); + + test('fails when clientCredentials OAuth flow is selected along with codeGrant or implicitGrant', () => { + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + expect(() => pool.addClient('Client1', { + oAuth: { + flows: { + authorizationCodeGrant: true, + clientCredentials: true, + }, + callbackUrls: [ 'redirect-url' ], + scopes: [ OAuthScope.PHONE ], + }, + })).toThrow(/clientCredentials OAuth flow cannot be selected/); + + expect(() => pool.addClient('Client2', { + oAuth: { + flows: { + implicitCodeGrant: true, + clientCredentials: true, + }, + callbackUrls: [ 'redirect-url' ], + scopes: [ OAuthScope.PHONE ], + }, + })).toThrow(/clientCredentials OAuth flow cannot be selected/); + }); + + test('OAuth scopes', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + pool.addClient('Client', { + oAuth: { + flows: { clientCredentials: true, }, + scopes: [ + OAuthScope.PHONE, + OAuthScope.EMAIL, + OAuthScope.OPENID, + OAuthScope.PROFILE, + OAuthScope.COGNITO_ADMIN, + OAuthScope.custom('my-resource-server/my-own-scope'), + ], + }, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolClient', { + AllowedOAuthScopes: [ + 'phone', + 'email', + 'openid', + 'profile', + 'aws.cognito.signin.user.admin', + 'my-resource-server/my-own-scope' + ], + }); + }); + + test('OAuthScope - openid is included when email or phone is specified', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + pool.addClient('Client1', { + userPoolClientName: 'Client1', + oAuth: { + flows: { clientCredentials: true, }, + scopes: [ OAuthScope.PHONE, ], + }, + }); + pool.addClient('Client2', { + userPoolClientName: 'Client2', + oAuth: { + flows: { clientCredentials: true, }, + scopes: [ OAuthScope.EMAIL, ], + }, + }); + pool.addClient('Client3', { + userPoolClientName: 'Client3', + oAuth: { + flows: { clientCredentials: true, }, + scopes: [ OAuthScope.PROFILE, ], + }, + }); + pool.addClient('Client4', { + userPoolClientName: 'Client4', + oAuth: { + flows: { clientCredentials: true, }, + scopes: [ OAuthScope.COGNITO_ADMIN, ], + }, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolClient', { + ClientName: 'Client1', + AllowedOAuthScopes: [ 'phone', 'openid', ], + }); + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolClient', { + ClientName: 'Client2', + AllowedOAuthScopes: [ 'email', 'openid', ], + }); + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolClient', { + ClientName: 'Client3', + AllowedOAuthScopes: [ 'profile', 'openid', ], + }); + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolClient', { + ClientName: 'Client4', + AllowedOAuthScopes: [ 'aws.cognito.signin.user.admin' ], + }); + }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts index 696ec8dfc5f80..7a997b5bc3554 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts @@ -227,7 +227,17 @@ describe('User Pool', () => { // WHEN const pool = UserPool.fromUserPoolArn(stack, 'userpool', userPoolArn); expect(pool.userPoolId).toEqual('test-user-pool'); - expect(pool.userPoolArn).toEqual(userPoolArn); + expect(stack.resolve(pool.userPoolArn)).toEqual({ + 'Fn::Join': [ '', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':cognito-idp:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':userpool/test-user-pool' + ] ] + }); }); test('support tags', () => { @@ -753,6 +763,31 @@ describe('User Pool', () => { }); }); +test('addClient', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const userpool = new UserPool(stack, 'Pool'); + userpool.addClient('UserPoolClient', { + userPoolClientName: 'userpoolclient' + }); + const imported = UserPool.fromUserPoolId(stack, 'imported', 'imported-userpool-id'); + imported.addClient('UserPoolImportedClient', { + userPoolClientName: 'userpoolimportedclient' + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolClient', { + ClientName: 'userpoolclient', + UserPoolId: stack.resolve(userpool.userPoolId), + }); + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolClient', { + ClientName: 'userpoolimportedclient', + UserPoolId: stack.resolve(imported.userPoolId), + }); +}); + function fooFunction(scope: Construct, name: string): lambda.IFunction { return new lambda.Function(scope, name, { functionName: name, From d30e0d11520128b5eb258a2a8c6657da68f36ef9 Mon Sep 17 00:00:00 2001 From: Shiv Lakshminarayan Date: Mon, 6 Apr 2020 06:04:37 -0700 Subject: [PATCH 3/4] chore(kinesis): some minor cleanup, adding a test (#7135) Since we infer the intent was to use `KMS` if an encryption key was provided, we no longer need the validation block. Also some minor cleanup so we don't return undefined --- packages/@aws-cdk/aws-kinesis/lib/stream.ts | 7 ++----- .../@aws-cdk/aws-kinesis/test/test.stream.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-kinesis/lib/stream.ts b/packages/@aws-cdk/aws-kinesis/lib/stream.ts index ff58a8b73d5f0..480446a1ab969 100644 --- a/packages/@aws-cdk/aws-kinesis/lib/stream.ts +++ b/packages/@aws-cdk/aws-kinesis/lib/stream.ts @@ -331,15 +331,12 @@ export class Stream extends StreamBase { } if (encryptionType === StreamEncryption.UNENCRYPTED) { - return { streamEncryption: undefined, encryptionKey: undefined }; + return { }; } if (encryptionType === StreamEncryption.MANAGED) { const encryption = { encryptionType: 'KMS', keyId: 'alias/aws/kinesis'}; - return { - streamEncryption: encryption, - encryptionKey: undefined - }; + return { streamEncryption: encryption }; } if (encryptionType === StreamEncryption.KMS) { diff --git a/packages/@aws-cdk/aws-kinesis/test/test.stream.ts b/packages/@aws-cdk/aws-kinesis/test/test.stream.ts index 943d8672cbfb4..12ac660be6774 100644 --- a/packages/@aws-cdk/aws-kinesis/test/test.stream.ts +++ b/packages/@aws-cdk/aws-kinesis/test/test.stream.ts @@ -258,6 +258,7 @@ export = { test.done(); }, + 'retention period must be between 24 and 168 hours'(test: Test) { test.throws(() => { new Stream(new Stack(), 'MyStream', { @@ -303,6 +304,21 @@ export = { test.done(); }, + 'encryption key cannot be supplied with UNENCRYPTED as the encryption type'(test: Test) { + + const stack = new Stack(); + const key = new kms.Key(stack, 'myKey'); + + test.throws(() => { + new Stream(stack, 'MyStream', { + encryptionKey: key, + encryption: StreamEncryption.UNENCRYPTED + }); + }, /encryptionKey is specified, so 'encryption' must be set to KMS/); + + test.done(); + }, + 'if a KMS key is supplied, use KMS as the encryption type'(test: Test) { // GIVEN const stack = new Stack(); @@ -409,6 +425,7 @@ export = { test.done(); }, + 'uses explicit KMS key if encryption type is KMS and a key is provided'(test: Test) { const stack = new Stack(); @@ -493,6 +510,7 @@ export = { test.done(); }, + permissions: { 'with encryption': { 'grantRead creates and attaches a policy with read only access to Stream and EncryptionKey'(test: Test) { From 6f00783c4ffafe7a74609a76544232689b9cca1b Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 6 Apr 2020 15:36:50 +0200 Subject: [PATCH 4/4] fix(events): Batch target does not work (#7191) The AWS Batch Event Rule target recently added actually did not emit BatchParameters, and so did not work. Also, the permission set was wrong. Unclear why this passed integration test previously, maybe the service has added validation in the mean time. Fixes #7137. --- .../@aws-cdk/aws-events-targets/lib/batch.ts | 36 +-- .../test/batch/batch.test.ts | 46 +-- .../integ.job-definition-events.expected.json | 304 +++++++++--------- packages/@aws-cdk/aws-events/lib/rule.ts | 1 + 4 files changed, 189 insertions(+), 198 deletions(-) diff --git a/packages/@aws-cdk/aws-events-targets/lib/batch.ts b/packages/@aws-cdk/aws-events-targets/lib/batch.ts index b21f234b82cae..26e5de92dcd90 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/batch.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/batch.ts @@ -32,6 +32,13 @@ export interface BatchJobProps { * @default no retryStrategy is set */ readonly attempts?: number; + + /** + * The name of the submitted job + * + * @default - Automatically generated + */ + readonly jobName?: string; } /** @@ -49,33 +56,26 @@ export class BatchJob implements events.IRuleTarget { * Returns a RuleTarget that can be used to trigger queue this batch job as a * result from a CloudWatch event. */ - public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig { - const baseBatchParameters: any = { + public bind(rule: events.IRule, _id?: string): events.RuleTargetConfig { + const batchParameters: events.CfnRule.BatchParametersProperty = { jobDefinition: this.jobDefinition.jobDefinitionArn, - jobName: this.jobDefinition.jobDefinitionName + jobName: this.props.jobName ?? rule.node.uniqueId, + arrayProperties: this.props.size ? { size: this.props.size } : undefined, + retryStrategy: this.props.attempts ? { attempts: this.props.attempts } : undefined, }; - if (this.props.size) { - baseBatchParameters.arrayProperties = { - size: this.props.size - }; - } - - if (this.props.attempts) { - baseBatchParameters.retryStrategy = { - attempts: this.props.attempts - }; - } - - const batchParameters: events.CfnRule.BatchParametersProperty = baseBatchParameters; - return { id: '', arn: this.jobQueue.jobQueueArn, + // When scoping resource-level access for job submission, you must provide both job queue and job definition resource types. + // https://docs.aws.amazon.com/batch/latest/userguide/ExamplePolicies_BATCH.html#iam-example-restrict-job-def role: singletonEventRole(this.jobDefinition, [ new iam.PolicyStatement({ actions: ['batch:SubmitJob'], - resources: [this.jobDefinition.jobDefinitionArn] + resources: [ + this.jobDefinition.jobDefinitionArn, + this.jobQueue.jobQueueArn, + ] }) ]), input: this.props.event, diff --git a/packages/@aws-cdk/aws-events-targets/test/batch/batch.test.ts b/packages/@aws-cdk/aws-events-targets/test/batch/batch.test.ts index a11fd2fd3cc77..cde0b9e87e7ff 100644 --- a/packages/@aws-cdk/aws-events-targets/test/batch/batch.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/batch/batch.test.ts @@ -45,35 +45,11 @@ test('use aws batch job as an eventrule target', () => { 'MyJobEventsRoleCF43C336', 'Arn' ] - } - } - ] - })); - expect(stack).to(haveResource('AWS::IAM::Role', { - AssumeRolePolicyDocument: { - Statement: [ - { - Action: 'sts:AssumeRole', - Effect: 'Allow', - Principal: { - Service: 'batch.amazonaws.com' - } - } - ], - Version: '2012-10-17' - }, - ManagedPolicyArns: [ - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition' - }, - ':iam::aws:policy/service-role/AWSBatchServiceRole' - ] - ] + }, + BatchParameters: { + JobDefinition: { Ref: 'MyJob8719E923' }, + JobName: 'Rule' + }, } ] })); @@ -84,18 +60,16 @@ test('use aws batch job as an eventrule target', () => { { Action: 'batch:SubmitJob', Effect: 'Allow', - Resource: { - Ref: 'MyJob8719E923' - } + Resource: [ + { Ref: 'MyJob8719E923' }, + { Ref: 'MyQueueE6CA6235' } + ], } ], Version: '2012-10-17' }, - PolicyName: 'MyJobEventsRoleDefaultPolicy7266D3A7', Roles: [ - { - Ref: 'MyJobEventsRoleCF43C336' - } + { Ref: 'MyJobEventsRoleCF43C336' } ] })); }); diff --git a/packages/@aws-cdk/aws-events-targets/test/batch/integ.job-definition-events.expected.json b/packages/@aws-cdk/aws-events-targets/test/batch/integ.job-definition-events.expected.json index ca10cd8e713b9..9319f72814c38 100644 --- a/packages/@aws-cdk/aws-events-targets/test/batch/integ.job-definition-events.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/batch/integ.job-definition-events.expected.json @@ -1,163 +1,179 @@ { - "Resources": { - "MyJob8719E923": { - "Type": "AWS::Batch::JobDefinition", - "Properties": { - "ContainerProperties": { - "Image": "test-repo", - "Memory": 4, - "Privileged": false, - "ReadonlyRootFilesystem": false, - "Vcpus": 1 - }, - "RetryStrategy": { - "Attempts": 1 - }, - "Timeout": {}, - "Type": "container" - } - }, - "MyQueueE6CA6235": { - "Type": "AWS::Batch::JobQueue", - "Properties": { - "ComputeEnvironmentOrder": [ + "Resources": { + "ComputeEnvironmentResourceServiceInstanceRoleDC6D4445": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ { - "ComputeEnvironment": { - "Ref": "ComputeEnvironmentC570994D" - }, - "Order": 1 + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "batch.amazonaws.com" + } } ], - "Priority": 1, - "State": "ENABLED" - } - }, - "ComputeEnvironmentC570994D": { - "Type": "AWS::Batch::ComputeEnvironment", - "Properties": { - "ServiceRole": { - "Fn::GetAtt": [ - "ComputeEnvironmentResourceServiceInstanceRoleDC6D4445", - "Arn" + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSBatchServiceRole" + ] ] - }, - "State": "ENABLED", - "Type": "UNMANAGED" - } - }, - "MyJobEventsRoleCF43C336": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "events.amazonaws.com" - } - } - ], - "Version": "2012-10-17" } - } - }, - "ComputeEnvironmentResourceServiceInstanceRoleDC6D4445": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "batch.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ + ] + } + }, + "ComputeEnvironmentC570994D": { + "Type": "AWS::Batch::ComputeEnvironment", + "Properties": { + "ServiceRole": { + "Fn::GetAtt": [ + "ComputeEnvironmentResourceServiceInstanceRoleDC6D4445", + "Arn" + ] + }, + "Type": "UNMANAGED", + "State": "ENABLED" + } + }, + "MyQueueE6CA6235": { + "Type": "AWS::Batch::JobQueue", + "Properties": { + "ComputeEnvironmentOrder": [ + { + "ComputeEnvironment": { + "Ref": "ComputeEnvironmentC570994D" + }, + "Order": 1 + } + ], + "Priority": 1, + "State": "ENABLED" + } + }, + "MyJob8719E923": { + "Type": "AWS::Batch::JobDefinition", + "Properties": { + "Type": "container", + "ContainerProperties": { + "Image": "test-repo", + "Memory": 4, + "Privileged": false, + "ReadonlyRootFilesystem": false, + "Vcpus": 1 + }, + "RetryStrategy": { + "Attempts": 1 + }, + "Timeout": {} + } + }, + "MyJobEventsRoleCF43C336": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSBatchServiceRole" - ] - ] + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + } } - ] + ], + "Version": "2012-10-17" } - }, - "MyJobEventsRoleDefaultPolicy7266D3A7": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "batch:SubmitJob", - "Effect": "Allow", - "Resource": { + } + }, + "MyJobEventsRoleDefaultPolicy7266D3A7": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "batch:SubmitJob", + "Effect": "Allow", + "Resource": [ + { "Ref": "MyJob8719E923" + }, + { + "Ref": "MyQueueE6CA6235" } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "MyJobEventsRoleDefaultPolicy7266D3A7", - "Roles": [ - { - "Ref": "MyJobEventsRoleCF43C336" + ] } - ] - } - }, - "TimerBF6F831F": { - "Type": "AWS::Events::Rule", - "Properties": { - "ScheduleExpression": "rate(1 minute)", - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Ref": "MyQueueE6CA6235" + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyJobEventsRoleDefaultPolicy7266D3A7", + "Roles": [ + { + "Ref": "MyJobEventsRoleCF43C336" + } + ] + } + }, + "TimerBF6F831F": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Ref": "MyQueueE6CA6235" + }, + "BatchParameters": { + "JobDefinition": { + "Ref": "MyJob8719E923" }, - "Id": "Target0", - "RoleArn": { - "Fn::GetAtt": [ - "MyJobEventsRoleCF43C336", - "Arn" - ] - } + "JobName": "batcheventsTimer32212B30" + }, + "Id": "Target0", + "RoleArn": { + "Fn::GetAtt": [ + "MyJobEventsRoleCF43C336", + "Arn" + ] } - ] - } - }, - "Timer2B6F162E9": { - "Type": "AWS::Events::Rule", - "Properties": { - "ScheduleExpression": "rate(2 minutes)", - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Ref": "MyQueueE6CA6235" + } + ] + } + }, + "Timer2B6F162E9": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(2 minutes)", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Ref": "MyQueueE6CA6235" + }, + "BatchParameters": { + "JobDefinition": { + "Ref": "MyJob8719E923" }, - "Id": "Target0", - "RoleArn": { - "Fn::GetAtt": [ - "MyJobEventsRoleCF43C336", - "Arn" - ] - } + "JobName": "batcheventsTimer232549135" + }, + "Id": "Target0", + "RoleArn": { + "Fn::GetAtt": [ + "MyJobEventsRoleCF43C336", + "Arn" + ] } - ] - } + } + ] } } } - \ No newline at end of file +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index dc0a29277bab6..5272350296e1f 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -283,6 +283,7 @@ export class Rule extends Resource implements IRule { ecsParameters: targetProps.ecsParameters, kinesisParameters: targetProps.kinesisParameters, runCommandParameters: targetProps.runCommandParameters, + batchParameters: targetProps.batchParameters, sqsParameters: targetProps.sqsParameters, input: inputProps && inputProps.input, inputPath: inputProps && inputProps.inputPath,