diff --git a/design/aws-guidelines.md b/design/aws-guidelines.md index dfb1a27ee8458..c634976f4ad0e 100644 --- a/design/aws-guidelines.md +++ b/design/aws-guidelines.md @@ -326,9 +326,9 @@ export interface IFoo extends cdk.IConstruct, ISomething { readonly connections: ec2.Connections; // permission grants (adds statements to the principal's policy) - grant(principal?: iam.IPrincipal, ...actions: string[]): void; - grantFoo(principal?: iam.IPrincipal): void; - grantBar(principal?: iam.IPrincipal): void; + grant(grantee?: iam.IGrantable, ...actions: string[]): void; + grantFoo(grantee?: iam.IGrantable): void; + grantBar(grantee?: iam.IGrantable): void; // resource policy (if applicable) addToResourcePolicy(statement: iam.PolicyStatement): void; @@ -364,7 +364,7 @@ export abstract class FooBase extends cdk.Construct implements IFoo { public abstract export(): FooAttributes; // grants can usually be shared - public grantYyy(principal?: iam.IPrincipal) { + public grantYyy(grantee?: iam.IGrantable) { // ... } diff --git a/package.json b/package.json index 1d7a4c13b30aa..ad3efdffe367f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "include": "dependencies/node-version" }, "scripts": { - "pkglint": "tools/pkglint/bin/pkglint -f ." + "pkglint": "tools/pkglint/bin/pkglint -f .", + "build-all": "tsc -b" }, "devDependencies": { "@types/node": "8.10.40", diff --git a/packages/@aws-cdk/assets-docker/test/test.image-asset.ts b/packages/@aws-cdk/assets-docker/test/test.image-asset.ts index de211f535fc39..ed0b5ae2b51c9 100644 --- a/packages/@aws-cdk/assets-docker/test/test.image-asset.ts +++ b/packages/@aws-cdk/assets-docker/test/test.image-asset.ts @@ -61,22 +61,13 @@ export = { ":", { "Ref": "AWS::AccountId" }, ":repository/", - { - "Fn::GetAtt": [ - "ImageAdoptRepositoryE1E84E35", - "RepositoryName" - ] - } + { "Fn::GetAtt": [ "ImageAdoptRepositoryE1E84E35", "RepositoryName" ] } ] ] } }, { - "Action": [ - "ecr:GetAuthorizationToken", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], + "Action": "ecr:GetAuthorizationToken", "Effect": "Allow", "Resource": "*" } diff --git a/packages/@aws-cdk/assets/lib/asset.ts b/packages/@aws-cdk/assets/lib/asset.ts index 378c73a2da87d..d985af72954b7 100644 --- a/packages/@aws-cdk/assets/lib/asset.ts +++ b/packages/@aws-cdk/assets/lib/asset.ts @@ -36,7 +36,7 @@ export interface GenericAssetProps { * A list of principals that should be able to read this asset from S3. * You can use `asset.grantRead(principal)` to grant read permissions later. */ - readonly readers?: iam.IPrincipal[]; + readonly readers?: iam.IGrantable[]; } /** @@ -171,12 +171,12 @@ export class Asset extends cdk.Construct { /** * Grants read permissions to the principal on the asset's S3 object. */ - public grantRead(principal?: iam.IPrincipal) { + public grantRead(grantee: iam.IGrantable) { // We give permissions on all files with the same prefix. Presumably // different versions of the same file will have the same prefix // and we don't want to accidentally revoke permission on old versions // when deploying a new version. - this.bucket.grantRead(principal, `${this.s3Prefix}*`); + this.bucket.grantRead(grantee, `${this.s3Prefix}*`); } } @@ -190,7 +190,7 @@ export interface FileAssetProps { * A list of principals that should be able to read this file asset from S3. * You can use `asset.grantRead(principal)` to grant read permissions later. */ - readonly readers?: iam.IPrincipal[]; + readonly readers?: iam.IGrantable[]; } /** @@ -212,7 +212,7 @@ export interface ZipDirectoryAssetProps { * A list of principals that should be able to read this ZIP file from S3. * You can use `asset.grantRead(principal)` to grant read permissions later. */ - readonly readers?: iam.IPrincipal[]; + readonly readers?: iam.IGrantable[]; } /** diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts index 3bacddc206d59..08e401470e925 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts @@ -85,14 +85,14 @@ export class Metric { /** * Grant permissions to the given identity to write metrics. * - * @param identity The IAM identity to give permissions to. + * @param grantee The IAM identity to give permissions to. */ - public static grantPutMetricData(identity?: iam.IPrincipal) { - if (!identity) { return; } - - identity.addToPolicy(new iam.PolicyStatement() - .addAllResources() - .addAction("cloudwatch:PutMetricData")); + public static grantPutMetricData(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions: ['cloudwatch:PutMetricData'], + resourceArns: ['*'] + }); } public readonly dimensions?: DimensionHash; diff --git a/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts b/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts index c85c79c7b4cc6..53c9e835a2766 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts @@ -130,7 +130,7 @@ export class S3BucketBuildArtifacts extends BuildArtifacts { * @internal */ public _bind(project: Project) { - this.props.bucket.grantReadWrite(project.role); + this.props.bucket.grantReadWrite(project); } protected toArtifactsProperty(): any { diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index a891b5400780f..e6bc958412eb9 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -16,7 +16,7 @@ const CODEPIPELINE_TYPE = 'CODEPIPELINE'; const S3_BUCKET_ENV = 'SCRIPT_S3_BUCKET'; const S3_KEY_ENV = 'SCRIPT_S3_KEY'; -export interface IProject extends cdk.IConstruct, events.IEventRuleTarget { +export interface IProject extends cdk.IConstruct, events.IEventRuleTarget, iam.IGrantable { /** The ARN of this Project. */ readonly projectArn: string; @@ -156,13 +156,15 @@ export interface ProjectImportProps { * use the {@link import} method. */ export abstract class ProjectBase extends cdk.Construct implements IProject { + public abstract readonly grantPrincipal: iam.IPrincipal; + /** The ARN of this Project. */ public abstract readonly projectArn: string; /** The human-visible name of this Project. */ public abstract readonly projectName: string; - /** The IAM service Role of this Project. Undefined for imported Projects. */ + /** The IAM service Role of this Project. */ public abstract readonly role?: iam.IRole; /** A role used by CloudWatch events to trigger a build */ @@ -369,6 +371,7 @@ export abstract class ProjectBase extends cdk.Construct implements IProject { } class ImportedProject extends ProjectBase { + public readonly grantPrincipal: iam.IPrincipal; public readonly projectArn: string; public readonly projectName: string; public readonly role?: iam.Role = undefined; @@ -381,6 +384,7 @@ class ImportedProject extends ProjectBase { resource: 'project', resourceName: props.projectName, }); + this.grantPrincipal = new iam.ImportedResourcePrincipal({ resource: this }); this.projectName = props.projectName; } @@ -572,6 +576,8 @@ export class Project extends ProjectBase { return new ImportedProject(scope, id, props); } + public readonly grantPrincipal: iam.IPrincipal; + /** * The IAM role for this project. */ @@ -603,6 +609,7 @@ export class Project extends ProjectBase { this.role = props.role || new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com') }); + this.grantPrincipal = this.role; let cache: CfnProject.ProjectCacheProperty | undefined; if (props.cacheBucket) { diff --git a/packages/@aws-cdk/aws-codebuild/lib/source.ts b/packages/@aws-cdk/aws-codebuild/lib/source.ts index c847d6acbdf62..3a93de4b3806a 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/source.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/source.ts @@ -167,7 +167,7 @@ export class S3BucketSource extends BuildSource { * @internal */ public _bind(project: Project) { - this.bucket.grantRead(project.role); + this.bucket.grantRead(project); } protected toSourceProperty(): any { diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts index 2eeaa41c3ce15..e661ad5bd5b2c 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts @@ -194,7 +194,7 @@ export class LambdaDeploymentGroup extends cdk.Construct implements ILambdaDeplo throw new Error('A pre-hook function is already defined for this deployment group'); } this.preHook = preHook; - this.grantPutLifecycleEventHookExecutionStatus(this.preHook.role); + this.grantPutLifecycleEventHookExecutionStatus(this.preHook); this.preHook.grantInvoke(this.role); } @@ -208,7 +208,7 @@ export class LambdaDeploymentGroup extends cdk.Construct implements ILambdaDeplo throw new Error('A post-hook function is already defined for this deployment group'); } this.postHook = postHook; - this.grantPutLifecycleEventHookExecutionStatus(this.postHook.role); + this.grantPutLifecycleEventHookExecutionStatus(this.postHook); this.postHook.grantInvoke(this.role); } @@ -217,12 +217,12 @@ export class LambdaDeploymentGroup extends cdk.Construct implements ILambdaDeplo * on this deployment group resource. * @param principal to grant permission to */ - public grantPutLifecycleEventHookExecutionStatus(principal?: iam.IPrincipal): void { - if (principal) { - principal.addToPolicy(new iam.PolicyStatement() - .addResource(this.deploymentGroupArn) - .addAction('codedeploy:PutLifecycleEventHookExecutionStatus')); - } + public grantPutLifecycleEventHookExecutionStatus(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + resourceArns: [this.deploymentGroupArn], + actions: ['codedeploy:PutLifecycleEventHookExecutionStatus'], + }); } public export(): LambdaDeploymentGroupImportProps { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/pipeline-actions.ts index 21ab3255eec60..1ff4ae6f53c87 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/pipeline-actions.ts @@ -186,9 +186,9 @@ function setCodeBuildNeededPermissions(info: codepipeline.ActionBind, project: c // allow the Project access to the Pipline's artifact Bucket if (needsPipelineBucketWrite) { - info.pipeline.grantBucketReadWrite(project.role); + info.pipeline.grantBucketReadWrite(project); } else { - info.pipeline.grantBucketRead(project.role); + info.pipeline.grantBucketRead(project); } } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts index c74e043620791..1ce4c7d4f8fa0 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts @@ -317,11 +317,11 @@ class PipelineDouble extends cdk.Construct implements codepipeline.IPipeline { throw new Error('asEventRuleTarget() is unsupported in PipelineDouble'); } - public grantBucketRead(_identity?: iam.IPrincipal): void { + public grantBucketRead(_identity?: iam.IGrantable): iam.Grant { throw new Error('grantBucketRead() is unsupported in PipelineDouble'); } - public grantBucketReadWrite(_identity?: iam.IPrincipal): void { + public grantBucketReadWrite(_identity?: iam.IGrantable): iam.Grant { throw new Error('grantBucketReadWrite() is unsupported in PipelineDouble'); } } @@ -369,9 +369,10 @@ class RoleDouble extends iam.Role { super(scope, id, props); } - public addToPolicy(statement: iam.PolicyStatement) { + public addToPolicy(statement: iam.PolicyStatement): boolean { super.addToPolicy(statement); this.statements.push(statement); + return true; } } diff --git a/packages/@aws-cdk/aws-codepipeline/lib/action.ts b/packages/@aws-cdk/aws-codepipeline/lib/action.ts index 44f93970196ec..476049a5fe810 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/action.ts @@ -83,14 +83,14 @@ export interface IPipeline extends cdk.IConstruct, events.IEventRuleTarget { * * @param identity the IAM Identity to grant the permissions to */ - grantBucketRead(identity?: iam.IPrincipal): void; + grantBucketRead(identity: iam.IGrantable): iam.Grant; /** * Grants read & write permissions to the Pipeline's S3 Bucket to the given Identity. * * @param identity the IAM Identity to grant the permissions to */ - grantBucketReadWrite(identity?: iam.IPrincipal): void; + grantBucketReadWrite(identity: iam.IGrantable): iam.Grant; } /** diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index b0faadbafc8f2..f17e421fc34e3 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -293,12 +293,12 @@ export class Pipeline extends cdk.Construct implements IPipeline { return this.stages.length; } - public grantBucketRead(identity?: iam.IPrincipal): void { - this.artifactBucket.grantRead(identity); + public grantBucketRead(identity: iam.IGrantable): iam.Grant { + return this.artifactBucket.grantRead(identity); } - public grantBucketReadWrite(identity?: iam.IPrincipal): void { - this.artifactBucket.grantReadWrite(identity); + public grantBucketReadWrite(identity: iam.IGrantable): iam.Grant { + return this.artifactBucket.grantReadWrite(identity); } /** diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index 097a0c48d5a14..48e6518954020 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -176,15 +176,15 @@ export interface LocalSecondaryIndexProps extends SecondaryIndexProps { export class Table extends Construct { /** * Permits an IAM Principal to list all DynamoDB Streams. - * @param principal The principal (no-op if undefined) + * @param grantee The principal (no-op if undefined) */ - public static grantListStreams(principal?: iam.IPrincipal): void { - if (principal) { - principal.addToPolicy(new iam.PolicyStatement() - .addAction('dynamodb:ListStreams') - .addResource("*")); - } - } + public static grantListStreams(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions: ['dynamodb:ListStreams'], + resourceArns: ['*'], + }); + } public readonly tableArn: string; public readonly tableName: string; @@ -404,31 +404,34 @@ export class Table extends Construct { /** * Adds an IAM policy statement associated with this table to an IAM * principal's policy. - * @param principal The principal (no-op if undefined) + * @param grantee The principal (no-op if undefined) * @param actions The set of actions to allow (i.e. "dynamodb:PutItem", "dynamodb:GetItem", ...) */ - public grant(principal?: iam.IPrincipal, ...actions: string[]) { - if (!principal) { - return; - } - principal.addToPolicy(new iam.PolicyStatement() - .addResources(this.tableArn, new cdk.Token(() => this.hasIndex ? `${this.tableArn}/index/*` : cdk.Aws.noValue).toString()) - .addActions(...actions)); + public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions, + resourceArns: [ + this.tableArn, + new cdk.Token(() => this.hasIndex ? `${this.tableArn}/index/*` : cdk.Aws.noValue).toString() + ], + scope: this, + }); } /** * Adds an IAM policy statement associated with this table's stream to an * IAM principal's policy. - * @param principal The principal (no-op if undefined) + * @param grantee The principal (no-op if undefined) * @param actions The set of actions to allow (i.e. "dynamodb:DescribeStream", "dynamodb:GetRecords", ...) */ - public grantStream(principal?: iam.IPrincipal, ...actions: string[]) { - if (!principal) { - return; - } - principal.addToPolicy(new iam.PolicyStatement() - .addResource(this.tableStreamArn) - .addActions(...actions)); + public grantStream(grantee: iam.IGrantable, ...actions: string[]) { + return iam.Grant.addToPrincipal({ + grantee, + actions, + resourceArns: [this.tableStreamArn], + scope: this, + }); } /** @@ -436,18 +439,18 @@ export class Table extends Construct { * BatchGetItem, GetRecords, GetShardIterator, Query, GetItem, Scan. * @param principal The principal to grant access to */ - public grantReadData(principal?: iam.IPrincipal) { - this.grant(principal, ...READ_DATA_ACTIONS); + public grantReadData(grantee: iam.IGrantable) { + return this.grant(grantee, ...READ_DATA_ACTIONS); } /** * Permis an IAM principal all stream data read operations for this * table's stream: * DescribeStream, GetRecords, GetShardIterator, ListStreams. - * @param principal The principal to grant access to + * @param grantee The principal to grant access to */ - public grantStreamRead(principal?: iam.IPrincipal) { - this.grantStream(principal, ...READ_STREAM_DATA_ACTIONS); + public grantStreamRead(grantee: iam.IGrantable) { + return this.grantStream(grantee, ...READ_STREAM_DATA_ACTIONS); } /** @@ -455,26 +458,26 @@ export class Table extends Construct { * BatchWriteItem, PutItem, UpdateItem, DeleteItem. * @param principal The principal to grant access to */ - public grantWriteData(principal?: iam.IPrincipal) { - this.grant(principal, ...WRITE_DATA_ACTIONS); + public grantWriteData(grantee: iam.IGrantable) { + return this.grant(grantee, ...WRITE_DATA_ACTIONS); } /** * Permits an IAM principal to all data read/write operations to this table. * BatchGetItem, GetRecords, GetShardIterator, Query, GetItem, Scan, * BatchWriteItem, PutItem, UpdateItem, DeleteItem - * @param principal The principal to grant access to + * @param grantee The principal to grant access to */ - public grantReadWriteData(principal?: iam.IPrincipal) { - this.grant(principal, ...READ_DATA_ACTIONS, ...WRITE_DATA_ACTIONS); + public grantReadWriteData(grantee: iam.IGrantable) { + return this.grant(grantee, ...READ_DATA_ACTIONS, ...WRITE_DATA_ACTIONS); } /** * Permits all DynamoDB operations ("dynamodb:*") to an IAM principal. - * @param principal The principal to grant access to + * @param grantee The principal to grant access to */ - public grantFullAccess(principal?: iam.IPrincipal) { - this.grant(principal, 'dynamodb:*'); + public grantFullAccess(grantee: iam.IGrantable) { + return this.grant(grantee, 'dynamodb:*'); } /** diff --git a/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts b/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts index c719ff0a6bd36..ed3c7685826b6 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts @@ -41,17 +41,17 @@ export interface IRepository extends cdk.IConstruct { /** * Grant the given principal identity permissions to perform the actions on this repository */ - grant(identity?: iam.IPrincipal, ...actions: string[]): void; + grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; /** * Grant the given identity permissions to pull images in this repository. */ - grantPull(identity?: iam.IPrincipal): void; + grantPull(grantee: iam.IGrantable): iam.Grant; /** * Grant the given identity permissions to pull and push images to this repository. */ - grantPullPush(identity?: iam.IPrincipal): void; + grantPullPush(grantee: iam.IGrantable): iam.Grant; /** * Defines an AWS CloudWatch event rule that can trigger a target when an image is pushed to this @@ -189,38 +189,41 @@ export abstract class RepositoryBase extends cdk.Construct implements IRepositor /** * Grant the given principal identity permissions to perform the actions on this repository */ - public grant(identity?: iam.IPrincipal, ...actions: string[]) { - if (!identity) { - return; - } - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.repositoryArn) - .addActions(...actions)); + public grant(grantee: iam.IGrantable, ...actions: string[]) { + return iam.Grant.addToPrincipalOrResource({ + grantee, + actions, + resourceArns: [this.repositoryArn], + resource: this, + }); } /** * Grant the given identity permissions to use the images in this repository */ - public grantPull(identity?: iam.IPrincipal) { - this.grant(identity, "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage"); + public grantPull(grantee: iam.IGrantable) { + const ret = this.grant(grantee, "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage"); + + iam.Grant.addToPrincipal({ + grantee, + actions: ["ecr:GetAuthorizationToken"], + resourceArns: ['*'], + scope: this, + }); - if (identity) { - identity.addToPolicy(new iam.PolicyStatement() - .addActions("ecr:GetAuthorizationToken", "logs:CreateLogStream", "logs:PutLogEvents") - .addAllResources()); - } + return ret; } /** * Grant the given identity permissions to pull and push images to this repository. */ - public grantPullPush(identity?: iam.IPrincipal) { - this.grantPull(identity); - this.grant(identity, - "ecr:PutImage", - "ecr:InitiateLayerUpload", - "ecr:UploadLayerPart", - "ecr:CompleteLayerUpload"); + public grantPullPush(grantee: iam.IGrantable) { + this.grantPull(grantee); + return this.grant(grantee, + "ecr:PutImage", + "ecr:InitiateLayerUpload", + "ecr:UploadLayerPart", + "ecr:CompleteLayerUpload"); } } diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.expected.json index dff3047152599..cf64430816ede 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.expected.json @@ -826,11 +826,7 @@ } }, { - "Action": [ - "ecr:GetAuthorizationToken", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], + "Action": "ecr:GetAuthorizationToken", "Effect": "Allow", "Resource": "*" }, diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.asset-image.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.asset-image.expected.json index 3ca6180e8e608..95576fee3a795 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.asset-image.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.asset-image.expected.json @@ -882,11 +882,7 @@ } }, { - "Action": [ - "ecr:GetAuthorizationToken", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], + "Action": "ecr:GetAuthorizationToken", "Effect": "Allow", "Resource": "*" }, diff --git a/packages/@aws-cdk/aws-glue/lib/table.ts b/packages/@aws-cdk/aws-glue/lib/table.ts index b12e811b95b4a..266e4d185d929 100644 --- a/packages/@aws-cdk/aws-glue/lib/table.ts +++ b/packages/@aws-cdk/aws-glue/lib/table.ts @@ -264,56 +264,45 @@ export class Table extends cdk.Construct implements ITable { /** * Grant read permissions to the table and the underlying data stored in S3 to an IAM principal. * - * @param identity the principal + * @param grantee the principal */ - public grantRead(identity: iam.IPrincipal): void { - this.grant(identity, { - permissions: readPermissions, - kmsActions: ['kms:Decrypt'] - }); - this.bucket.grantRead(identity, this.s3Prefix); + public grantRead(grantee: iam.IGrantable): iam.Grant { + const ret = this.grant(grantee, readPermissions); + if (this.encryptionKey && this.encryption === TableEncryption.ClientSideKms) { this.encryptionKey.grantDecrypt(grantee); } + this.bucket.grantRead(grantee, this.s3Prefix); + return ret; } /** * Grant write permissions to the table and the underlying data stored in S3 to an IAM principal. * - * @param identity the principal + * @param grantee the principal */ - public grantWrite(identity: iam.IPrincipal): void { - this.grant(identity, { - permissions: writePermissions, - kmsActions: ['kms:Encrypt', 'kms:GenerateDataKey'] - }); - this.bucket.grantWrite(identity, this.s3Prefix); + public grantWrite(grantee: iam.IGrantable): iam.Grant { + const ret = this.grant(grantee, writePermissions); + if (this.encryptionKey && this.encryption === TableEncryption.ClientSideKms) { this.encryptionKey.grantEncrypt(grantee); } + this.bucket.grantWrite(grantee, this.s3Prefix); + return ret; } /** * Grant read and write permissions to the table and the underlying data stored in S3 to an IAM principal. * - * @param identity the principal + * @param grantee the principal */ - public grantReadWrite(identity: iam.IPrincipal): void { - this.grant(identity, { - permissions: readPermissions.concat(writePermissions), - kmsActions: ['kms:Decrypt', 'kms:Encrypt', 'kms:GenerateDataKey'] - }); - this.bucket.grantReadWrite(identity, this.s3Prefix); + public grantReadWrite(grantee: iam.IGrantable): iam.Grant { + const ret = this.grant(grantee, [...readPermissions, ...writePermissions]); + if (this.encryptionKey && this.encryption === TableEncryption.ClientSideKms) { this.encryptionKey.grantEncryptDecrypt(grantee); } + this.bucket.grantReadWrite(grantee, this.s3Prefix); + return ret; } - private grant(identity: iam.IPrincipal, props: { - permissions: string[]; - // CSE-KMS needs to grant its own KMS policies because the bucket is unaware of the key. - // TODO: we wouldn't need this if kms.EncryptionKey exposed grant methods. - kmsActions?: string[]; - }) { - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.tableArn) - .addActions(...props.permissions)); - if (this.encryption === TableEncryption.ClientSideKms) { - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.encryptionKey!.keyArn) - .addActions(...props.kmsActions!)); - } + private grant(grantee: iam.IGrantable, actions: string[]) { + return iam.Grant.addToPrincipal({ + grantee, + resourceArns: [this.tableArn], + actions, + }); } } diff --git a/packages/@aws-cdk/aws-iam/README.md b/packages/@aws-cdk/aws-iam/README.md index 59b875e56df8b..a1428f1eeacd4 100644 --- a/packages/@aws-cdk/aws-iam/README.md +++ b/packages/@aws-cdk/aws-iam/README.md @@ -22,6 +22,14 @@ an `ExternalId` works like this: [supplying an external ID](test/example.external-id.lit.ts) +### Principals vs Identities + +When we say *Principal*, we mean an entity you grant permissions to. This +entity can be an AWS Service, a Role, or something more abstract such as "all +users in this account" or even "all users in this organization". An +*Identity* is an IAM representing a single IAM entity that can have +a policy attached, one of `Role`, `User`, or `Group`. + ### IAM Principals When defining policy statements as part of an AssumeRole policy or as part of a diff --git a/packages/@aws-cdk/aws-iam/lib/grant.ts b/packages/@aws-cdk/aws-iam/lib/grant.ts new file mode 100644 index 0000000000000..f9eaad56795b7 --- /dev/null +++ b/packages/@aws-cdk/aws-iam/lib/grant.ts @@ -0,0 +1,224 @@ +import cdk = require('@aws-cdk/cdk'); +import { PolicyStatement } from "./policy-document"; +import { IGrantable } from "./principals"; + +/** + * Basic options for a grant operation + */ +export interface CommonGrantOptions { + /** + * The principal to grant to + * + * @default if principal is undefined, no work is done. + */ + readonly grantee: IGrantable; + + /** + * The actions to grant + */ + readonly actions: string[]; + + /** + * The resource ARNs to grant to + */ + readonly resourceArns: string[]; +} + +/** + * Options for a grant operation + */ +export interface GrantWithResourceOptions extends CommonGrantOptions { + /** + * The resource with a resource policy + * + * The statement will be added to the resource policy if it couldn't be + * added to the principal policy. + */ + readonly resource: IResourceWithPolicy; + + /** + * When referring to the resource in a resource policy, use this as ARN. + * + * (Depending on the resource type, this needs to be '*' in a resource policy). + * + * @default Same as regular resource ARNs + */ + readonly resourceSelfArns?: string[]; +} + +/** + * Options for a grant operation that only applies to principals + */ +export interface GrantOnPrincipalOptions extends CommonGrantOptions { + /** + * Construct to report warnings on in case grant could not be registered + */ + readonly scope?: cdk.IConstruct; +} + +/** + * Options for a grant operation to both identity and resource + */ +export interface GrantOnPrincipalAndResourceOptions extends CommonGrantOptions { + /** + * The resource with a resource policy + * + * The statement will always be added to the resource policy. + */ + readonly resource: IResourceWithPolicy; + + /** + * When referring to the resource in a resource policy, use this as ARN. + * + * (Depending on the resource type, this needs to be '*' in a resource policy). + * + * @default Same as regular resource ARNs + */ + readonly resourceSelfArns?: string[]; +} + +/** + * Result of a grant() operation + * + * This class is not instantiable by consumers on purpose, so that they will be + * required to call the Grant factory functions. + */ +export class Grant { + /** + * Grant the given permissions to the principal + * + * The permissions will be added to the principal policy primarily, falling + * back to the resource policy if necessary. The permissions must be granted + * somewhere. + * + * - Trying to grant permissions to a principal that does not admit adding to + * the principal policy while not providing a resource with a resource policy + * is an error. + * - Trying to grant permissions to an absent principal (possible in the + * case of imported resources) leads to a warning being added to the + * resource construct. + */ + public static addToPrincipalOrResource(options: GrantWithResourceOptions): Grant { + const result = Grant.addToPrincipal({ + ...options, + scope: options.resource + }); + + if (result.success) { return result; } + + const statement = new PolicyStatement() + .addActions(...options.actions) + .addResources(...(options.resourceSelfArns || options.resourceArns)) + .addPrincipal(options.grantee!.grantPrincipal); + + options.resource.addToResourcePolicy(statement); + + return new Grant({ resourceStatement: statement, options }); + } + + /** + * Try to grant the given permissions to the given principal + * + * Absence of a principal leads to a warning, but failing to add + * the permissions to a present principal is not an error. + */ + public static addToPrincipal(options: GrantOnPrincipalOptions): Grant { + const statement = new PolicyStatement() + .addActions(...options.actions) + .addResources(...options.resourceArns); + + const addedToPrincipal = options.grantee.grantPrincipal.addToPolicy(statement); + + return new Grant({ principalStatement: addedToPrincipal ? statement : undefined, options }); + } + + /** + * Add a grant both on the principal and on the resource + * + * As long as any principal is given, granting on the pricipal may fail (in + * case of a non-identity principal), but granting on the resource will + * never fail. + * + * Statement will be the resource statement. + */ + public static addToPrincipalAndResource(options: GrantOnPrincipalAndResourceOptions): Grant { + const result = Grant.addToPrincipal({ + ...options, + scope: options.resource, + }); + + const statement = new PolicyStatement() + .addActions(...options.actions) + .addResources(...(options.resourceSelfArns || options.resourceArns)) + .addPrincipal(options.grantee!.grantPrincipal); + + options.resource.addToResourcePolicy(statement); + + return new Grant({ principalStatement: statement, resourceStatement: result.resourceStatement, options }); + } + + /** + * The statement that was added to the principal's policy + * + * Can be accessed to (e.g.) add additional conditions to the statement. + */ + public readonly principalStatement?: PolicyStatement; + + /** + * The statement that was added to the resource policy + * + * Can be accessed to (e.g.) add additional conditions to the statement. + */ + public readonly resourceStatement?: PolicyStatement; + + /** + * The options originally used to set this result + * + * Private member doubles as a way to make it impossible for an object literal to + * be structurally the same as this class. + */ + private readonly options: CommonGrantOptions; + + private constructor(props: GrantProps) { + this.options = props.options; + this.principalStatement = props.principalStatement; + this.resourceStatement = props.resourceStatement; + } + + /** + * Whether the grant operation was successful + */ + public get success(): boolean { + return this.principalStatement !== undefined || this.resourceStatement !== undefined; + } + + /** + * Throw an error if this grant wasn't successful + */ + public assertSuccess(): void { + if (!this.success) { + // tslint:disable-next-line:max-line-length + throw new Error(`${describeGrant(this.options)} could not be added on either identity or resource policy.`); + } + } +} + +function describeGrant(options: CommonGrantOptions) { + return `Permissions for '${options.grantee}' to call '${options.actions}' on '${options.resourceArns}'`; +} + +interface GrantProps { + readonly options: CommonGrantOptions; + readonly principalStatement?: PolicyStatement; + readonly resourceStatement?: PolicyStatement; +} + +/** + * A resource with a resource policy that can be added to + */ +export interface IResourceWithPolicy extends cdk.IConstruct { + /** + * Add a statement to the resource's resource policy + */ + addToResourcePolicy(statement: PolicyStatement): void; +} diff --git a/packages/@aws-cdk/aws-iam/lib/group.ts b/packages/@aws-cdk/aws-iam/lib/group.ts index 4946acb61bdc4..f9e6360230e04 100644 --- a/packages/@aws-cdk/aws-iam/lib/group.ts +++ b/packages/@aws-cdk/aws-iam/lib/group.ts @@ -1,7 +1,9 @@ import { Construct } from '@aws-cdk/cdk'; import { CfnGroup } from './iam.generated'; -import { IPrincipal, Policy } from './policy'; -import { ArnPrincipal, PolicyPrincipal, PolicyStatement } from './policy-document'; +import { IIdentity } from './identity-base'; +import { Policy } from './policy'; +import { ArnPrincipal, PolicyStatement, PrincipalPolicyFragment } from './policy-document'; +import { IPrincipal } from './principals'; import { User } from './user'; import { AttachedPolicies, undefinedIfEmpty } from './util'; @@ -34,7 +36,9 @@ export interface GroupProps { readonly path?: string; } -export class Group extends Construct implements IPrincipal { +export class Group extends Construct implements IIdentity { + public readonly grantPrincipal: IPrincipal = this; + public readonly assumeRoleAction: string = 'sts:AssumeRole'; /** * The runtime name of this group. */ @@ -45,10 +49,7 @@ export class Group extends Construct implements IPrincipal { */ public readonly groupArn: string; - /** - * An "AWS" policy principal that represents this group. - */ - public readonly principal: PolicyPrincipal; + public readonly policyFragment: PrincipalPolicyFragment; private readonly managedPolicies: string[]; private readonly attachedPolicies = new AttachedPolicies(); @@ -67,7 +68,7 @@ export class Group extends Construct implements IPrincipal { this.groupName = group.groupName; this.groupArn = group.groupArn; - this.principal = new ArnPrincipal(this.groupArn); + this.policyFragment = new ArnPrincipal(this.groupArn).policyFragment; } /** @@ -97,12 +98,13 @@ export class Group extends Construct implements IPrincipal { /** * Adds an IAM statement to the default policy. */ - public addToPolicy(statement: PolicyStatement) { + public addToPolicy(statement: PolicyStatement): boolean { if (!this.defaultPolicy) { this.defaultPolicy = new Policy(this, 'DefaultPolicy'); this.defaultPolicy.attachToGroup(this); } this.defaultPolicy.addStatement(statement); + return true; } } diff --git a/packages/@aws-cdk/aws-iam/lib/identity-base.ts b/packages/@aws-cdk/aws-iam/lib/identity-base.ts new file mode 100644 index 0000000000000..9b0e2d93bd7f6 --- /dev/null +++ b/packages/@aws-cdk/aws-iam/lib/identity-base.ts @@ -0,0 +1,21 @@ +import cdk = require('@aws-cdk/cdk'); +import { Policy } from "./policy"; +import { IPrincipal } from "./principals"; + +/** + * A construct that represents an IAM principal, such as a user, group or role. + */ +export interface IIdentity extends IPrincipal, cdk.IConstruct { + /** + * Attaches an inline policy to this principal. + * This is the same as calling `policy.addToXxx(principal)`. + * @param policy The policy resource to attach to this principal. + */ + attachInlinePolicy(policy: Policy): void; + + /** + * Attaches a managed policy to this principal. + * @param arn The ARN of the managed policy + */ + attachManagedPolicy(arn: string): void; +} diff --git a/packages/@aws-cdk/aws-iam/lib/imported-resource-principal.ts b/packages/@aws-cdk/aws-iam/lib/imported-resource-principal.ts new file mode 100644 index 0000000000000..e4079c8c75694 --- /dev/null +++ b/packages/@aws-cdk/aws-iam/lib/imported-resource-principal.ts @@ -0,0 +1,45 @@ +import cdk = require('@aws-cdk/cdk'); +import { PolicyStatement, PrincipalPolicyFragment } from './policy-document'; +import { IPrincipal } from './principals'; + +/** + * Properties for an ImportedResourcePrincipal + */ +export interface ImportedResourcePrincipalProps { + /** + * The resource the role proxy is for + */ + readonly resource: cdk.IConstruct; +} + +/** + * A principal associated with an imported resource + * + * Some resources have roles associated with them which they assume, such as + * Lambda Functions, CodeBuild projects, StepFunctions machines, etc. + * + * When those resources are imported, their actual roles are not always + * imported with them. When that happens, we use an instance of this class + * instead, which will add user warnings when statements are attempted to be + * added to it. + */ +export class ImportedResourcePrincipal implements IPrincipal { + public readonly assumeRoleAction: string = 'sts:AssumeRole'; + public readonly grantPrincipal: IPrincipal; + private readonly resource: cdk.IConstruct; + + constructor(props: ImportedResourcePrincipalProps) { + this.resource = props.resource; + this.grantPrincipal = this; + } + + public get policyFragment(): PrincipalPolicyFragment { + throw new Error(`Cannot get policy fragment of ${this.resource.node.path}, resource imported without a role`); + } + + public addToPolicy(statement: PolicyStatement): boolean { + const repr = JSON.stringify(this.resource.node.resolve(statement)); + this.resource.node.addWarning(`Add statement to this resource's role: ${repr}`); + return true; // Pretend we did the work. The human will do it for us, eventually. + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/lib/index.ts b/packages/@aws-cdk/aws-iam/lib/index.ts index 2301ccd5b6ae8..d8566a304f30f 100644 --- a/packages/@aws-cdk/aws-iam/lib/index.ts +++ b/packages/@aws-cdk/aws-iam/lib/index.ts @@ -5,6 +5,10 @@ export * from './policy'; export * from './user'; export * from './group'; export * from './lazy-role'; +export * from './principals'; +export * from './identity-base'; +export * from './grant'; +export * from './imported-resource-principal'; // AWS::IAM CloudFormation Resources: export * from './iam.generated'; diff --git a/packages/@aws-cdk/aws-iam/lib/lazy-role.ts b/packages/@aws-cdk/aws-iam/lib/lazy-role.ts index 846732dd985e5..9b4a290b9fa1f 100644 --- a/packages/@aws-cdk/aws-iam/lib/lazy-role.ts +++ b/packages/@aws-cdk/aws-iam/lib/lazy-role.ts @@ -1,6 +1,8 @@ import cdk = require('@aws-cdk/cdk'); -import { IPrincipal, Policy } from './policy'; -import { PolicyPrincipal, PolicyStatement } from './policy-document'; +import { Grant } from './grant'; +import { Policy } from './policy'; +import { PolicyStatement, PrincipalPolicyFragment } from './policy-document'; +import { IPrincipal } from './principals'; import { IRole, Role, RoleImportProps, RoleProps } from './role'; /** @@ -13,6 +15,8 @@ import { IRole, Role, RoleImportProps, RoleProps } from './role'; * not be synthesized or deployed. */ export class LazyRole extends cdk.Construct implements IRole { + public readonly grantPrincipal: IPrincipal = this; + public readonly assumeRoleAction: string = 'sts:AssumeRole'; private role?: Role; private readonly statements = new Array(); private readonly policies = new Array(); @@ -31,11 +35,12 @@ export class LazyRole extends cdk.Construct implements IRole { * If there is no default policy attached to this role, it will be created. * @param permission The permission statement to add to the policy document */ - public addToPolicy(statement: PolicyStatement): void { + public addToPolicy(statement: PolicyStatement): boolean { if (this.role) { - this.role.addToPolicy(statement); + return this.role.addToPolicy(statement); } else { this.statements.push(statement); + return true; } } @@ -78,24 +83,21 @@ export class LazyRole extends cdk.Construct implements IRole { return this.instantiate().roleName; } - /** - * Returns a Principal object representing the ARN of this role. - */ - public get principal(): PolicyPrincipal { - return this.instantiate().principal; + public get policyFragment(): PrincipalPolicyFragment { + return this.instantiate().policyFragment; } /** * Grant the actions defined in actions to the identity Principal on this resource. */ - public grant(identity?: IPrincipal, ...actions: string[]): void { + public grant(identity: IPrincipal, ...actions: string[]): Grant { return this.instantiate().grant(identity, ...actions); } /** * Grant permissions to the given principal to pass this role. */ - public grantPassRole(identity?: IPrincipal): void { + public grantPassRole(identity: IPrincipal): Grant { return this.instantiate().grantPassRole(identity); } diff --git a/packages/@aws-cdk/aws-iam/lib/policy-document.ts b/packages/@aws-cdk/aws-iam/lib/policy-document.ts index 2072b164f5d32..21022bb5ee64c 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy-document.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy-document.ts @@ -1,5 +1,7 @@ import cdk = require('@aws-cdk/cdk'); import { Default, RegionInfo } from '@aws-cdk/region-info'; +import { IPrincipal } from './principals'; +import { mergePrincipal } from './util'; export class PolicyDocument extends cdk.Token { private statements = new Array(); @@ -44,18 +46,37 @@ export class PolicyDocument extends cdk.Token { } /** - * Represents an IAM principal. + * Base class for policy principals */ -export abstract class PolicyPrincipal { +export abstract class PrincipalBase implements IPrincipal { + public readonly grantPrincipal: IPrincipal = this; + /** - * When this Principal is used in an AssumeRole policy, the action to use. + * Return the policy fragment that identifies this principal in a Policy. */ - public assumeRoleAction: string = 'sts:AssumeRole'; + public abstract readonly policyFragment: PrincipalPolicyFragment; /** - * Return the policy fragment that identifies this principal in a Policy. + * When this Principal is used in an AssumeRole policy, the action to use. */ - public abstract policyFragment(): PrincipalPolicyFragment; + public readonly assumeRoleAction: string = 'sts:AssumeRole'; + + public addToPolicy(_statement: PolicyStatement): boolean { + // This base class is used for non-identity principals. None of them + // have a PolicyDocument to add to. + return false; + } + + public toString() { + // This is a first pass to make the object readable. Descendant principals + // should return something nicer. + return JSON.stringify(this.policyFragment.principalJson); + } + + public toJSON() { + // Have to implement toJSON() because the default will lead to infinite recursion. + return this.policyFragment.principalJson; + } } /** @@ -71,37 +92,69 @@ export class PrincipalPolicyFragment { } } -export class ArnPrincipal extends PolicyPrincipal { +export class ArnPrincipal extends PrincipalBase { constructor(public readonly arn: string) { super(); } - public policyFragment(): PrincipalPolicyFragment { + public get policyFragment(): PrincipalPolicyFragment { return new PrincipalPolicyFragment({ AWS: [ this.arn ] }); } + + public toString() { + return `ArnPrincipal(${this.arn})`; + } } export class AccountPrincipal extends ArnPrincipal { constructor(public readonly accountId: any) { super(new StackDependentToken(stack => `arn:${stack.partition}:iam::${accountId}:root`).toString()); } + + public toString() { + return `AccountPrincipal(${this.accountId})`; + } } /** * An IAM principal that represents an AWS service (i.e. sqs.amazonaws.com). */ -export class ServicePrincipal extends PolicyPrincipal { +export class ServicePrincipal extends PrincipalBase { constructor(public readonly service: string, private readonly opts: ServicePrincipalOpts = {}) { super(); } - public policyFragment(): PrincipalPolicyFragment { + public get policyFragment(): PrincipalPolicyFragment { return new PrincipalPolicyFragment({ Service: [ new ServicePrincipalToken(this.service, this.opts).toString() ] }); } + + public toString() { + return `ServicePrincipal(${this.service})`; + } +} + +/** + * A principal that represents an AWS Organization + */ +export class OrganizationPrincipal extends PrincipalBase { + constructor(public readonly organizationId: string) { + super(); + } + + public get policyFragment(): PrincipalPolicyFragment { + return new PrincipalPolicyFragment( + { AWS: ['*'] }, + { StringEquals: { 'aws:PrincipalOrgID': this.organizationId } } + ); + } + + public toString() { + return `OrganizationPrincipal(${this.organizationId})`; + } } /** @@ -117,33 +170,49 @@ export class ServicePrincipal extends PolicyPrincipal { * for more details. * */ -export class CanonicalUserPrincipal extends PolicyPrincipal { +export class CanonicalUserPrincipal extends PrincipalBase { constructor(public readonly canonicalUserId: string) { super(); } - public policyFragment(): PrincipalPolicyFragment { + public get policyFragment(): PrincipalPolicyFragment { return new PrincipalPolicyFragment({ CanonicalUser: [ this.canonicalUserId ] }); } + + public toString() { + return `CanonicalUserPrincipal(${this.canonicalUserId})`; + } } -export class FederatedPrincipal extends PolicyPrincipal { +export class FederatedPrincipal extends PrincipalBase { + public readonly assumeRoleAction: string; + constructor( public readonly federated: string, public readonly conditions: {[key: string]: any}, - public assumeRoleAction: string = 'sts:AssumeRole') { + assumeRoleAction: string = 'sts:AssumeRole') { super(); + + this.assumeRoleAction = assumeRoleAction; } - public policyFragment(): PrincipalPolicyFragment { + public get policyFragment(): PrincipalPolicyFragment { return new PrincipalPolicyFragment({ Federated: [ this.federated ] }, this.conditions); } + + public toString() { + return `FederatedPrincipal(${this.federated})`; + } } export class AccountRootPrincipal extends AccountPrincipal { constructor() { super(new StackDependentToken(stack => stack.accountId).toString()); } + + public toString() { + return `AccountRootPrincipal()`; + } } /** @@ -153,6 +222,10 @@ export class AnyPrincipal extends ArnPrincipal { constructor() { super('*'); } + + public toString() { + return `AnyPrincipal()`; + } } /** @@ -161,17 +234,18 @@ export class AnyPrincipal extends ArnPrincipal { */ export class Anyone extends AnyPrincipal { } -export class CompositePrincipal extends PolicyPrincipal { - private readonly principals = new Array(); +export class CompositePrincipal extends PrincipalBase { + public readonly assumeRoleAction: string; + private readonly principals = new Array(); - constructor(principal: PolicyPrincipal, ...additionalPrincipals: PolicyPrincipal[]) { + constructor(principal: PrincipalBase, ...additionalPrincipals: PrincipalBase[]) { super(); this.assumeRoleAction = principal.assumeRoleAction; this.addPrincipals(principal); this.addPrincipals(...additionalPrincipals); } - public addPrincipals(...principals: PolicyPrincipal[]): this { + public addPrincipals(...principals: PrincipalBase[]): this { for (const p of principals) { if (p.assumeRoleAction !== this.assumeRoleAction) { throw new Error( @@ -179,7 +253,7 @@ export class CompositePrincipal extends PolicyPrincipal { `Expecting "${this.assumeRoleAction}", got "${p.assumeRoleAction}"`); } - const fragment = p.policyFragment(); + const fragment = p.policyFragment; if (fragment.conditions && Object.keys(fragment.conditions).length > 0) { throw new Error( `Components of a CompositePrincipal must not have conditions. ` + @@ -192,15 +266,19 @@ export class CompositePrincipal extends PolicyPrincipal { return this; } - public policyFragment(): PrincipalPolicyFragment { + public get policyFragment(): PrincipalPolicyFragment { const principalJson: { [key: string]: string[] } = { }; for (const p of this.principals) { - mergePrincipal(principalJson, p.policyFragment().principalJson); + mergePrincipal(principalJson, p.policyFragment.principalJson); } return new PrincipalPolicyFragment(principalJson); } + + public toString() { + return `CompositePrincipal(${this.principals})`; + } } /** @@ -244,8 +322,8 @@ export class PolicyStatement extends cdk.Token { return Object.keys(this.principal).length > 0; } - public addPrincipal(principal: PolicyPrincipal): this { - const fragment = principal.policyFragment(); + public addPrincipal(principal: IPrincipal): this { + const fragment = principal.policyFragment; mergePrincipal(this.principal, fragment.principalJson); this.addConditions(fragment.conditions); return this; @@ -446,21 +524,6 @@ export enum PolicyStatementEffect { Deny = 'Deny', } -function mergePrincipal(target: { [key: string]: string[] }, source: { [key: string]: string[] }) { - for (const key of Object.keys(source)) { - target[key] = target[key] || []; - - const value = source[key]; - if (!Array.isArray(value)) { - throw new Error(`Principal value must be an array (it will be normalized later): ${value}`); - } - - target[key].push(...value); - } - - return target; -} - /** * A lazy token that requires an instance of Stack to evaluate */ diff --git a/packages/@aws-cdk/aws-iam/lib/policy.ts b/packages/@aws-cdk/aws-iam/lib/policy.ts index f6c329889cbbc..6ac5a4e9d8c5f 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy.ts @@ -1,46 +1,11 @@ import { Construct, Token } from '@aws-cdk/cdk'; import { Group } from './group'; import { CfnPolicy } from './iam.generated'; -import { PolicyDocument, PolicyPrincipal, PolicyStatement } from './policy-document'; +import { PolicyDocument, PolicyStatement } from './policy-document'; import { IRole } from './role'; import { User } from './user'; import { generatePolicyName, undefinedIfEmpty } from './util'; -/** - * A construct that represents an IAM principal, such as a user, group or role. - */ -export interface IPrincipal { - /** - * The IAM principal of this identity (i.e. AWS principal, service principal, etc). - */ - readonly principal: PolicyPrincipal; - - /** - * Adds an IAM statement to the default inline policy associated with this - * principal. If a policy doesn't exist, it is created. - */ - addToPolicy(statement: PolicyStatement): void; - - /** - * Attaches an inline policy to this principal. - * This is the same as calling `policy.addToXxx(principal)`. - * @param policy The policy resource to attach to this principal. - */ - attachInlinePolicy(policy: Policy): void; - - /** - * Attaches a managed policy to this principal. - * @param arn The ARN of the managed policy - */ - attachManagedPolicy(arn: string): void; -} - -/** - * @deprecated Use IPrincipal - */ -// tslint:disable-next-line:no-empty-interface -export type IIdentityResource = IPrincipal; - export interface PolicyProps { /** * The name of the policy. If you specify multiple policies for an entity, diff --git a/packages/@aws-cdk/aws-iam/lib/principals.ts b/packages/@aws-cdk/aws-iam/lib/principals.ts new file mode 100644 index 0000000000000..70bd154fc2e3c --- /dev/null +++ b/packages/@aws-cdk/aws-iam/lib/principals.ts @@ -0,0 +1,50 @@ +import { PolicyStatement, PrincipalPolicyFragment } from './policy-document'; + +/** + * Any object that has an associated principal that a permission can be granted to + */ +export interface IGrantable { + /** + * The principal to grant permissions to + */ + readonly grantPrincipal: IPrincipal; +} + +/** + * Represents a logical IAM principal. + * + * An IPrincipal describes a logical entity that can perform AWS API calls + * against sets of resources, optionally under certain conditions. + * + * Examples of simple principals are IAM objects that you create, such + * as Users or Roles. + * + * An example of a more complex principals is a `ServicePrincipal` (such as + * `new ServicePrincipal("sns.amazonaws.com")`, which represents the Simple + * Notifications Service). + * + * A single logical Principal may also map to a set of physical principals. + * For example, `new OrganizationPrincipal('o-1234')` represents all + * identities that are part of the given AWS Organization. + */ +export interface IPrincipal extends IGrantable { + /** + * When this Principal is used in an AssumeRole policy, the action to use. + */ + readonly assumeRoleAction: string; + + /** + * Return the policy fragment that identifies this principal in a Policy. + */ + readonly policyFragment: PrincipalPolicyFragment; + + /** + * Add to the policy of this principal. + * + * @returns true if the statement was added, false if the principal in + * question does not have a policy document to add the statement to. + */ + addToPolicy(statement: PolicyStatement): boolean; +} + +// FIXME: Move all principals here after approval of changing PR. \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/lib/role.ts b/packages/@aws-cdk/aws-iam/lib/role.ts index a70f2e7b261e5..dc20cdbf382d8 100644 --- a/packages/@aws-cdk/aws-iam/lib/role.ts +++ b/packages/@aws-cdk/aws-iam/lib/role.ts @@ -1,7 +1,10 @@ -import { CfnOutput, Construct, IConstruct } from '@aws-cdk/cdk'; +import { CfnOutput, Construct } from '@aws-cdk/cdk'; +import { Grant } from './grant'; import { CfnRole } from './iam.generated'; -import { IPrincipal, Policy } from './policy'; -import { ArnPrincipal, PolicyDocument, PolicyPrincipal, PolicyStatement } from './policy-document'; +import { IIdentity } from './identity-base'; +import { Policy } from './policy'; +import { ArnPrincipal, PolicyDocument, PolicyStatement, PrincipalPolicyFragment } from './policy-document'; +import { IPrincipal } from './principals'; import { AttachedPolicies, undefinedIfEmpty } from './util'; export interface RoleProps { @@ -12,7 +15,7 @@ export interface RoleProps { * You can later modify the assume role policy document by accessing it via * the `assumeRolePolicy` property. */ - readonly assumedBy: PolicyPrincipal; + readonly assumedBy: IPrincipal; /** * ID that the role assumer needs to provide when assuming this role @@ -98,6 +101,10 @@ export class Role extends Construct implements IRole { return new ImportedRole(scope, id, props); } + public readonly grantPrincipal: IPrincipal = this; + + public readonly assumeRoleAction: string = 'sts:AssumeRole'; + /** * The assume role policy document associated with this role. */ @@ -120,9 +127,9 @@ export class Role extends Construct implements IRole { public readonly roleName: string; /** - * Returns the ARN of this role. + * Returns the role. */ - public readonly principal: PolicyPrincipal; + public readonly policyFragment: PrincipalPolicyFragment; private defaultPolicy?: Policy; private readonly managedPolicyArns: string[]; @@ -147,8 +154,8 @@ export class Role extends Construct implements IRole { this.roleId = role.roleId; this.roleArn = role.roleArn; - this.principal = new ArnPrincipal(this.roleArn); this.roleName = role.roleName; + this.policyFragment = new ArnPrincipal(this.roleArn).policyFragment; function _flatten(policies?: { [name: string]: PolicyDocument }) { if (policies == null || Object.keys(policies).length === 0) { @@ -175,12 +182,13 @@ export class Role extends Construct implements IRole { * If there is no default policy attached to this role, it will be created. * @param permission The permission statement to add to the policy document */ - public addToPolicy(statement: PolicyStatement) { + public addToPolicy(statement: PolicyStatement): boolean { if (!this.defaultPolicy) { this.defaultPolicy = new Policy(this, 'DefaultPolicy'); this.attachInlinePolicy(this.defaultPolicy); } this.defaultPolicy.addStatement(statement); + return true; } /** @@ -203,28 +211,27 @@ export class Role extends Construct implements IRole { /** * Grant the actions defined in actions to the identity Principal on this resource. */ - public grant(identity?: IPrincipal, ...actions: string[]) { - if (!identity) { - return; - } - - identity.addToPolicy(new PolicyStatement() - .addResource(this.roleArn) - .addActions(...actions)); + public grant(grantee: IPrincipal, ...actions: string[]) { + return Grant.addToPrincipal({ + grantee, + actions, + resourceArns: [this.roleArn], + scope: this + }); } /** * Grant permissions to the given principal to pass this role. */ - public grantPassRole(identity?: IPrincipal) { - this.grant(identity, 'iam:PassRole'); + public grantPassRole(identity: IPrincipal) { + return this.grant(identity, 'iam:PassRole'); } } /** * A Role object */ -export interface IRole extends IConstruct, IPrincipal { +export interface IRole extends IIdentity { /** * Returns the ARN of this role. */ @@ -249,15 +256,15 @@ export interface IRole extends IConstruct, IPrincipal { /** * Grant the actions defined in actions to the identity Principal on this resource. */ - grant(identity?: IPrincipal, ...actions: string[]): void; + grant(grantee: IPrincipal, ...actions: string[]): Grant; /** * Grant permissions to the given principal to pass this role. */ - grantPassRole(identity?: IPrincipal): void; + grantPassRole(grantee: IPrincipal): Grant; } -function createAssumeRolePolicy(principal: PolicyPrincipal, externalId?: string) { +function createAssumeRolePolicy(principal: IPrincipal, externalId?: string) { const statement = new PolicyStatement(); statement .addPrincipal(principal) @@ -303,8 +310,10 @@ export interface RoleImportProps { * A role that already exists */ class ImportedRole extends Construct implements IRole { + public readonly grantPrincipal: IPrincipal = this; + public readonly assumeRoleAction: string = 'sts:AssumeRole'; + public readonly policyFragment: PrincipalPolicyFragment; public readonly roleArn: string; - public readonly principal: PolicyPrincipal; private readonly _roleId?: string; @@ -312,7 +321,7 @@ class ImportedRole extends Construct implements IRole { super(scope, id); this.roleArn = props.roleArn; this._roleId = props.roleId; - this.principal = new ArnPrincipal(this.roleArn); + this.policyFragment = new ArnPrincipal(this.roleArn).policyFragment; } public get roleId() { @@ -330,8 +339,9 @@ class ImportedRole extends Construct implements IRole { return this.props; } - public addToPolicy(_statement: PolicyStatement): void { - // FIXME: Add warning that we're ignoring this + public addToPolicy(_statement: PolicyStatement): boolean { + // Statement will be added to resource instead + return false; } public attachInlinePolicy(_policy: Policy): void { @@ -345,14 +355,19 @@ class ImportedRole extends Construct implements IRole { /** * Grant the actions defined in actions to the identity Principal on this resource. */ - public grant(_identity?: IPrincipal, ..._actions: string[]): void { - // FIXME: Add warning that we're ignoring this + public grant(grantee: IPrincipal, ...actions: string[]): Grant { + return Grant.addToPrincipal({ + grantee, + actions, + resourceArns: [this.roleArn], + scope: this + }); } /** * Grant permissions to the given principal to pass this role. */ - public grantPassRole(_identity?: IPrincipal): void { - // FIXME: Add warning that we're ignoring this + public grantPassRole(identity: IPrincipal): Grant { + return this.grant(identity, 'iam:PassRole'); } } diff --git a/packages/@aws-cdk/aws-iam/lib/user.ts b/packages/@aws-cdk/aws-iam/lib/user.ts index a2b8850f52e00..3c9a04e49a7af 100644 --- a/packages/@aws-cdk/aws-iam/lib/user.ts +++ b/packages/@aws-cdk/aws-iam/lib/user.ts @@ -1,8 +1,11 @@ import { Construct } from '@aws-cdk/cdk'; import { Group } from './group'; import { CfnUser } from './iam.generated'; -import { IPrincipal, Policy } from './policy'; -import { ArnPrincipal, PolicyPrincipal, PolicyStatement } from './policy-document'; +import { IIdentity } from './identity-base'; +import { Policy } from './policy'; +import { PolicyStatement } from './policy-document'; +import { ArnPrincipal, PrincipalPolicyFragment } from './policy-document'; +import { IPrincipal } from './principals'; import { AttachedPolicies, undefinedIfEmpty } from './util'; export interface UserProps { @@ -62,7 +65,9 @@ export interface UserProps { readonly passwordResetRequired?: boolean; } -export class User extends Construct implements IPrincipal { +export class User extends Construct implements IIdentity { + public readonly grantPrincipal: IPrincipal = this; + public readonly assumeRoleAction: string = 'sts:AssumeRole'; /** * An attribute that represents the user name. @@ -74,10 +79,7 @@ export class User extends Construct implements IPrincipal { */ public readonly userArn: string; - /** - * Returns the ARN of this user. - */ - public readonly principal: PolicyPrincipal; + public readonly policyFragment: PrincipalPolicyFragment; private readonly groups = new Array(); private readonly managedPolicyArns = new Array(); @@ -97,7 +99,7 @@ export class User extends Construct implements IPrincipal { this.userName = user.userName; this.userArn = user.userArn; - this.principal = new ArnPrincipal(this.userArn); + this.policyFragment = new ArnPrincipal(this.userArn).policyFragment; if (props.groups) { props.groups.forEach(g => this.addToGroup(g)); @@ -129,14 +131,17 @@ export class User extends Construct implements IPrincipal { /** * Adds an IAM statement to the default policy. + * + * @returns true */ - public addToPolicy(statement: PolicyStatement) { + public addToPolicy(statement: PolicyStatement): boolean { if (!this.defaultPolicy) { this.defaultPolicy = new Policy(this, 'DefaultPolicy'); this.defaultPolicy.attachToUser(this); } this.defaultPolicy.addStatement(statement); + return true; } private parseLoginProfile(props: UserProps): CfnUser.LoginProfileProperty | undefined { diff --git a/packages/@aws-cdk/aws-iam/lib/util.ts b/packages/@aws-cdk/aws-iam/lib/util.ts index e3d2bae372627..8fc2a504f9557 100644 --- a/packages/@aws-cdk/aws-iam/lib/util.ts +++ b/packages/@aws-cdk/aws-iam/lib/util.ts @@ -44,3 +44,21 @@ export class AttachedPolicies { this.policies.push(policy); } } + +/** + * Merge two dictionaries that represent IAM principals + */ +export function mergePrincipal(target: { [key: string]: string[] }, source: { [key: string]: string[] }) { + for (const key of Object.keys(source)) { + target[key] = target[key] || []; + + const value = source[key]; + if (!Array.isArray(value)) { + throw new Error(`Principal value must be an array (it will be normalized later): ${value}`); + } + + target[key].push(...value); + } + + return target; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/test.policy-document.ts b/packages/@aws-cdk/aws-iam/test/test.policy-document.ts index 64e051a66c647..6b730c272ac3d 100644 --- a/packages/@aws-cdk/aws-iam/test/test.policy-document.ts +++ b/packages/@aws-cdk/aws-iam/test/test.policy-document.ts @@ -1,6 +1,6 @@ import { Stack, Token } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { Anyone, AnyPrincipal, CanonicalUserPrincipal, PolicyDocument, PolicyPrincipal, PolicyStatement } from '../lib'; +import { Anyone, AnyPrincipal, CanonicalUserPrincipal, IPrincipal, PolicyDocument, PolicyStatement } from '../lib'; import { ArnPrincipal, CompositePrincipal, FederatedPrincipal, PrincipalPolicyFragment, ServicePrincipal } from '../lib'; export = { @@ -278,9 +278,11 @@ export = { 'addPrincipal correctly merges array in'(test: Test) { const stack = new Stack(); - const arrayPrincipal: PolicyPrincipal = { + const arrayPrincipal: IPrincipal = { + get grantPrincipal() { return this; }, assumeRoleAction: 'sts:AssumeRole', - policyFragment: () => new PrincipalPolicyFragment({ AWS: ['foo', 'bar'] }), + policyFragment: new PrincipalPolicyFragment({ AWS: ['foo', 'bar'] }), + addToPolicy() { return false; } }; const s = new PolicyStatement().addAccountRootPrincipal() .addPrincipal(arrayPrincipal); diff --git a/packages/@aws-cdk/aws-kinesis/lib/stream.ts b/packages/@aws-cdk/aws-kinesis/lib/stream.ts index c48d0b7cabbd3..1e86c62ad84bd 100644 --- a/packages/@aws-cdk/aws-kinesis/lib/stream.ts +++ b/packages/@aws-cdk/aws-kinesis/lib/stream.ts @@ -32,7 +32,7 @@ export interface IStream extends cdk.IConstruct, logs.ILogSubscriptionDestinatio * If an encryption key is used, permission to ues the key to decrypt the * contents of the stream will also be granted. */ - grantRead(identity?: iam.IPrincipal): void; + grantRead(grantee: iam.IGrantable): iam.Grant; /** * Grant write permissions for this stream and its contents to an IAM @@ -41,7 +41,7 @@ export interface IStream extends cdk.IConstruct, logs.ILogSubscriptionDestinatio * If an encryption key is used, permission to ues the key to encrypt the * contents of the stream will also be granted. */ - grantWrite(identity?: iam.IPrincipal): void; + grantWrite(grantee: iam.IGrantable): iam.Grant; /** * Grants read/write permissions for this stream and its contents to an IAM @@ -50,7 +50,7 @@ export interface IStream extends cdk.IConstruct, logs.ILogSubscriptionDestinatio * If an encryption key is used, permission to use the key for * encrypt/decrypt will also be granted. */ - grantReadWrite(identity?: iam.IPrincipal): void; + grantReadWrite(grantee: iam.IGrantable): iam.Grant; } /** @@ -117,23 +117,14 @@ export abstract class StreamBase extends cdk.Construct implements IStream { * If an encryption key is used, permission to ues the key to decrypt the * contents of the stream will also be granted. */ - public grantRead(identity?: iam.IPrincipal) { - if (!identity) { - return; + public grantRead(grantee: iam.IGrantable) { + const ret = this.grant(grantee, 'kinesis:DescribeStream', 'kinesis:GetRecords', 'kinesis:GetShardIterator'); + + if (this.encryptionKey) { + this.encryptionKey.grantDecrypt(grantee); } - this.grant( - identity, - { - streamActions: [ - 'kinesis:DescribeStream', - 'kinesis:GetRecords', - 'kinesis:GetShardIterator' - ], - keyActions: [ - 'kms:Decrypt' - ] - } - ); + + return ret; } /** @@ -143,25 +134,14 @@ export abstract class StreamBase extends cdk.Construct implements IStream { * If an encryption key is used, permission to ues the key to decrypt the * contents of the stream will also be granted. */ - public grantWrite(identity?: iam.IPrincipal) { - if (!identity) { - return; + public grantWrite(grantee: iam.IGrantable) { + const ret = this.grant(grantee, 'kinesis:DescribeStream', 'kinesis:PutRecord', 'kinesis:PutRecords'); + + if (this.encryptionKey) { + this.encryptionKey.grantEncrypt(grantee); } - this.grant( - identity, - { - streamActions: [ - 'kinesis:DescribeStream', - 'kinesis:PutRecord', - 'kinesis:PutRecords' - ], - keyActions: [ - 'kms:GenerateDataKey', - 'kms:Encrypt' - ] - } - ); + return ret; } /** @@ -171,27 +151,20 @@ export abstract class StreamBase extends cdk.Construct implements IStream { * If an encryption key is used, permission to use the key for * encrypt/decrypt will also be granted. */ - public grantReadWrite(identity?: iam.IPrincipal) { - if (!identity) { - return; + public grantReadWrite(grantee: iam.IGrantable) { + const ret = this.grant( + grantee, + 'kinesis:DescribeStream', + 'kinesis:GetRecords', + 'kinesis:GetShardIterator', + 'kinesis:PutRecord', + 'kinesis:PutRecords'); + + if (this.encryptionKey) { + this.encryptionKey.grantEncryptDecrypt(grantee); } - this.grant( - identity, - { - streamActions: [ - 'kinesis:DescribeStream', - 'kinesis:GetRecords', - 'kinesis:GetShardIterator', - 'kinesis:PutRecord', - 'kinesis:PutRecords' - ], - keyActions: [ - 'kms:Decrypt', - 'kms:GenerateDataKey', - 'kms:Encrypt' - ] - } - ); + + return ret; } public logSubscriptionDestination(sourceLogGroup: logs.ILogGroup): logs.LogSubscriptionDestination { @@ -254,17 +227,13 @@ export abstract class StreamBase extends cdk.Construct implements IStream { return dest.logSubscriptionDestination(sourceLogGroup); } - private grant(identity: iam.IPrincipal, actions: { streamActions: string[], keyActions: string[] }) { - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.streamArn) - .addActions(...actions.streamActions)); - - // grant key permissions if there's an associated key. - if (this.encryptionKey) { - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.encryptionKey.keyArn) - .addActions(...actions.keyActions)); - } + private grant(grantee: iam.IGrantable, ...actions: string[]) { + return iam.Grant.addToPrincipal({ + grantee, + actions, + resourceArns: [this.streamArn], + scope: this, + }); } } diff --git a/packages/@aws-cdk/aws-kinesis/test/test.stream.ts b/packages/@aws-cdk/aws-kinesis/test/test.stream.ts index 5e5d8a1a872ce..4612cf0f3c14f 100644 --- a/packages/@aws-cdk/aws-kinesis/test/test.stream.ts +++ b/packages/@aws-cdk/aws-kinesis/test/test.stream.ts @@ -305,6 +305,19 @@ export = { } }, "Resource": "*" + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "MyUserDC45028B", + "Arn" + ] + } + }, + "Resource": "*" } ], "Version": "2012-10-17" @@ -428,6 +441,23 @@ export = { } }, "Resource": "*" + }, + { + "Action": [ + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "MyUserDC45028B", + "Arn" + ] + } + }, + "Resource": "*" } ], "Version": "2012-10-17" @@ -475,8 +505,9 @@ export = { }, { "Action": [ - "kms:GenerateDataKey", - "kms:Encrypt" + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", ], "Effect": "Allow", "Resource": { @@ -554,6 +585,24 @@ export = { } }, "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "MyUserDC45028B", + "Arn" + ] + } + }, + "Resource": "*" } ], "Version": "2012-10-17" @@ -604,8 +653,9 @@ export = { { "Action": [ "kms:Decrypt", - "kms:GenerateDataKey", - "kms:Encrypt" + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-kms/lib/index.ts b/packages/@aws-cdk/aws-kms/lib/index.ts index 52d9014cc7b9c..5affbeb43c54c 100644 --- a/packages/@aws-cdk/aws-kms/lib/index.ts +++ b/packages/@aws-cdk/aws-kms/lib/index.ts @@ -1,5 +1,6 @@ export * from './key'; export * from './alias'; +export * from './via-service-principal'; // AWS::KMS CloudFormation Resources: export * from './kms.generated'; diff --git a/packages/@aws-cdk/aws-kms/lib/key.ts b/packages/@aws-cdk/aws-kms/lib/key.ts index dfd8b010f4870..cf4c7584c061c 100644 --- a/packages/@aws-cdk/aws-kms/lib/key.ts +++ b/packages/@aws-cdk/aws-kms/lib/key.ts @@ -1,3 +1,4 @@ +import iam = require('@aws-cdk/aws-iam'); import { PolicyDocument, PolicyStatement } from '@aws-cdk/aws-iam'; import { CfnOutput, Construct, DeletionPolicy, IConstruct } from '@aws-cdk/cdk'; import { EncryptionKeyAlias } from './alias'; @@ -28,6 +29,26 @@ export interface IEncryptionKey extends IConstruct { * @returns a key ref which can be used in a call to `EncryptionKey.import(ref)`. */ export(): EncryptionKeyImportProps; + + /** + * Grant the indicated permissions on this key to the given principal + */ + grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; + + /** + * Grant decryption permisisons using this key to the given principal + */ + grantDecrypt(grantee: iam.IGrantable): iam.Grant; + + /** + * Grant encryption permisisons using this key to the given principal + */ + grantEncrypt(grantee: iam.IGrantable): iam.Grant; + + /** + * Grant encryption and decryption permisisons using this key to the given principal + */ + grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant; } export interface EncryptionKeyImportProps { @@ -75,6 +96,55 @@ export abstract class EncryptionKeyBase extends Construct implements IEncryption } public abstract export(): EncryptionKeyImportProps; + + /** + * Grant the indicated permissions on this key to the given principal + * + * This modifies both the principal's policy as well as the resource policy, + * since the default CloudFormation setup for KMS keys is that the policy + * must not be empty and so default grants won't work. + */ + public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { + return iam.Grant.addToPrincipalAndResource({ + grantee, + actions, + resourceArns: [this.keyArn], + resource: this, + resourceSelfArns: ['*'] + }); + } + + /** + * Grant decryption permisisons using this key to the given principal + */ + public grantDecrypt(grantee: iam.IGrantable): iam.Grant { + return this.grant(grantee, + 'kms:Decrypt', + ); + } + + /** + * Grant encryption permisisons using this key to the given principal + */ + public grantEncrypt(grantee: iam.IGrantable): iam.Grant { + return this.grant(grantee, + 'kms:Encrypt', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*' + ); + } + + /** + * Grant encryption and decryption permisisons using this key to the given principal + */ + public grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant { + return this.grant(grantee, + 'kms:Decrypt', + 'kms:Encrypt', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*' + ); + } } /** diff --git a/packages/@aws-cdk/aws-kms/lib/via-service-principal.ts b/packages/@aws-cdk/aws-kms/lib/via-service-principal.ts new file mode 100644 index 0000000000000..08029a64d400f --- /dev/null +++ b/packages/@aws-cdk/aws-kms/lib/via-service-principal.ts @@ -0,0 +1,27 @@ +import iam = require('@aws-cdk/aws-iam'); + +/** + * A principal to allow access to a key if it's being used through another AWS service + */ +export class ViaServicePrincipal extends iam.PrincipalBase { + private readonly basePrincipal: iam.IPrincipal; + + constructor(private readonly serviceName: string, basePrincipal?: iam.IPrincipal) { + super(); + this.basePrincipal = basePrincipal ? basePrincipal : new iam.AnyPrincipal(); + } + + public get policyFragment(): iam.PrincipalPolicyFragment { + // Make a copy of the base policyFragment to add a condition to it + const base = this.basePrincipal.policyFragment; + const conditions = Object.assign({}, base.conditions); + + if (conditions.StringEquals) { + conditions.StringEquals = Object.assign({ 'kms:ViaService': this.serviceName }, conditions.StringEquals); + } else { + conditions.StringEquals = { 'kms:ViaService': this.serviceName }; + } + + return { principalJson: base.principalJson, conditions }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kms/test/test.key.ts b/packages/@aws-cdk/aws-kms/test/test.key.ts index fe537b8984dd8..b0afeab8c0978 100644 --- a/packages/@aws-cdk/aws-kms/test/test.key.ts +++ b/packages/@aws-cdk/aws-kms/test/test.key.ts @@ -1,5 +1,5 @@ import { exactlyMatchTemplate, expect, haveResource, ResourcePart } from '@aws-cdk/assert'; -import { PolicyDocument, PolicyStatement } from '@aws-cdk/aws-iam'; +import { PolicyDocument, PolicyStatement, User } from '@aws-cdk/aws-iam'; import { App, Stack, Tag } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { EncryptionKey } from '../lib'; @@ -321,6 +321,55 @@ export = { test.done(); }, + 'grant decrypt on a key'(test: Test) { + // GIVEN + const stack = new Stack(); + const key = new EncryptionKey(stack, 'Key'); + const user = new User(stack, 'User'); + + // WHEN + key.grantDecrypt(user); + + // THEN + expect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + // This one is there by default + { + // tslint:disable-next-line:max-line-length + Action: [ "kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*", "kms:Put*", "kms:Update*", "kms:Revoke*", "kms:Disable*", "kms:Get*", "kms:Delete*", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion" ], + Effect: "Allow", + Principal: { AWS: { "Fn::Join": [ "", [ "arn:", { Ref: "AWS::Partition" }, ":iam::", { Ref: "AWS::AccountId" }, ":root" ] ] } }, + Resource: "*" + }, + // This is the interesting one + { + Action: "kms:Decrypt", + Effect: "Allow", + Principal: { AWS: { "Fn::GetAtt": [ "User00B015A1", "Arn" ] } }, + Resource: "*" + } + ], + Version: "2012-10-17" + } + })); + + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: "kms:Decrypt", + Effect: "Allow", + Resource: { "Fn::GetAtt": [ "Key961B73FD", "Arn" ] } + } + ], + Version: "2012-10-17" + }, + })); + + test.done(); + }, + 'import/export can be used to bring in an existing key'(test: Test) { const stack1 = new Stack(); const policy = new PolicyDocument(); diff --git a/packages/@aws-cdk/aws-kms/test/test.via-service-principal.ts b/packages/@aws-cdk/aws-kms/test/test.via-service-principal.ts new file mode 100644 index 0000000000000..308bdd996b829 --- /dev/null +++ b/packages/@aws-cdk/aws-kms/test/test.via-service-principal.ts @@ -0,0 +1,55 @@ +import iam = require('@aws-cdk/aws-iam'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import kms = require('../lib'); + +export = { + 'Via service, any principal'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const statement = new iam.PolicyStatement() + .addAction('abc:call') + .addPrincipal(new kms.ViaServicePrincipal('bla.amazonaws.com')) + .addResource('*'); + + // THEN + test.deepEqual(stack.node.resolve(statement), { + Action: 'abc:call', + Condition: { StringEquals: { 'kms:ViaService': 'bla.amazonaws.com' } }, + Effect: 'Allow', + Principal: '*', + Resource: '*' + }); + + test.done(); + }, + + 'Via service, principal with conditions'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const statement = new iam.PolicyStatement() + .addAction('abc:call') + .addPrincipal(new kms.ViaServicePrincipal('bla.amazonaws.com', new iam.OrganizationPrincipal('o-1234'))) + .addResource('*'); + + // THEN + test.deepEqual(stack.node.resolve(statement), { + Action: 'abc:call', + Condition: { + StringEquals: { + 'kms:ViaService': 'bla.amazonaws.com', + 'aws:PrincipalOrgID': 'o-1234' + } + }, + Effect: 'Allow', + Principal: '*', + Resource: '*' + }); + + test.done(); + }, +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts index 46678640f36ce..156ceea800db7 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts @@ -37,7 +37,7 @@ export class DynamoEventSource implements lambda.IEventSource { startingPosition: this.props.startingPosition }); - this.table.grantStreamRead(target.role); - dynamodb.Table.grantListStreams(target.role); + this.table.grantStreamRead(target); + dynamodb.Table.grantListStreams(target); } } diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts index db015ffcc7cad..5893935ab375b 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts @@ -37,6 +37,6 @@ export class KinesisEventSource implements lambda.IEventSource { eventSourceArn: this.stream.streamArn, }); - this.stream.grantRead(target.role); + this.stream.grantRead(target); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts index 5206fce5cae05..d372116fe49f2 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts @@ -31,6 +31,6 @@ export class SqsEventSource implements lambda.IEventSource { eventSourceArn: this.queue.queueArn, }); - this.queue.grantConsumeMessages(target.role); + this.queue.grantConsumeMessages(target); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/lib/alias.ts b/packages/@aws-cdk/aws-lambda/lib/alias.ts index 98fa53ed58c02..9a046e2422e10 100644 --- a/packages/@aws-cdk/aws-lambda/lib/alias.ts +++ b/packages/@aws-cdk/aws-lambda/lib/alias.ts @@ -101,6 +101,10 @@ export class Alias extends FunctionBase { return this.underlyingLambda.role; } + public get grantPrincipal() { + return this.underlyingLambda.grantPrincipal; + } + public metric(metricName: string, props: cloudwatch.MetricCustomization = {}): cloudwatch.Metric { // Metrics on Aliases need the "bare" function name, and the alias' ARN, this differes from the base behavior. return super.metric(metricName, { diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index b1a2be0de15c3..0fb8dd77cd6bd 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -11,7 +11,7 @@ import { CfnPermission } from './lambda.generated'; import { Permission } from './permission'; export interface IFunction extends cdk.IConstruct, events.IEventRuleTarget, logs.ILogSubscriptionDestination, - s3n.IBucketNotificationDestination, ec2.IConnectable, stepfunctions.IStepFunctionsTaskResource { + s3n.IBucketNotificationDestination, ec2.IConnectable, stepfunctions.IStepFunctionsTaskResource, iam.IGrantable { /** * Logical ID of this Function. @@ -51,7 +51,7 @@ export interface IFunction extends cdk.IConstruct, events.IEventRuleTarget, logs /** * Grant the given identity permissions to invoke this Lambda */ - grantInvoke(identity?: iam.IPrincipal): void; + grantInvoke(identity: iam.IGrantable): iam.Grant; /** * Return the given named metric for this Lambda @@ -115,6 +115,10 @@ export interface FunctionImportProps { } export abstract class FunctionBase extends cdk.Construct implements IFunction { + /** + * The principal this Lambda Function is running as + */ + public abstract readonly grantPrincipal: iam.IPrincipal; /** * The name of the function. @@ -128,6 +132,8 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { /** * The IAM role associated with this function. + * + * Undefined if the function was imported without a role. */ public abstract readonly role?: iam.IRole; @@ -231,12 +237,27 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { /** * Grant the given identity permissions to invoke this Lambda */ - public grantInvoke(identity?: iam.IPrincipal) { - if (identity) { - identity.addToPolicy(new iam.PolicyStatement() - .addAction('lambda:InvokeFunction') - .addResource(this.functionArn)); - } + public grantInvoke(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipalOrResource({ + grantee, + actions: ['lambda:InvokeFunction'], + resourceArns: [this.functionArn], + + // Fake resource-like object on which to call addToResourcePolicy(), which actually + // calls addPermission() + resource: { + addToResourcePolicy: (_statement) => { + // Couldn't add permissions to the principal, so add them locally. + const identifier = 'Invoke' + JSON.stringify(grantee!.grantPrincipal.policyFragment.principalJson); + this.addPermission(identifier, { + principal: grantee.grantPrincipal!, + action: 'lambda:InvokeFunction', + }); + }, + dependencyRoots: [], + node: this.node, + }, + }); } public logSubscriptionDestination(sourceLogGroup: logs.ILogGroup): logs.LogSubscriptionDestination { @@ -315,11 +336,10 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { source.bind(this); } - private parsePermissionPrincipal(principal?: iam.PolicyPrincipal) { + private parsePermissionPrincipal(principal?: iam.IPrincipal) { if (!principal) { return undefined; } - // use duck-typing, not instance of if ('accountId' in principal) { @@ -330,7 +350,7 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { return (principal as iam.ServicePrincipal).service; } - throw new Error(`Invalid principal type for Lambda permission statement: ${JSON.stringify(this.node.resolve(principal))}. ` + + throw new Error(`Invalid principal type for Lambda permission statement: ${this.node.resolve(principal.toString())}. ` + 'Supported: AccountPrincipal, ServicePrincipal'); } } diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 87ad0335cf238..c0a750f248731 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -333,6 +333,11 @@ export class Function extends FunctionBase { */ public readonly handler: string; + /** + * The principal this Lambda Function is running as + */ + public readonly grantPrincipal: iam.IPrincipal; + protected readonly canCreatePermissions = true; private readonly layers: ILayerVersion[] = []; @@ -361,6 +366,7 @@ export class Function extends FunctionBase { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), managedPolicyArns, }); + this.grantPrincipal = this.role; for (const statement of (props.initialPolicy || [])) { this.role.addToPolicy(statement); @@ -600,6 +606,7 @@ export class Function extends FunctionBase { } export class ImportedFunction extends FunctionBase { + public readonly grantPrincipal: iam.IPrincipal; public readonly functionName: string; public readonly functionArn: string; public readonly role?: iam.IRole; @@ -612,6 +619,7 @@ export class ImportedFunction extends FunctionBase { this.functionArn = props.functionArn; this.functionName = extractNameFromArn(props.functionArn); this.role = props.role; + this.grantPrincipal = this.role || new iam.ImportedResourcePrincipal({ resource: this } ); if (props.securityGroupId) { this._connections = new ec2.Connections({ diff --git a/packages/@aws-cdk/aws-lambda/lib/permission.ts b/packages/@aws-cdk/aws-lambda/lib/permission.ts index 44c4ac725d582..e2a3fcbee57d3 100644 --- a/packages/@aws-cdk/aws-lambda/lib/permission.ts +++ b/packages/@aws-cdk/aws-lambda/lib/permission.ts @@ -1,4 +1,4 @@ -import { PolicyPrincipal } from '@aws-cdk/aws-iam'; +import iam = require('@aws-cdk/aws-iam'); /** * Represents a permission statement that can be added to a Lambda's resource policy @@ -34,7 +34,7 @@ export interface Permission { * * The principal can be either an AccountPrincipal or a ServicePrincipal. */ - readonly principal: PolicyPrincipal; + readonly principal: iam.IPrincipal; /** * The AWS account ID (without hyphens) of the source owner. For example, if diff --git a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts index 2860c3e2456d5..fbecb0b0e997e 100644 --- a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts @@ -35,9 +35,10 @@ export interface SingletonFunctionProps extends FunctionProps { * for every SingletonLambda you create. */ export class SingletonFunction extends FunctionBase { + public readonly grantPrincipal: iam.IPrincipal; public readonly functionName: string; public readonly functionArn: string; - public readonly role?: iam.IRole | undefined; + public readonly role?: iam.IRole; protected readonly canCreatePermissions: boolean; private lambdaFunction: IFunction; @@ -49,6 +50,7 @@ export class SingletonFunction extends FunctionBase { this.functionArn = this.lambdaFunction.functionArn; this.functionName = this.lambdaFunction.functionName; this.role = this.lambdaFunction.role; + this.grantPrincipal = this.lambdaFunction.grantPrincipal; this.canCreatePermissions = true; // Doesn't matter, addPermission is overriden anyway } diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index 52b75d1ed17c7..be384f81f9d1d 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -105,5 +105,10 @@ }, "engines": { "node": ">= 8.10.0" + }, + "awslint": { + "exclude": [ + "grant-result:@aws-cdk/aws-lambda.LayerVersionBase.grantUsage" + ] } } diff --git a/packages/@aws-cdk/aws-logs/lib/log-group.ts b/packages/@aws-cdk/aws-logs/lib/log-group.ts index 4c227fbf25c61..9453cfa67c448 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-group.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-group.ts @@ -69,12 +69,12 @@ export interface ILogGroup extends cdk.IConstruct { /** * Give permissions to write to create and write to streams in this log group */ - grantWrite(principal?: iam.IPrincipal): void; + grantWrite(grantee: iam.IGrantable): iam.Grant; /** * Give the indicated permissions on this log group and all streams */ - grant(principal?: iam.IPrincipal, ...actions: string[]): void; + grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; } /** @@ -171,21 +171,22 @@ export abstract class LogGroupBase extends cdk.Construct implements ILogGroup { /** * Give permissions to write to create and write to streams in this log group */ - public grantWrite(principal?: iam.IPrincipal) { - this.grant(principal, 'logs:CreateLogStream', 'logs:PutLogEvents'); + public grantWrite(grantee: iam.IGrantable) { + return this.grant(grantee, 'logs:CreateLogStream', 'logs:PutLogEvents'); } /** * Give the indicated permissions on this log group and all streams */ - public grant(principal?: iam.IPrincipal, ...actions: string[]) { - if (!principal) { return; } - - principal.addToPolicy(new iam.PolicyStatement() - .addActions(...actions) - // This ARN includes a ':*' at the end to include the log streams. + public grant(grantee: iam.IGrantable, ...actions: string[]) { + return iam.Grant.addToPrincipal({ + grantee, + actions, + // A LogGroup ARN out of CloudFormation already includes a ':*' at the end to include the log streams under the group. // See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#w2ab1c21c10c63c43c11 - .addResource(`${this.logGroupArn}`)); + resourceArns: [this.logGroupArn], + scope: this, + }); } } diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts index e3b7b7b9ef7ef..950143b8100ca 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts @@ -54,8 +54,8 @@ export class BucketDeployment extends cdk.Construct { const source = props.source.bind(this); - source.bucket.grantRead(handler.role); - props.destinationBucket.grantReadWrite(handler.role); + source.bucket.grantRead(handler); + props.destinationBucket.grantReadWrite(handler); new cloudformation.CustomResource(this, 'CustomResource', { lambdaProvider: handler, diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 32b44413009c7..5357dbe754dc4 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -91,7 +91,7 @@ export interface IBucket extends cdk.IConstruct { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - grantRead(identity?: iam.IPrincipal, objectsKeyPattern?: any): void; + grantRead(identity: iam.IGrantable, objectsKeyPattern?: any): iam.Grant; /** * Grant write permissions to this bucket to an IAM principal. @@ -102,7 +102,7 @@ export interface IBucket extends cdk.IConstruct { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - grantWrite(identity?: iam.IPrincipal, objectsKeyPattern?: any): void; + grantWrite(identity: iam.IGrantable, objectsKeyPattern?: any): iam.Grant; /** * Grants s3:PutObject* and s3:Abort* permissions for this bucket to an IAM principal. @@ -112,7 +112,7 @@ export interface IBucket extends cdk.IConstruct { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - grantPut(identity?: iam.IPrincipal, objectsKeyPattern?: any): void; + grantPut(identity: iam.IGrantable, objectsKeyPattern?: any): iam.Grant; /** * Grants s3:DeleteObject* permission to an IAM pricipal for objects @@ -121,7 +121,7 @@ export interface IBucket extends cdk.IConstruct { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - grantDelete(identity?: iam.IPrincipal, objectsKeyPattern?: any): void; + grantDelete(identity: iam.IGrantable, objectsKeyPattern?: any): iam.Grant; /** * Grants read/write permissions for this bucket and it's contents to an IAM @@ -133,7 +133,7 @@ export interface IBucket extends cdk.IConstruct { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - grantReadWrite(identity?: iam.IPrincipal, objectsKeyPattern?: any): void; + grantReadWrite(identity: iam.IGrantable, objectsKeyPattern?: any): iam.Grant; /** * Allows unrestricted access to objects from this bucket. @@ -158,7 +158,7 @@ export interface IBucket extends cdk.IConstruct { * @param allowedActions the set of S3 actions to allow. Default is "s3:GetObject". * @returns The `iam.PolicyStatement` object, which can be used to apply e.g. conditions. */ - grantPublicAccess(keyPrefix?: string, ...allowedActions: string[]): iam.PolicyStatement; + grantPublicAccess(keyPrefix?: string, ...allowedActions: string[]): iam.Grant; /** * Defines a CloudWatch Event Rule that triggers upon putting an object into the Bucket. @@ -375,8 +375,8 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - public grantRead(identity?: iam.IPrincipal, objectsKeyPattern: any = '*') { - this.grant(identity, perms.BUCKET_READ_ACTIONS, perms.KEY_READ_ACTIONS, + public grantRead(identity: iam.IGrantable, objectsKeyPattern: any = '*') { + return this.grant(identity, perms.BUCKET_READ_ACTIONS, perms.KEY_READ_ACTIONS, this.bucketArn, this.arnForObjects(objectsKeyPattern)); } @@ -390,8 +390,8 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - public grantWrite(identity?: iam.IPrincipal, objectsKeyPattern: any = '*') { - this.grant(identity, perms.BUCKET_WRITE_ACTIONS, perms.KEY_WRITE_ACTIONS, + public grantWrite(identity: iam.IGrantable, objectsKeyPattern: any = '*') { + return this.grant(identity, perms.BUCKET_WRITE_ACTIONS, perms.KEY_WRITE_ACTIONS, this.bucketArn, this.arnForObjects(objectsKeyPattern)); } @@ -404,8 +404,8 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - public grantPut(identity?: iam.IPrincipal, objectsKeyPattern: any = '*') { - this.grant(identity, perms.BUCKET_PUT_ACTIONS, perms.KEY_WRITE_ACTIONS, + public grantPut(identity: iam.IGrantable, objectsKeyPattern: any = '*') { + return this.grant(identity, perms.BUCKET_PUT_ACTIONS, perms.KEY_WRITE_ACTIONS, this.arnForObjects(objectsKeyPattern)); } @@ -416,8 +416,8 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - public grantDelete(identity?: iam.IPrincipal, objectsKeyPattern: any = '*') { - this.grant(identity, perms.BUCKET_DELETE_ACTIONS, [], + public grantDelete(identity: iam.IGrantable, objectsKeyPattern: any = '*') { + return this.grant(identity, perms.BUCKET_DELETE_ACTIONS, [], this.arnForObjects(objectsKeyPattern)); } @@ -431,11 +431,11 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - public grantReadWrite(identity?: iam.IPrincipal, objectsKeyPattern: any = '*') { + public grantReadWrite(identity: iam.IGrantable, objectsKeyPattern: any = '*') { const bucketActions = perms.BUCKET_READ_ACTIONS.concat(perms.BUCKET_WRITE_ACTIONS); const keyActions = perms.KEY_READ_ACTIONS.concat(perms.KEY_WRITE_ACTIONS); - this.grant(identity, + return this.grant(identity, bucketActions, keyActions, this.bucketArn, @@ -463,51 +463,40 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { * * @param keyPrefix the prefix of S3 object keys (e.g. `home/*`). Default is "*". * @param allowedActions the set of S3 actions to allow. Default is "s3:GetObject". - * @returns The `iam.PolicyStatement` object, which can be used to apply e.g. conditions. */ - public grantPublicAccess(keyPrefix = '*', ...allowedActions: string[]): iam.PolicyStatement { + public grantPublicAccess(keyPrefix = '*', ...allowedActions: string[]) { if (this.disallowPublicAccess) { throw new Error("Cannot grant public access when 'blockPublicPolicy' is enabled"); } allowedActions = allowedActions.length > 0 ? allowedActions : [ 's3:GetObject' ]; - const statement = new iam.PolicyStatement() - .addActions(...allowedActions) - .addResource(this.arnForObjects(keyPrefix)) - .addPrincipal(new iam.Anyone()); - - this.addToResourcePolicy(statement); - return statement; + return iam.Grant.addToPrincipalOrResource({ + actions: allowedActions, + resourceArns: [this.arnForObjects(keyPrefix)], + grantee: new iam.Anyone(), + resource: this, + }); } - private grant(identity: iam.IPrincipal | undefined, + private grant(grantee: iam.IGrantable, bucketActions: string[], keyActions: string[], resourceArn: string, ...otherResourceArns: string[]) { - - if (!identity) { - return; - } - const resources = [ resourceArn, ...otherResourceArns ]; - identity.addToPolicy(new iam.PolicyStatement() - .addResources(...resources) - .addActions(...bucketActions)); + const ret = iam.Grant.addToPrincipalOrResource({ + grantee, + actions: bucketActions, + resourceArns: resources, + resource: this, + }); - // grant key permissions if there's an associated key. if (this.encryptionKey) { - // KMS permissions need to be granted both directions - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.encryptionKey.keyArn) - .addActions(...keyActions)); - - this.encryptionKey.addToResourcePolicy(new iam.PolicyStatement() - .addAllResources() - .addPrincipal(identity.principal) - .addActions(...keyActions)); + this.encryptionKey.grant(grantee, ...keyActions); } + + return ret; } } diff --git a/packages/@aws-cdk/aws-s3/test/test.bucket.ts b/packages/@aws-cdk/aws-s3/test/test.bucket.ts index 37949b9c463f7..8789d97ceb91e 100644 --- a/packages/@aws-cdk/aws-s3/test/test.bucket.ts +++ b/packages/@aws-cdk/aws-s3/test/test.bucket.ts @@ -743,6 +743,65 @@ export = { test.done(); }, + 'grant permissions to non-identity principal'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.Kms }); + + // WHEN + bucket.grantRead(new iam.OrganizationPrincipal('o-1234')); + + // THEN + expect(stack).to(haveResource('AWS::S3::BucketPolicy', { + PolicyDocument: { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ "s3:GetObject*", "s3:GetBucket*", "s3:List*" ], + "Condition": { "StringEquals": { "aws:PrincipalOrgID": "o-1234" } }, + "Effect": "Allow", + "Principal": "*", + "Resource": [ + { "Fn::GetAtt": [ "MyBucketF68F3FF0", "Arn" ] }, + { "Fn::Join": [ "", [ { "Fn::GetAtt": [ "MyBucketF68F3FF0", "Arn" ] }, "/*" ] ] } + ] + } + ] + } + })); + + expect(stack).to(haveResource('AWS::KMS::Key', { + "KeyPolicy": { + "Statement": [ + { + "Action": [ "kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*", "kms:Put*", "kms:Update*", + "kms:Revoke*", "kms:Disable*", "kms:Get*", "kms:Delete*", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion" ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ "", [ + "arn:", { "Ref": "AWS::Partition" }, ":iam::", { "Ref": "AWS::AccountId" }, ":root" + ]] + } + }, + "Resource": "*" + }, + { + "Action": [ "kms:Decrypt", "kms:DescribeKey" ], + "Effect": "Allow", + "Resource": "*", + "Principal": "*", + "Condition": { "StringEquals": { "aws:PrincipalOrgID": "o-1234" } }, + } + ], + "Version": "2012-10-17" + }, + + })); + + test.done(); + }, + 'if an encryption key is included, encrypt/decrypt permissions are also added both ways'(test: Test) { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.Kms }); @@ -1214,8 +1273,8 @@ export = { const bucket = new s3.Bucket(stack, 'b'); // WHEN - const statement = bucket.grantPublicAccess(); - statement.addCondition('IpAddress', { "aws:SourceIp": "54.240.143.0/24" }); + const result = bucket.grantPublicAccess(); + result.resourceStatement!.addCondition('IpAddress', { "aws:SourceIp": "54.240.143.0/24" }); // THEN expect(stack).to(haveResource('AWS::S3::BucketPolicy', { diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 422d47736b244..74661d1ccc1d2 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -51,7 +51,7 @@ export interface ISecret extends cdk.IConstruct { * @param versionStages the version stages the grant is limited to. If not specified, no restriction on the version * stages is applied. */ - grantRead(grantee: iam.IPrincipal, versionStages?: string[]): void; + grantRead(grantee: iam.IGrantable, versionStages?: string[]): iam.Grant; /** * Adds a rotation schedule to the secret. @@ -118,30 +118,29 @@ export abstract class SecretBase extends cdk.Construct implements ISecret { public abstract export(): SecretImportProps; - public grantRead(grantee: iam.IPrincipal, versionStages?: string[]): void { + public grantRead(grantee: iam.IGrantable, versionStages?: string[]): iam.Grant { // @see https://docs.aws.amazon.com/fr_fr/secretsmanager/latest/userguide/auth-and-access_identity-based-policies.html - const statement = new iam.PolicyStatement() - .allow() - .addAction('secretsmanager:GetSecretValue') - .addResource(this.secretArn); - if (versionStages != null) { - statement.addCondition('ForAnyValue:StringEquals', { + + const result = iam.Grant.addToPrincipal({ + grantee, + actions: ['secretsmanager:GetSecretValue'], + resourceArns: [this.secretArn], + scope: this + }); + if (versionStages != null && result.principalStatement) { + result.principalStatement.addCondition('ForAnyValue:StringEquals', { 'secretsmanager:VersionStage': versionStages }); } - grantee.addToPolicy(statement); if (this.encryptionKey) { // @see https://docs.aws.amazon.com/fr_fr/kms/latest/developerguide/services-secrets-manager.html - this.encryptionKey.addToResourcePolicy(new iam.PolicyStatement() - .allow() - .addPrincipal(grantee.principal) - .addAction('kms:Decrypt') - .addAllResources() - .addCondition('StringEquals', { - 'kms:ViaService': `secretsmanager.${this.node.stack.region}.amazonaws.com` - })); + this.encryptionKey.grantDecrypt( + new kms.ViaServicePrincipal(`secretsmanager.${this.node.stack.region}.amazonaws.com`, grantee.grantPrincipal) + ); } + + return result; } public get secretString() { diff --git a/packages/@aws-cdk/aws-serverless/lib/serverless.generated.ts b/packages/@aws-cdk/aws-serverless/lib/serverless.generated.ts new file mode 100644 index 0000000000000..d45bee28444ee --- /dev/null +++ b/packages/@aws-cdk/aws-serverless/lib/serverless.generated.ts @@ -0,0 +1,2035 @@ +// Copyright 2012-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Generated from the AWS CloudFormation Resource Specification +// See: docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-resource-specification.html +// @cfn2ts:meta@ {"generated":"2019-03-21T09:12:23.615Z","fingerprint":"UJ42fezBazlHPOkm0pNitOMM9peubfGEd2/XcTwSlsI="} + +// tslint:disable:max-line-length | This is generated code - line lengths are difficult to control + +import cdk = require('@aws-cdk/cdk'); + +/** + * Properties for defining a `AWS::Serverless::Api` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ +export interface CfnApiProps { + /** + * `AWS::Serverless::Api.StageName` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + stageName: string; + /** + * `AWS::Serverless::Api.Auth` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + auth?: CfnApi.AuthProperty | cdk.Token; + /** + * `AWS::Serverless::Api.BinaryMediaTypes` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + binaryMediaTypes?: string[]; + /** + * `AWS::Serverless::Api.CacheClusterEnabled` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + cacheClusterEnabled?: boolean | cdk.Token; + /** + * `AWS::Serverless::Api.CacheClusterSize` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + cacheClusterSize?: string; + /** + * `AWS::Serverless::Api.Cors` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + cors?: string; + /** + * `AWS::Serverless::Api.DefinitionBody` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + definitionBody?: object | cdk.Token; + /** + * `AWS::Serverless::Api.DefinitionUri` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + definitionUri?: CfnApi.S3LocationProperty | string | cdk.Token; + /** + * `AWS::Serverless::Api.EndpointConfiguration` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + endpointConfiguration?: string; + /** + * `AWS::Serverless::Api.MethodSettings` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + methodSettings?: object | cdk.Token; + /** + * `AWS::Serverless::Api.Name` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + name?: string; + /** + * `AWS::Serverless::Api.Variables` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + variables?: { [key: string]: (string) } | cdk.Token; +} + +/** + * Determine whether the given properties match those of a `CfnApiProps` + * + * @param properties - the TypeScript properties of a `CfnApiProps` + * + * @returns the result of the validation. + */ +function CfnApiPropsValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('auth', CfnApi_AuthPropertyValidator)(properties.auth)); + errors.collect(cdk.propertyValidator('binaryMediaTypes', cdk.listValidator(cdk.validateString))(properties.binaryMediaTypes)); + errors.collect(cdk.propertyValidator('cacheClusterEnabled', cdk.validateBoolean)(properties.cacheClusterEnabled)); + errors.collect(cdk.propertyValidator('cacheClusterSize', cdk.validateString)(properties.cacheClusterSize)); + errors.collect(cdk.propertyValidator('cors', cdk.validateString)(properties.cors)); + errors.collect(cdk.propertyValidator('definitionBody', cdk.validateObject)(properties.definitionBody)); + errors.collect(cdk.propertyValidator('definitionUri', cdk.unionValidator(CfnApi_S3LocationPropertyValidator, cdk.validateString))(properties.definitionUri)); + errors.collect(cdk.propertyValidator('endpointConfiguration', cdk.validateString)(properties.endpointConfiguration)); + errors.collect(cdk.propertyValidator('methodSettings', cdk.validateObject)(properties.methodSettings)); + errors.collect(cdk.propertyValidator('name', cdk.validateString)(properties.name)); + errors.collect(cdk.propertyValidator('stageName', cdk.requiredValidator)(properties.stageName)); + errors.collect(cdk.propertyValidator('stageName', cdk.validateString)(properties.stageName)); + errors.collect(cdk.propertyValidator('variables', cdk.hashValidator(cdk.validateString))(properties.variables)); + return errors.wrap('supplied properties not correct for "CfnApiProps"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Api` resource + * + * @param properties - the TypeScript properties of a `CfnApiProps` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Api` resource. + */ +// @ts-ignore TS6133 +function cfnApiPropsToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnApiPropsValidator(properties).assertSuccess(); + return { + StageName: cdk.stringToCloudFormation(properties.stageName), + Auth: cfnApiAuthPropertyToCloudFormation(properties.auth), + BinaryMediaTypes: cdk.listMapper(cdk.stringToCloudFormation)(properties.binaryMediaTypes), + CacheClusterEnabled: cdk.booleanToCloudFormation(properties.cacheClusterEnabled), + CacheClusterSize: cdk.stringToCloudFormation(properties.cacheClusterSize), + Cors: cdk.stringToCloudFormation(properties.cors), + DefinitionBody: cdk.objectToCloudFormation(properties.definitionBody), + DefinitionUri: cdk.unionMapper([CfnApi_S3LocationPropertyValidator, cdk.validateString], [cfnApiS3LocationPropertyToCloudFormation, cdk.stringToCloudFormation])(properties.definitionUri), + EndpointConfiguration: cdk.stringToCloudFormation(properties.endpointConfiguration), + MethodSettings: cdk.objectToCloudFormation(properties.methodSettings), + Name: cdk.stringToCloudFormation(properties.name), + Variables: cdk.hashMapper(cdk.stringToCloudFormation)(properties.variables), + }; +} + +/** + * A CloudFormation `AWS::Serverless::Api` + * + * @cloudformationResource AWS::Serverless::Api + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ +export class CfnApi extends cdk.CfnResource { + /** + * The CloudFormation resource type name for this resource class. + */ + public static readonly resourceTypeName = "AWS::Serverless::Api"; + /** + * The `Transform` a template must use in order to use this resource + */ + public static readonly requiredTransform = "AWS::Serverless-2016-10-31"; + public readonly apiName: string; + + /** + * Create a new `AWS::Serverless::Api`. + * + * @param scope - scope in which this resource is defined + * @param id - scoped id of the resource + * @param props - resource properties + */ + constructor(scope: cdk.Construct, id: string, props: CfnApiProps) { + super(scope, id, { type: CfnApi.resourceTypeName, properties: props }); + cdk.requireProperty(props, 'stageName', this); + // If a different transform than the required one is in use, this resource cannot be used + if (this.node.stack.templateOptions.transform && this.node.stack.templateOptions.transform !== CfnApi.requiredTransform) { + throw new Error(`The ${JSON.stringify(CfnApi.requiredTransform)} transform is required when using CfnApi, but the ${JSON.stringify(this.node.stack.templateOptions.transform)} is used.`); + } + // Automatically configure the required transform + this.node.stack.templateOptions.transform = CfnApi.requiredTransform; + this.apiName = this.ref.toString(); + } + + public get propertyOverrides(): CfnApiProps { + return this.untypedPropertyOverrides; + } + protected renderProperties(properties: any): { [key: string]: any } { + return cfnApiPropsToCloudFormation(this.node.resolve(properties)); + } +} + +export namespace CfnApi { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api-auth-object + */ + export interface AuthProperty { + /** + * `CfnApi.AuthProperty.Authorizers` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api-auth-object + */ + authorizers?: object | cdk.Token; + /** + * `CfnApi.AuthProperty.DefaultAuthorizer` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api-auth-object + */ + defaultAuthorizer?: string; + } +} + +/** + * Determine whether the given properties match those of a `AuthProperty` + * + * @param properties - the TypeScript properties of a `AuthProperty` + * + * @returns the result of the validation. + */ +function CfnApi_AuthPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('authorizers', cdk.validateObject)(properties.authorizers)); + errors.collect(cdk.propertyValidator('defaultAuthorizer', cdk.validateString)(properties.defaultAuthorizer)); + return errors.wrap('supplied properties not correct for "AuthProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Api.Auth` resource + * + * @param properties - the TypeScript properties of a `AuthProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Api.Auth` resource. + */ +// @ts-ignore TS6133 +function cfnApiAuthPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnApi_AuthPropertyValidator(properties).assertSuccess(); + return { + Authorizers: cdk.objectToCloudFormation(properties.authorizers), + DefaultAuthorizer: cdk.stringToCloudFormation(properties.defaultAuthorizer), + }; +} + +export namespace CfnApi { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#s3-location-object + */ + export interface S3LocationProperty { + /** + * `CfnApi.S3LocationProperty.Bucket` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + bucket: string; + /** + * `CfnApi.S3LocationProperty.Key` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + key: string; + /** + * `CfnApi.S3LocationProperty.Version` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + version: number | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `S3LocationProperty` + * + * @param properties - the TypeScript properties of a `S3LocationProperty` + * + * @returns the result of the validation. + */ +function CfnApi_S3LocationPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('bucket', cdk.requiredValidator)(properties.bucket)); + errors.collect(cdk.propertyValidator('bucket', cdk.validateString)(properties.bucket)); + errors.collect(cdk.propertyValidator('key', cdk.requiredValidator)(properties.key)); + errors.collect(cdk.propertyValidator('key', cdk.validateString)(properties.key)); + errors.collect(cdk.propertyValidator('version', cdk.requiredValidator)(properties.version)); + errors.collect(cdk.propertyValidator('version', cdk.validateNumber)(properties.version)); + return errors.wrap('supplied properties not correct for "S3LocationProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Api.S3Location` resource + * + * @param properties - the TypeScript properties of a `S3LocationProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Api.S3Location` resource. + */ +// @ts-ignore TS6133 +function cfnApiS3LocationPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnApi_S3LocationPropertyValidator(properties).assertSuccess(); + return { + Bucket: cdk.stringToCloudFormation(properties.bucket), + Key: cdk.stringToCloudFormation(properties.key), + Version: cdk.numberToCloudFormation(properties.version), + }; +} + +/** + * Properties for defining a `AWS::Serverless::Application` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ +export interface CfnApplicationProps { + /** + * `AWS::Serverless::Application.Location` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + location: CfnApplication.ApplicationLocationProperty | string | cdk.Token; + /** + * `AWS::Serverless::Application.NotificationArns` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + notificationArns?: string[]; + /** + * `AWS::Serverless::Application.Parameters` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + parameters?: { [key: string]: (string) } | cdk.Token; + /** + * `AWS::Serverless::Application.Tags` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + tags?: { [key: string]: (string) }; + /** + * `AWS::Serverless::Application.TimeoutInMinutes` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + timeoutInMinutes?: number | cdk.Token; +} + +/** + * Determine whether the given properties match those of a `CfnApplicationProps` + * + * @param properties - the TypeScript properties of a `CfnApplicationProps` + * + * @returns the result of the validation. + */ +function CfnApplicationPropsValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('location', cdk.requiredValidator)(properties.location)); + errors.collect(cdk.propertyValidator('location', cdk.unionValidator(CfnApplication_ApplicationLocationPropertyValidator, cdk.validateString))(properties.location)); + errors.collect(cdk.propertyValidator('notificationArns', cdk.listValidator(cdk.validateString))(properties.notificationArns)); + errors.collect(cdk.propertyValidator('parameters', cdk.hashValidator(cdk.validateString))(properties.parameters)); + errors.collect(cdk.propertyValidator('tags', cdk.hashValidator(cdk.validateString))(properties.tags)); + errors.collect(cdk.propertyValidator('timeoutInMinutes', cdk.validateNumber)(properties.timeoutInMinutes)); + return errors.wrap('supplied properties not correct for "CfnApplicationProps"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Application` resource + * + * @param properties - the TypeScript properties of a `CfnApplicationProps` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Application` resource. + */ +// @ts-ignore TS6133 +function cfnApplicationPropsToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnApplicationPropsValidator(properties).assertSuccess(); + return { + Location: cdk.unionMapper([CfnApplication_ApplicationLocationPropertyValidator, cdk.validateString], [cfnApplicationApplicationLocationPropertyToCloudFormation, cdk.stringToCloudFormation])(properties.location), + NotificationArns: cdk.listMapper(cdk.stringToCloudFormation)(properties.notificationArns), + Parameters: cdk.hashMapper(cdk.stringToCloudFormation)(properties.parameters), + Tags: cdk.hashMapper(cdk.stringToCloudFormation)(properties.tags), + TimeoutInMinutes: cdk.numberToCloudFormation(properties.timeoutInMinutes), + }; +} + +/** + * A CloudFormation `AWS::Serverless::Application` + * + * @cloudformationResource AWS::Serverless::Application + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ +export class CfnApplication extends cdk.CfnResource { + /** + * The CloudFormation resource type name for this resource class. + */ + public static readonly resourceTypeName = "AWS::Serverless::Application"; + /** + * The `Transform` a template must use in order to use this resource + */ + public static readonly requiredTransform = "AWS::Serverless-2016-10-31"; + public readonly applicationName: string; + + /** + * The `TagManager` handles setting, removing and formatting tags + * + * Tags should be managed either passing them as properties during + * initiation or by calling methods on this object. If both techniques are + * used only the tags from the TagManager will be used. `Tag` (aspect) + * will use the manager. + */ + public readonly tags: cdk.TagManager; + + /** + * Create a new `AWS::Serverless::Application`. + * + * @param scope - scope in which this resource is defined + * @param id - scoped id of the resource + * @param props - resource properties + */ + constructor(scope: cdk.Construct, id: string, props: CfnApplicationProps) { + super(scope, id, { type: CfnApplication.resourceTypeName, properties: props }); + cdk.requireProperty(props, 'location', this); + // If a different transform than the required one is in use, this resource cannot be used + if (this.node.stack.templateOptions.transform && this.node.stack.templateOptions.transform !== CfnApplication.requiredTransform) { + throw new Error(`The ${JSON.stringify(CfnApplication.requiredTransform)} transform is required when using CfnApplication, but the ${JSON.stringify(this.node.stack.templateOptions.transform)} is used.`); + } + // Automatically configure the required transform + this.node.stack.templateOptions.transform = CfnApplication.requiredTransform; + this.applicationName = this.ref.toString(); + const tags = props === undefined ? undefined : props.tags; + this.tags = new cdk.TagManager(cdk.TagType.Map, "AWS::Serverless::Application", tags); + } + + public get propertyOverrides(): CfnApplicationProps { + return this.untypedPropertyOverrides; + } + protected renderProperties(properties: any): { [key: string]: any } { + return cfnApplicationPropsToCloudFormation(this.node.resolve(properties)); + } +} + +export namespace CfnApplication { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + export interface ApplicationLocationProperty { + /** + * `CfnApplication.ApplicationLocationProperty.ApplicationId` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + applicationId: string; + /** + * `CfnApplication.ApplicationLocationProperty.SemanticVersion` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + semanticVersion: string; + } +} + +/** + * Determine whether the given properties match those of a `ApplicationLocationProperty` + * + * @param properties - the TypeScript properties of a `ApplicationLocationProperty` + * + * @returns the result of the validation. + */ +function CfnApplication_ApplicationLocationPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('applicationId', cdk.requiredValidator)(properties.applicationId)); + errors.collect(cdk.propertyValidator('applicationId', cdk.validateString)(properties.applicationId)); + errors.collect(cdk.propertyValidator('semanticVersion', cdk.requiredValidator)(properties.semanticVersion)); + errors.collect(cdk.propertyValidator('semanticVersion', cdk.validateString)(properties.semanticVersion)); + return errors.wrap('supplied properties not correct for "ApplicationLocationProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Application.ApplicationLocation` resource + * + * @param properties - the TypeScript properties of a `ApplicationLocationProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Application.ApplicationLocation` resource. + */ +// @ts-ignore TS6133 +function cfnApplicationApplicationLocationPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnApplication_ApplicationLocationPropertyValidator(properties).assertSuccess(); + return { + ApplicationId: cdk.stringToCloudFormation(properties.applicationId), + SemanticVersion: cdk.stringToCloudFormation(properties.semanticVersion), + }; +} + +/** + * Properties for defining a `AWS::Serverless::Function` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ +export interface CfnFunctionProps { + /** + * `AWS::Serverless::Function.CodeUri` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + codeUri: CfnFunction.S3LocationProperty | string | cdk.Token; + /** + * `AWS::Serverless::Function.Handler` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + handler: string; + /** + * `AWS::Serverless::Function.Runtime` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + runtime: string; + /** + * `AWS::Serverless::Function.AutoPublishAlias` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + autoPublishAlias?: string; + /** + * `AWS::Serverless::Function.DeadLetterQueue` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + deadLetterQueue?: CfnFunction.DeadLetterQueueProperty | cdk.Token; + /** + * `AWS::Serverless::Function.DeploymentPreference` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#deploymentpreference-object + */ + deploymentPreference?: CfnFunction.DeploymentPreferenceProperty | cdk.Token; + /** + * `AWS::Serverless::Function.Description` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + description?: string; + /** + * `AWS::Serverless::Function.Environment` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + environment?: CfnFunction.FunctionEnvironmentProperty | cdk.Token; + /** + * `AWS::Serverless::Function.Events` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + events?: { [key: string]: (CfnFunction.EventSourceProperty | cdk.Token) } | cdk.Token; + /** + * `AWS::Serverless::Function.FunctionName` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + functionName?: string; + /** + * `AWS::Serverless::Function.KmsKeyArn` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + kmsKeyArn?: string; + /** + * `AWS::Serverless::Function.Layers` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + layers?: string[]; + /** + * `AWS::Serverless::Function.MemorySize` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + memorySize?: number | cdk.Token; + /** + * `AWS::Serverless::Function.Policies` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + policies?: Array | CfnFunction.IAMPolicyDocumentProperty | string | cdk.Token; + /** + * `AWS::Serverless::Function.ReservedConcurrentExecutions` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + reservedConcurrentExecutions?: number | cdk.Token; + /** + * `AWS::Serverless::Function.Role` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + role?: string; + /** + * `AWS::Serverless::Function.Tags` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + tags?: { [key: string]: (string) }; + /** + * `AWS::Serverless::Function.Timeout` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + timeout?: number | cdk.Token; + /** + * `AWS::Serverless::Function.Tracing` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + tracing?: string; + /** + * `AWS::Serverless::Function.VpcConfig` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + vpcConfig?: CfnFunction.VpcConfigProperty | cdk.Token; +} + +/** + * Determine whether the given properties match those of a `CfnFunctionProps` + * + * @param properties - the TypeScript properties of a `CfnFunctionProps` + * + * @returns the result of the validation. + */ +function CfnFunctionPropsValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('autoPublishAlias', cdk.validateString)(properties.autoPublishAlias)); + errors.collect(cdk.propertyValidator('codeUri', cdk.requiredValidator)(properties.codeUri)); + errors.collect(cdk.propertyValidator('codeUri', cdk.unionValidator(CfnFunction_S3LocationPropertyValidator, cdk.validateString))(properties.codeUri)); + errors.collect(cdk.propertyValidator('deadLetterQueue', CfnFunction_DeadLetterQueuePropertyValidator)(properties.deadLetterQueue)); + errors.collect(cdk.propertyValidator('deploymentPreference', CfnFunction_DeploymentPreferencePropertyValidator)(properties.deploymentPreference)); + errors.collect(cdk.propertyValidator('description', cdk.validateString)(properties.description)); + errors.collect(cdk.propertyValidator('environment', CfnFunction_FunctionEnvironmentPropertyValidator)(properties.environment)); + errors.collect(cdk.propertyValidator('events', cdk.hashValidator(CfnFunction_EventSourcePropertyValidator))(properties.events)); + errors.collect(cdk.propertyValidator('functionName', cdk.validateString)(properties.functionName)); + errors.collect(cdk.propertyValidator('handler', cdk.requiredValidator)(properties.handler)); + errors.collect(cdk.propertyValidator('handler', cdk.validateString)(properties.handler)); + errors.collect(cdk.propertyValidator('kmsKeyArn', cdk.validateString)(properties.kmsKeyArn)); + errors.collect(cdk.propertyValidator('layers', cdk.listValidator(cdk.validateString))(properties.layers)); + errors.collect(cdk.propertyValidator('memorySize', cdk.validateNumber)(properties.memorySize)); + errors.collect(cdk.propertyValidator('policies', cdk.unionValidator(cdk.unionValidator(CfnFunction_IAMPolicyDocumentPropertyValidator, cdk.validateString), cdk.listValidator(cdk.unionValidator(CfnFunction_IAMPolicyDocumentPropertyValidator, cdk.validateString))))(properties.policies)); + errors.collect(cdk.propertyValidator('reservedConcurrentExecutions', cdk.validateNumber)(properties.reservedConcurrentExecutions)); + errors.collect(cdk.propertyValidator('role', cdk.validateString)(properties.role)); + errors.collect(cdk.propertyValidator('runtime', cdk.requiredValidator)(properties.runtime)); + errors.collect(cdk.propertyValidator('runtime', cdk.validateString)(properties.runtime)); + errors.collect(cdk.propertyValidator('tags', cdk.hashValidator(cdk.validateString))(properties.tags)); + errors.collect(cdk.propertyValidator('timeout', cdk.validateNumber)(properties.timeout)); + errors.collect(cdk.propertyValidator('tracing', cdk.validateString)(properties.tracing)); + errors.collect(cdk.propertyValidator('vpcConfig', CfnFunction_VpcConfigPropertyValidator)(properties.vpcConfig)); + return errors.wrap('supplied properties not correct for "CfnFunctionProps"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function` resource + * + * @param properties - the TypeScript properties of a `CfnFunctionProps` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionPropsToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunctionPropsValidator(properties).assertSuccess(); + return { + CodeUri: cdk.unionMapper([CfnFunction_S3LocationPropertyValidator, cdk.validateString], [cfnFunctionS3LocationPropertyToCloudFormation, cdk.stringToCloudFormation])(properties.codeUri), + Handler: cdk.stringToCloudFormation(properties.handler), + Runtime: cdk.stringToCloudFormation(properties.runtime), + AutoPublishAlias: cdk.stringToCloudFormation(properties.autoPublishAlias), + DeadLetterQueue: cfnFunctionDeadLetterQueuePropertyToCloudFormation(properties.deadLetterQueue), + DeploymentPreference: cfnFunctionDeploymentPreferencePropertyToCloudFormation(properties.deploymentPreference), + Description: cdk.stringToCloudFormation(properties.description), + Environment: cfnFunctionFunctionEnvironmentPropertyToCloudFormation(properties.environment), + Events: cdk.hashMapper(cfnFunctionEventSourcePropertyToCloudFormation)(properties.events), + FunctionName: cdk.stringToCloudFormation(properties.functionName), + KmsKeyArn: cdk.stringToCloudFormation(properties.kmsKeyArn), + Layers: cdk.listMapper(cdk.stringToCloudFormation)(properties.layers), + MemorySize: cdk.numberToCloudFormation(properties.memorySize), + Policies: cdk.unionMapper([cdk.unionValidator(CfnFunction_IAMPolicyDocumentPropertyValidator, cdk.validateString), cdk.listValidator(cdk.unionValidator(CfnFunction_IAMPolicyDocumentPropertyValidator, cdk.validateString))], [cdk.unionMapper([CfnFunction_IAMPolicyDocumentPropertyValidator, cdk.validateString], [cfnFunctionIAMPolicyDocumentPropertyToCloudFormation, cdk.stringToCloudFormation]), cdk.listMapper(cdk.unionMapper([CfnFunction_IAMPolicyDocumentPropertyValidator, cdk.validateString], [cfnFunctionIAMPolicyDocumentPropertyToCloudFormation, cdk.stringToCloudFormation]))])(properties.policies), + ReservedConcurrentExecutions: cdk.numberToCloudFormation(properties.reservedConcurrentExecutions), + Role: cdk.stringToCloudFormation(properties.role), + Tags: cdk.hashMapper(cdk.stringToCloudFormation)(properties.tags), + Timeout: cdk.numberToCloudFormation(properties.timeout), + Tracing: cdk.stringToCloudFormation(properties.tracing), + VpcConfig: cfnFunctionVpcConfigPropertyToCloudFormation(properties.vpcConfig), + }; +} + +/** + * A CloudFormation `AWS::Serverless::Function` + * + * @cloudformationResource AWS::Serverless::Function + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ +export class CfnFunction extends cdk.CfnResource { + /** + * The CloudFormation resource type name for this resource class. + */ + public static readonly resourceTypeName = "AWS::Serverless::Function"; + /** + * The `Transform` a template must use in order to use this resource + */ + public static readonly requiredTransform = "AWS::Serverless-2016-10-31"; + public readonly functionName: string; + + /** + * The `TagManager` handles setting, removing and formatting tags + * + * Tags should be managed either passing them as properties during + * initiation or by calling methods on this object. If both techniques are + * used only the tags from the TagManager will be used. `Tag` (aspect) + * will use the manager. + */ + public readonly tags: cdk.TagManager; + + /** + * Create a new `AWS::Serverless::Function`. + * + * @param scope - scope in which this resource is defined + * @param id - scoped id of the resource + * @param props - resource properties + */ + constructor(scope: cdk.Construct, id: string, props: CfnFunctionProps) { + super(scope, id, { type: CfnFunction.resourceTypeName, properties: props }); + cdk.requireProperty(props, 'codeUri', this); + cdk.requireProperty(props, 'handler', this); + cdk.requireProperty(props, 'runtime', this); + // If a different transform than the required one is in use, this resource cannot be used + if (this.node.stack.templateOptions.transform && this.node.stack.templateOptions.transform !== CfnFunction.requiredTransform) { + throw new Error(`The ${JSON.stringify(CfnFunction.requiredTransform)} transform is required when using CfnFunction, but the ${JSON.stringify(this.node.stack.templateOptions.transform)} is used.`); + } + // Automatically configure the required transform + this.node.stack.templateOptions.transform = CfnFunction.requiredTransform; + this.functionName = this.ref.toString(); + const tags = props === undefined ? undefined : props.tags; + this.tags = new cdk.TagManager(cdk.TagType.Map, "AWS::Serverless::Function", tags); + } + + public get propertyOverrides(): CfnFunctionProps { + return this.untypedPropertyOverrides; + } + protected renderProperties(properties: any): { [key: string]: any } { + return cfnFunctionPropsToCloudFormation(this.node.resolve(properties)); + } +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#alexaskill + */ + export interface AlexaSkillEventProperty { + /** + * `CfnFunction.AlexaSkillEventProperty.Variables` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#alexaskill + */ + variables?: { [key: string]: (string) } | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `AlexaSkillEventProperty` + * + * @param properties - the TypeScript properties of a `AlexaSkillEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_AlexaSkillEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('variables', cdk.hashValidator(cdk.validateString))(properties.variables)); + return errors.wrap('supplied properties not correct for "AlexaSkillEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.AlexaSkillEvent` resource + * + * @param properties - the TypeScript properties of a `AlexaSkillEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.AlexaSkillEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionAlexaSkillEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_AlexaSkillEventPropertyValidator(properties).assertSuccess(); + return { + Variables: cdk.hashMapper(cdk.stringToCloudFormation)(properties.variables), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + */ + export interface ApiEventProperty { + /** + * `CfnFunction.ApiEventProperty.Method` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + */ + method: string; + /** + * `CfnFunction.ApiEventProperty.Path` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + */ + path: string; + /** + * `CfnFunction.ApiEventProperty.RestApiId` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + */ + restApiId?: string; + } +} + +/** + * Determine whether the given properties match those of a `ApiEventProperty` + * + * @param properties - the TypeScript properties of a `ApiEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_ApiEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('method', cdk.requiredValidator)(properties.method)); + errors.collect(cdk.propertyValidator('method', cdk.validateString)(properties.method)); + errors.collect(cdk.propertyValidator('path', cdk.requiredValidator)(properties.path)); + errors.collect(cdk.propertyValidator('path', cdk.validateString)(properties.path)); + errors.collect(cdk.propertyValidator('restApiId', cdk.validateString)(properties.restApiId)); + return errors.wrap('supplied properties not correct for "ApiEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.ApiEvent` resource + * + * @param properties - the TypeScript properties of a `ApiEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.ApiEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionApiEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_ApiEventPropertyValidator(properties).assertSuccess(); + return { + Method: cdk.stringToCloudFormation(properties.method), + Path: cdk.stringToCloudFormation(properties.path), + RestApiId: cdk.stringToCloudFormation(properties.restApiId), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#cloudwatchevent + */ + export interface CloudWatchEventEventProperty { + /** + * `CfnFunction.CloudWatchEventEventProperty.Input` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#cloudwatchevent + */ + input?: string; + /** + * `CfnFunction.CloudWatchEventEventProperty.InputPath` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#cloudwatchevent + */ + inputPath?: string; + /** + * `CfnFunction.CloudWatchEventEventProperty.Pattern` + * @see http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CloudWatchEventsandEventPatterns.html + */ + pattern: object | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `CloudWatchEventEventProperty` + * + * @param properties - the TypeScript properties of a `CloudWatchEventEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_CloudWatchEventEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('input', cdk.validateString)(properties.input)); + errors.collect(cdk.propertyValidator('inputPath', cdk.validateString)(properties.inputPath)); + errors.collect(cdk.propertyValidator('pattern', cdk.requiredValidator)(properties.pattern)); + errors.collect(cdk.propertyValidator('pattern', cdk.validateObject)(properties.pattern)); + return errors.wrap('supplied properties not correct for "CloudWatchEventEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.CloudWatchEventEvent` resource + * + * @param properties - the TypeScript properties of a `CloudWatchEventEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.CloudWatchEventEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionCloudWatchEventEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_CloudWatchEventEventPropertyValidator(properties).assertSuccess(); + return { + Input: cdk.stringToCloudFormation(properties.input), + InputPath: cdk.stringToCloudFormation(properties.inputPath), + Pattern: cdk.objectToCloudFormation(properties.pattern), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#deadletterqueue-object + */ + export interface DeadLetterQueueProperty { + /** + * `CfnFunction.DeadLetterQueueProperty.TargetArn` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + targetArn: string; + /** + * `CfnFunction.DeadLetterQueueProperty.Type` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + type: string; + } +} + +/** + * Determine whether the given properties match those of a `DeadLetterQueueProperty` + * + * @param properties - the TypeScript properties of a `DeadLetterQueueProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_DeadLetterQueuePropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('targetArn', cdk.requiredValidator)(properties.targetArn)); + errors.collect(cdk.propertyValidator('targetArn', cdk.validateString)(properties.targetArn)); + errors.collect(cdk.propertyValidator('type', cdk.requiredValidator)(properties.type)); + errors.collect(cdk.propertyValidator('type', cdk.validateString)(properties.type)); + return errors.wrap('supplied properties not correct for "DeadLetterQueueProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.DeadLetterQueue` resource + * + * @param properties - the TypeScript properties of a `DeadLetterQueueProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.DeadLetterQueue` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionDeadLetterQueuePropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_DeadLetterQueuePropertyValidator(properties).assertSuccess(); + return { + TargetArn: cdk.stringToCloudFormation(properties.targetArn), + Type: cdk.stringToCloudFormation(properties.type), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/docs/safe_lambda_deployments.rst + */ + export interface DeploymentPreferenceProperty { + /** + * `CfnFunction.DeploymentPreferenceProperty.Enabled` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#deploymentpreference-object + */ + enabled: boolean | cdk.Token; + /** + * `CfnFunction.DeploymentPreferenceProperty.Type` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#deploymentpreference-object + */ + type: string; + /** + * `CfnFunction.DeploymentPreferenceProperty.Alarms` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#deploymentpreference-object + */ + alarms?: string[]; + /** + * `CfnFunction.DeploymentPreferenceProperty.Hooks` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#deploymentpreference-object + */ + hooks?: string[]; + } +} + +/** + * Determine whether the given properties match those of a `DeploymentPreferenceProperty` + * + * @param properties - the TypeScript properties of a `DeploymentPreferenceProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_DeploymentPreferencePropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('enabled', cdk.requiredValidator)(properties.enabled)); + errors.collect(cdk.propertyValidator('enabled', cdk.validateBoolean)(properties.enabled)); + errors.collect(cdk.propertyValidator('type', cdk.requiredValidator)(properties.type)); + errors.collect(cdk.propertyValidator('type', cdk.validateString)(properties.type)); + errors.collect(cdk.propertyValidator('alarms', cdk.listValidator(cdk.validateString))(properties.alarms)); + errors.collect(cdk.propertyValidator('hooks', cdk.listValidator(cdk.validateString))(properties.hooks)); + return errors.wrap('supplied properties not correct for "DeploymentPreferenceProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.DeploymentPreference` resource + * + * @param properties - the TypeScript properties of a `DeploymentPreferenceProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.DeploymentPreference` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionDeploymentPreferencePropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_DeploymentPreferencePropertyValidator(properties).assertSuccess(); + return { + Enabled: cdk.booleanToCloudFormation(properties.enabled), + Type: cdk.stringToCloudFormation(properties.type), + Alarms: cdk.listMapper(cdk.stringToCloudFormation)(properties.alarms), + Hooks: cdk.listMapper(cdk.stringToCloudFormation)(properties.hooks), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#dynamodb + */ + export interface DynamoDBEventProperty { + /** + * `CfnFunction.DynamoDBEventProperty.BatchSize` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#dynamodb + */ + batchSize: number | cdk.Token; + /** + * `CfnFunction.DynamoDBEventProperty.StartingPosition` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#dynamodb + */ + startingPosition: string; + /** + * `CfnFunction.DynamoDBEventProperty.Stream` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#dynamodb + */ + stream: string; + } +} + +/** + * Determine whether the given properties match those of a `DynamoDBEventProperty` + * + * @param properties - the TypeScript properties of a `DynamoDBEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_DynamoDBEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('batchSize', cdk.requiredValidator)(properties.batchSize)); + errors.collect(cdk.propertyValidator('batchSize', cdk.validateNumber)(properties.batchSize)); + errors.collect(cdk.propertyValidator('startingPosition', cdk.requiredValidator)(properties.startingPosition)); + errors.collect(cdk.propertyValidator('startingPosition', cdk.validateString)(properties.startingPosition)); + errors.collect(cdk.propertyValidator('stream', cdk.requiredValidator)(properties.stream)); + errors.collect(cdk.propertyValidator('stream', cdk.validateString)(properties.stream)); + return errors.wrap('supplied properties not correct for "DynamoDBEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.DynamoDBEvent` resource + * + * @param properties - the TypeScript properties of a `DynamoDBEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.DynamoDBEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionDynamoDBEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_DynamoDBEventPropertyValidator(properties).assertSuccess(); + return { + BatchSize: cdk.numberToCloudFormation(properties.batchSize), + StartingPosition: cdk.stringToCloudFormation(properties.startingPosition), + Stream: cdk.stringToCloudFormation(properties.stream), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#event-source-object + */ + export interface EventSourceProperty { + /** + * `CfnFunction.EventSourceProperty.Properties` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#event-source-types + */ + properties: CfnFunction.S3EventProperty | CfnFunction.SNSEventProperty | CfnFunction.SQSEventProperty | CfnFunction.KinesisEventProperty | CfnFunction.DynamoDBEventProperty | CfnFunction.ApiEventProperty | CfnFunction.ScheduleEventProperty | CfnFunction.CloudWatchEventEventProperty | CfnFunction.IoTRuleEventProperty | CfnFunction.AlexaSkillEventProperty | cdk.Token; + /** + * `CfnFunction.EventSourceProperty.Type` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#event-source-object + */ + type: string; + } +} + +/** + * Determine whether the given properties match those of a `EventSourceProperty` + * + * @param properties - the TypeScript properties of a `EventSourceProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_EventSourcePropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('properties', cdk.requiredValidator)(properties.properties)); + errors.collect(cdk.propertyValidator('properties', cdk.unionValidator(CfnFunction_S3EventPropertyValidator, CfnFunction_SNSEventPropertyValidator, CfnFunction_SQSEventPropertyValidator, CfnFunction_KinesisEventPropertyValidator, CfnFunction_DynamoDBEventPropertyValidator, CfnFunction_ApiEventPropertyValidator, CfnFunction_ScheduleEventPropertyValidator, CfnFunction_CloudWatchEventEventPropertyValidator, CfnFunction_IoTRuleEventPropertyValidator, CfnFunction_AlexaSkillEventPropertyValidator))(properties.properties)); + errors.collect(cdk.propertyValidator('type', cdk.requiredValidator)(properties.type)); + errors.collect(cdk.propertyValidator('type', cdk.validateString)(properties.type)); + return errors.wrap('supplied properties not correct for "EventSourceProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.EventSource` resource + * + * @param properties - the TypeScript properties of a `EventSourceProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.EventSource` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionEventSourcePropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_EventSourcePropertyValidator(properties).assertSuccess(); + return { + Properties: cdk.unionMapper([CfnFunction_S3EventPropertyValidator, CfnFunction_SNSEventPropertyValidator, CfnFunction_SQSEventPropertyValidator, CfnFunction_KinesisEventPropertyValidator, CfnFunction_DynamoDBEventPropertyValidator, CfnFunction_ApiEventPropertyValidator, CfnFunction_ScheduleEventPropertyValidator, CfnFunction_CloudWatchEventEventPropertyValidator, CfnFunction_IoTRuleEventPropertyValidator, CfnFunction_AlexaSkillEventPropertyValidator], [cfnFunctionS3EventPropertyToCloudFormation, cfnFunctionSNSEventPropertyToCloudFormation, cfnFunctionSQSEventPropertyToCloudFormation, cfnFunctionKinesisEventPropertyToCloudFormation, cfnFunctionDynamoDBEventPropertyToCloudFormation, cfnFunctionApiEventPropertyToCloudFormation, cfnFunctionScheduleEventPropertyToCloudFormation, cfnFunctionCloudWatchEventEventPropertyToCloudFormation, cfnFunctionIoTRuleEventPropertyToCloudFormation, cfnFunctionAlexaSkillEventPropertyToCloudFormation])(properties.properties), + Type: cdk.stringToCloudFormation(properties.type), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object + */ + export interface FunctionEnvironmentProperty { + /** + * `CfnFunction.FunctionEnvironmentProperty.Variables` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object + */ + variables: { [key: string]: (string) } | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `FunctionEnvironmentProperty` + * + * @param properties - the TypeScript properties of a `FunctionEnvironmentProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_FunctionEnvironmentPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('variables', cdk.requiredValidator)(properties.variables)); + errors.collect(cdk.propertyValidator('variables', cdk.hashValidator(cdk.validateString))(properties.variables)); + return errors.wrap('supplied properties not correct for "FunctionEnvironmentProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.FunctionEnvironment` resource + * + * @param properties - the TypeScript properties of a `FunctionEnvironmentProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.FunctionEnvironment` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionFunctionEnvironmentPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_FunctionEnvironmentPropertyValidator(properties).assertSuccess(); + return { + Variables: cdk.hashMapper(cdk.stringToCloudFormation)(properties.variables), + }; +} + +export namespace CfnFunction { + /** + * @see http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html + */ + export interface IAMPolicyDocumentProperty { + /** + * `CfnFunction.IAMPolicyDocumentProperty.Statement` + * @see http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html + */ + statement: object | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `IAMPolicyDocumentProperty` + * + * @param properties - the TypeScript properties of a `IAMPolicyDocumentProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_IAMPolicyDocumentPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('statement', cdk.requiredValidator)(properties.statement)); + errors.collect(cdk.propertyValidator('statement', cdk.validateObject)(properties.statement)); + return errors.wrap('supplied properties not correct for "IAMPolicyDocumentProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.IAMPolicyDocument` resource + * + * @param properties - the TypeScript properties of a `IAMPolicyDocumentProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.IAMPolicyDocument` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionIAMPolicyDocumentPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_IAMPolicyDocumentPropertyValidator(properties).assertSuccess(); + return { + Statement: cdk.objectToCloudFormation(properties.statement), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#iotrule + */ + export interface IoTRuleEventProperty { + /** + * `CfnFunction.IoTRuleEventProperty.AwsIotSqlVersion` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#iotrule + */ + awsIotSqlVersion?: string; + /** + * `CfnFunction.IoTRuleEventProperty.Sql` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#iotrule + */ + sql: string; + } +} + +/** + * Determine whether the given properties match those of a `IoTRuleEventProperty` + * + * @param properties - the TypeScript properties of a `IoTRuleEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_IoTRuleEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('awsIotSqlVersion', cdk.validateString)(properties.awsIotSqlVersion)); + errors.collect(cdk.propertyValidator('sql', cdk.requiredValidator)(properties.sql)); + errors.collect(cdk.propertyValidator('sql', cdk.validateString)(properties.sql)); + return errors.wrap('supplied properties not correct for "IoTRuleEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.IoTRuleEvent` resource + * + * @param properties - the TypeScript properties of a `IoTRuleEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.IoTRuleEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionIoTRuleEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_IoTRuleEventPropertyValidator(properties).assertSuccess(); + return { + AwsIotSqlVersion: cdk.stringToCloudFormation(properties.awsIotSqlVersion), + Sql: cdk.stringToCloudFormation(properties.sql), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#kinesis + */ + export interface KinesisEventProperty { + /** + * `CfnFunction.KinesisEventProperty.BatchSize` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#kinesis + */ + batchSize?: number | cdk.Token; + /** + * `CfnFunction.KinesisEventProperty.StartingPosition` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#kinesis + */ + startingPosition: string; + /** + * `CfnFunction.KinesisEventProperty.Stream` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#kinesis + */ + stream: string; + } +} + +/** + * Determine whether the given properties match those of a `KinesisEventProperty` + * + * @param properties - the TypeScript properties of a `KinesisEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_KinesisEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('batchSize', cdk.validateNumber)(properties.batchSize)); + errors.collect(cdk.propertyValidator('startingPosition', cdk.requiredValidator)(properties.startingPosition)); + errors.collect(cdk.propertyValidator('startingPosition', cdk.validateString)(properties.startingPosition)); + errors.collect(cdk.propertyValidator('stream', cdk.requiredValidator)(properties.stream)); + errors.collect(cdk.propertyValidator('stream', cdk.validateString)(properties.stream)); + return errors.wrap('supplied properties not correct for "KinesisEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.KinesisEvent` resource + * + * @param properties - the TypeScript properties of a `KinesisEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.KinesisEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionKinesisEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_KinesisEventPropertyValidator(properties).assertSuccess(); + return { + BatchSize: cdk.numberToCloudFormation(properties.batchSize), + StartingPosition: cdk.stringToCloudFormation(properties.startingPosition), + Stream: cdk.stringToCloudFormation(properties.stream), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#s3 + */ + export interface S3EventProperty { + /** + * `CfnFunction.S3EventProperty.Bucket` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#s3 + */ + bucket: string; + /** + * `CfnFunction.S3EventProperty.Events` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#s3 + */ + events: string[] | string | cdk.Token; + /** + * `CfnFunction.S3EventProperty.Filter` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#s3 + */ + filter?: CfnFunction.S3NotificationFilterProperty | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `S3EventProperty` + * + * @param properties - the TypeScript properties of a `S3EventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_S3EventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('bucket', cdk.requiredValidator)(properties.bucket)); + errors.collect(cdk.propertyValidator('bucket', cdk.validateString)(properties.bucket)); + errors.collect(cdk.propertyValidator('events', cdk.requiredValidator)(properties.events)); + errors.collect(cdk.propertyValidator('events', cdk.unionValidator(cdk.unionValidator(cdk.validateString), cdk.listValidator(cdk.unionValidator(cdk.validateString))))(properties.events)); + errors.collect(cdk.propertyValidator('filter', CfnFunction_S3NotificationFilterPropertyValidator)(properties.filter)); + return errors.wrap('supplied properties not correct for "S3EventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.S3Event` resource + * + * @param properties - the TypeScript properties of a `S3EventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.S3Event` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionS3EventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_S3EventPropertyValidator(properties).assertSuccess(); + return { + Bucket: cdk.stringToCloudFormation(properties.bucket), + Events: cdk.unionMapper([cdk.unionValidator(cdk.validateString), cdk.listValidator(cdk.unionValidator(cdk.validateString))], [cdk.unionMapper([cdk.validateString], [cdk.stringToCloudFormation]), cdk.listMapper(cdk.unionMapper([cdk.validateString], [cdk.stringToCloudFormation]))])(properties.events), + Filter: cfnFunctionS3NotificationFilterPropertyToCloudFormation(properties.filter), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#s3-location-object + */ + export interface S3LocationProperty { + /** + * `CfnFunction.S3LocationProperty.Bucket` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + bucket: string; + /** + * `CfnFunction.S3LocationProperty.Key` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + key: string; + /** + * `CfnFunction.S3LocationProperty.Version` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + version?: number | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `S3LocationProperty` + * + * @param properties - the TypeScript properties of a `S3LocationProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_S3LocationPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('bucket', cdk.requiredValidator)(properties.bucket)); + errors.collect(cdk.propertyValidator('bucket', cdk.validateString)(properties.bucket)); + errors.collect(cdk.propertyValidator('key', cdk.requiredValidator)(properties.key)); + errors.collect(cdk.propertyValidator('key', cdk.validateString)(properties.key)); + errors.collect(cdk.propertyValidator('version', cdk.validateNumber)(properties.version)); + return errors.wrap('supplied properties not correct for "S3LocationProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.S3Location` resource + * + * @param properties - the TypeScript properties of a `S3LocationProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.S3Location` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionS3LocationPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_S3LocationPropertyValidator(properties).assertSuccess(); + return { + Bucket: cdk.stringToCloudFormation(properties.bucket), + Key: cdk.stringToCloudFormation(properties.key), + Version: cdk.numberToCloudFormation(properties.version), + }; +} + +export namespace CfnFunction { + /** + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-notificationconfiguration-config-filter.html + */ + export interface S3NotificationFilterProperty { + /** + * `CfnFunction.S3NotificationFilterProperty.S3Key` + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-notificationconfiguration-config-filter.html + */ + s3Key: string; + } +} + +/** + * Determine whether the given properties match those of a `S3NotificationFilterProperty` + * + * @param properties - the TypeScript properties of a `S3NotificationFilterProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_S3NotificationFilterPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('s3Key', cdk.requiredValidator)(properties.s3Key)); + errors.collect(cdk.propertyValidator('s3Key', cdk.validateString)(properties.s3Key)); + return errors.wrap('supplied properties not correct for "S3NotificationFilterProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.S3NotificationFilter` resource + * + * @param properties - the TypeScript properties of a `S3NotificationFilterProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.S3NotificationFilter` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionS3NotificationFilterPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_S3NotificationFilterPropertyValidator(properties).assertSuccess(); + return { + S3Key: cdk.stringToCloudFormation(properties.s3Key), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#sns + */ + export interface SNSEventProperty { + /** + * `CfnFunction.SNSEventProperty.Topic` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#sns + */ + topic: string; + } +} + +/** + * Determine whether the given properties match those of a `SNSEventProperty` + * + * @param properties - the TypeScript properties of a `SNSEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_SNSEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('topic', cdk.requiredValidator)(properties.topic)); + errors.collect(cdk.propertyValidator('topic', cdk.validateString)(properties.topic)); + return errors.wrap('supplied properties not correct for "SNSEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.SNSEvent` resource + * + * @param properties - the TypeScript properties of a `SNSEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.SNSEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionSNSEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_SNSEventPropertyValidator(properties).assertSuccess(); + return { + Topic: cdk.stringToCloudFormation(properties.topic), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#sqs + */ + export interface SQSEventProperty { + /** + * `CfnFunction.SQSEventProperty.BatchSize` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#sqs + */ + batchSize?: number | cdk.Token; + /** + * `CfnFunction.SQSEventProperty.Queue` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#sqs + */ + queue: string; + } +} + +/** + * Determine whether the given properties match those of a `SQSEventProperty` + * + * @param properties - the TypeScript properties of a `SQSEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_SQSEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('batchSize', cdk.validateNumber)(properties.batchSize)); + errors.collect(cdk.propertyValidator('queue', cdk.requiredValidator)(properties.queue)); + errors.collect(cdk.propertyValidator('queue', cdk.validateString)(properties.queue)); + return errors.wrap('supplied properties not correct for "SQSEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.SQSEvent` resource + * + * @param properties - the TypeScript properties of a `SQSEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.SQSEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionSQSEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_SQSEventPropertyValidator(properties).assertSuccess(); + return { + BatchSize: cdk.numberToCloudFormation(properties.batchSize), + Queue: cdk.stringToCloudFormation(properties.queue), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#schedule + */ + export interface ScheduleEventProperty { + /** + * `CfnFunction.ScheduleEventProperty.Input` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#schedule + */ + input?: string; + /** + * `CfnFunction.ScheduleEventProperty.Schedule` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#schedule + */ + schedule: string; + } +} + +/** + * Determine whether the given properties match those of a `ScheduleEventProperty` + * + * @param properties - the TypeScript properties of a `ScheduleEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_ScheduleEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('input', cdk.validateString)(properties.input)); + errors.collect(cdk.propertyValidator('schedule', cdk.requiredValidator)(properties.schedule)); + errors.collect(cdk.propertyValidator('schedule', cdk.validateString)(properties.schedule)); + return errors.wrap('supplied properties not correct for "ScheduleEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.ScheduleEvent` resource + * + * @param properties - the TypeScript properties of a `ScheduleEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.ScheduleEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionScheduleEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_ScheduleEventPropertyValidator(properties).assertSuccess(); + return { + Input: cdk.stringToCloudFormation(properties.input), + Schedule: cdk.stringToCloudFormation(properties.schedule), + }; +} + +export namespace CfnFunction { + /** + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-vpcconfig.html + */ + export interface VpcConfigProperty { + /** + * `CfnFunction.VpcConfigProperty.SecurityGroupIds` + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-vpcconfig.html + */ + securityGroupIds: string[]; + /** + * `CfnFunction.VpcConfigProperty.SubnetIds` + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-vpcconfig.html + */ + subnetIds: string[]; + } +} + +/** + * Determine whether the given properties match those of a `VpcConfigProperty` + * + * @param properties - the TypeScript properties of a `VpcConfigProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_VpcConfigPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('securityGroupIds', cdk.requiredValidator)(properties.securityGroupIds)); + errors.collect(cdk.propertyValidator('securityGroupIds', cdk.listValidator(cdk.validateString))(properties.securityGroupIds)); + errors.collect(cdk.propertyValidator('subnetIds', cdk.requiredValidator)(properties.subnetIds)); + errors.collect(cdk.propertyValidator('subnetIds', cdk.listValidator(cdk.validateString))(properties.subnetIds)); + return errors.wrap('supplied properties not correct for "VpcConfigProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.VpcConfig` resource + * + * @param properties - the TypeScript properties of a `VpcConfigProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.VpcConfig` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionVpcConfigPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_VpcConfigPropertyValidator(properties).assertSuccess(); + return { + SecurityGroupIds: cdk.listMapper(cdk.stringToCloudFormation)(properties.securityGroupIds), + SubnetIds: cdk.listMapper(cdk.stringToCloudFormation)(properties.subnetIds), + }; +} + +/** + * Properties for defining a `AWS::Serverless::LayerVersion` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ +export interface CfnLayerVersionProps { + /** + * `AWS::Serverless::LayerVersion.CompatibleRuntimes` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ + compatibleRuntimes?: string[]; + /** + * `AWS::Serverless::LayerVersion.ContentUri` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ + contentUri?: string; + /** + * `AWS::Serverless::LayerVersion.Description` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ + description?: string; + /** + * `AWS::Serverless::LayerVersion.LayerName` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ + layerName?: string; + /** + * `AWS::Serverless::LayerVersion.LicenseInfo` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ + licenseInfo?: string; + /** + * `AWS::Serverless::LayerVersion.RetentionPolicy` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ + retentionPolicy?: string; +} + +/** + * Determine whether the given properties match those of a `CfnLayerVersionProps` + * + * @param properties - the TypeScript properties of a `CfnLayerVersionProps` + * + * @returns the result of the validation. + */ +function CfnLayerVersionPropsValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('compatibleRuntimes', cdk.listValidator(cdk.validateString))(properties.compatibleRuntimes)); + errors.collect(cdk.propertyValidator('contentUri', cdk.validateString)(properties.contentUri)); + errors.collect(cdk.propertyValidator('description', cdk.validateString)(properties.description)); + errors.collect(cdk.propertyValidator('layerName', cdk.validateString)(properties.layerName)); + errors.collect(cdk.propertyValidator('licenseInfo', cdk.validateString)(properties.licenseInfo)); + errors.collect(cdk.propertyValidator('retentionPolicy', cdk.validateString)(properties.retentionPolicy)); + return errors.wrap('supplied properties not correct for "CfnLayerVersionProps"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::LayerVersion` resource + * + * @param properties - the TypeScript properties of a `CfnLayerVersionProps` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::LayerVersion` resource. + */ +// @ts-ignore TS6133 +function cfnLayerVersionPropsToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnLayerVersionPropsValidator(properties).assertSuccess(); + return { + CompatibleRuntimes: cdk.listMapper(cdk.stringToCloudFormation)(properties.compatibleRuntimes), + ContentUri: cdk.stringToCloudFormation(properties.contentUri), + Description: cdk.stringToCloudFormation(properties.description), + LayerName: cdk.stringToCloudFormation(properties.layerName), + LicenseInfo: cdk.stringToCloudFormation(properties.licenseInfo), + RetentionPolicy: cdk.stringToCloudFormation(properties.retentionPolicy), + }; +} + +/** + * A CloudFormation `AWS::Serverless::LayerVersion` + * + * @cloudformationResource AWS::Serverless::LayerVersion + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ +export class CfnLayerVersion extends cdk.CfnResource { + /** + * The CloudFormation resource type name for this resource class. + */ + public static readonly resourceTypeName = "AWS::Serverless::LayerVersion"; + /** + * The `Transform` a template must use in order to use this resource + */ + public static readonly requiredTransform = "AWS::Serverless-2016-10-31"; + public readonly layerVersionArn: string; + + /** + * Create a new `AWS::Serverless::LayerVersion`. + * + * @param scope - scope in which this resource is defined + * @param id - scoped id of the resource + * @param props - resource properties + */ + constructor(scope: cdk.Construct, id: string, props?: CfnLayerVersionProps) { + super(scope, id, { type: CfnLayerVersion.resourceTypeName, properties: props }); + // If a different transform than the required one is in use, this resource cannot be used + if (this.node.stack.templateOptions.transform && this.node.stack.templateOptions.transform !== CfnLayerVersion.requiredTransform) { + throw new Error(`The ${JSON.stringify(CfnLayerVersion.requiredTransform)} transform is required when using CfnLayerVersion, but the ${JSON.stringify(this.node.stack.templateOptions.transform)} is used.`); + } + // Automatically configure the required transform + this.node.stack.templateOptions.transform = CfnLayerVersion.requiredTransform; + this.layerVersionArn = this.ref.toString(); + } + + public get propertyOverrides(): CfnLayerVersionProps { + return this.untypedPropertyOverrides; + } + protected renderProperties(properties: any): { [key: string]: any } { + return cfnLayerVersionPropsToCloudFormation(this.node.resolve(properties)); + } +} + +/** + * Properties for defining a `AWS::Serverless::SimpleTable` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable + */ +export interface CfnSimpleTableProps { + /** + * `AWS::Serverless::SimpleTable.PrimaryKey` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#primary-key-object + */ + primaryKey?: CfnSimpleTable.PrimaryKeyProperty | cdk.Token; + /** + * `AWS::Serverless::SimpleTable.ProvisionedThroughput` + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-provisionedthroughput.html + */ + provisionedThroughput?: CfnSimpleTable.ProvisionedThroughputProperty | cdk.Token; + /** + * `AWS::Serverless::SimpleTable.SSESpecification` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable + */ + sseSpecification?: CfnSimpleTable.SSESpecificationProperty | cdk.Token; + /** + * `AWS::Serverless::SimpleTable.TableName` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable + */ + tableName?: string; + /** + * `AWS::Serverless::SimpleTable.Tags` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable + */ + tags?: { [key: string]: (string) }; +} + +/** + * Determine whether the given properties match those of a `CfnSimpleTableProps` + * + * @param properties - the TypeScript properties of a `CfnSimpleTableProps` + * + * @returns the result of the validation. + */ +function CfnSimpleTablePropsValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('primaryKey', CfnSimpleTable_PrimaryKeyPropertyValidator)(properties.primaryKey)); + errors.collect(cdk.propertyValidator('provisionedThroughput', CfnSimpleTable_ProvisionedThroughputPropertyValidator)(properties.provisionedThroughput)); + errors.collect(cdk.propertyValidator('sseSpecification', CfnSimpleTable_SSESpecificationPropertyValidator)(properties.sseSpecification)); + errors.collect(cdk.propertyValidator('tableName', cdk.validateString)(properties.tableName)); + errors.collect(cdk.propertyValidator('tags', cdk.hashValidator(cdk.validateString))(properties.tags)); + return errors.wrap('supplied properties not correct for "CfnSimpleTableProps"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable` resource + * + * @param properties - the TypeScript properties of a `CfnSimpleTableProps` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable` resource. + */ +// @ts-ignore TS6133 +function cfnSimpleTablePropsToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnSimpleTablePropsValidator(properties).assertSuccess(); + return { + PrimaryKey: cfnSimpleTablePrimaryKeyPropertyToCloudFormation(properties.primaryKey), + ProvisionedThroughput: cfnSimpleTableProvisionedThroughputPropertyToCloudFormation(properties.provisionedThroughput), + SSESpecification: cfnSimpleTableSSESpecificationPropertyToCloudFormation(properties.sseSpecification), + TableName: cdk.stringToCloudFormation(properties.tableName), + Tags: cdk.hashMapper(cdk.stringToCloudFormation)(properties.tags), + }; +} + +/** + * A CloudFormation `AWS::Serverless::SimpleTable` + * + * @cloudformationResource AWS::Serverless::SimpleTable + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable + */ +export class CfnSimpleTable extends cdk.CfnResource { + /** + * The CloudFormation resource type name for this resource class. + */ + public static readonly resourceTypeName = "AWS::Serverless::SimpleTable"; + /** + * The `Transform` a template must use in order to use this resource + */ + public static readonly requiredTransform = "AWS::Serverless-2016-10-31"; + public readonly simpleTableName: string; + + /** + * The `TagManager` handles setting, removing and formatting tags + * + * Tags should be managed either passing them as properties during + * initiation or by calling methods on this object. If both techniques are + * used only the tags from the TagManager will be used. `Tag` (aspect) + * will use the manager. + */ + public readonly tags: cdk.TagManager; + + /** + * Create a new `AWS::Serverless::SimpleTable`. + * + * @param scope - scope in which this resource is defined + * @param id - scoped id of the resource + * @param props - resource properties + */ + constructor(scope: cdk.Construct, id: string, props?: CfnSimpleTableProps) { + super(scope, id, { type: CfnSimpleTable.resourceTypeName, properties: props }); + // If a different transform than the required one is in use, this resource cannot be used + if (this.node.stack.templateOptions.transform && this.node.stack.templateOptions.transform !== CfnSimpleTable.requiredTransform) { + throw new Error(`The ${JSON.stringify(CfnSimpleTable.requiredTransform)} transform is required when using CfnSimpleTable, but the ${JSON.stringify(this.node.stack.templateOptions.transform)} is used.`); + } + // Automatically configure the required transform + this.node.stack.templateOptions.transform = CfnSimpleTable.requiredTransform; + this.simpleTableName = this.ref.toString(); + const tags = props === undefined ? undefined : props.tags; + this.tags = new cdk.TagManager(cdk.TagType.Map, "AWS::Serverless::SimpleTable", tags); + } + + public get propertyOverrides(): CfnSimpleTableProps { + return this.untypedPropertyOverrides; + } + protected renderProperties(properties: any): { [key: string]: any } { + return cfnSimpleTablePropsToCloudFormation(this.node.resolve(properties)); + } +} + +export namespace CfnSimpleTable { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#primary-key-object + */ + export interface PrimaryKeyProperty { + /** + * `CfnSimpleTable.PrimaryKeyProperty.Name` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#primary-key-object + */ + name?: string; + /** + * `CfnSimpleTable.PrimaryKeyProperty.Type` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#primary-key-object + */ + type: string; + } +} + +/** + * Determine whether the given properties match those of a `PrimaryKeyProperty` + * + * @param properties - the TypeScript properties of a `PrimaryKeyProperty` + * + * @returns the result of the validation. + */ +function CfnSimpleTable_PrimaryKeyPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('name', cdk.validateString)(properties.name)); + errors.collect(cdk.propertyValidator('type', cdk.requiredValidator)(properties.type)); + errors.collect(cdk.propertyValidator('type', cdk.validateString)(properties.type)); + return errors.wrap('supplied properties not correct for "PrimaryKeyProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable.PrimaryKey` resource + * + * @param properties - the TypeScript properties of a `PrimaryKeyProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable.PrimaryKey` resource. + */ +// @ts-ignore TS6133 +function cfnSimpleTablePrimaryKeyPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnSimpleTable_PrimaryKeyPropertyValidator(properties).assertSuccess(); + return { + Name: cdk.stringToCloudFormation(properties.name), + Type: cdk.stringToCloudFormation(properties.type), + }; +} + +export namespace CfnSimpleTable { + /** + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-provisionedthroughput.html + */ + export interface ProvisionedThroughputProperty { + /** + * `CfnSimpleTable.ProvisionedThroughputProperty.ReadCapacityUnits` + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-provisionedthroughput.html + */ + readCapacityUnits?: number | cdk.Token; + /** + * `CfnSimpleTable.ProvisionedThroughputProperty.WriteCapacityUnits` + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-provisionedthroughput.html + */ + writeCapacityUnits: number | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `ProvisionedThroughputProperty` + * + * @param properties - the TypeScript properties of a `ProvisionedThroughputProperty` + * + * @returns the result of the validation. + */ +function CfnSimpleTable_ProvisionedThroughputPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('readCapacityUnits', cdk.validateNumber)(properties.readCapacityUnits)); + errors.collect(cdk.propertyValidator('writeCapacityUnits', cdk.requiredValidator)(properties.writeCapacityUnits)); + errors.collect(cdk.propertyValidator('writeCapacityUnits', cdk.validateNumber)(properties.writeCapacityUnits)); + return errors.wrap('supplied properties not correct for "ProvisionedThroughputProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable.ProvisionedThroughput` resource + * + * @param properties - the TypeScript properties of a `ProvisionedThroughputProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable.ProvisionedThroughput` resource. + */ +// @ts-ignore TS6133 +function cfnSimpleTableProvisionedThroughputPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnSimpleTable_ProvisionedThroughputPropertyValidator(properties).assertSuccess(); + return { + ReadCapacityUnits: cdk.numberToCloudFormation(properties.readCapacityUnits), + WriteCapacityUnits: cdk.numberToCloudFormation(properties.writeCapacityUnits), + }; +} + +export namespace CfnSimpleTable { + /** + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-table-ssespecification.html + */ + export interface SSESpecificationProperty { + /** + * `CfnSimpleTable.SSESpecificationProperty.SSEEnabled` + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-table-ssespecification.html + */ + sseEnabled?: boolean | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `SSESpecificationProperty` + * + * @param properties - the TypeScript properties of a `SSESpecificationProperty` + * + * @returns the result of the validation. + */ +function CfnSimpleTable_SSESpecificationPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('sseEnabled', cdk.validateBoolean)(properties.sseEnabled)); + return errors.wrap('supplied properties not correct for "SSESpecificationProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable.SSESpecification` resource + * + * @param properties - the TypeScript properties of a `SSESpecificationProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable.SSESpecification` resource. + */ +// @ts-ignore TS6133 +function cfnSimpleTableSSESpecificationPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnSimpleTable_SSESpecificationPropertyValidator(properties).assertSuccess(); + return { + SSEEnabled: cdk.booleanToCloudFormation(properties.sseEnabled), + }; +} diff --git a/packages/@aws-cdk/aws-sns/lib/topic-base.ts b/packages/@aws-cdk/aws-sns/lib/topic-base.ts index 2a68246e6e841..f455cb8648dcb 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic-base.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic-base.ts @@ -83,7 +83,7 @@ export interface ITopic extends /** * Grant topic publishing permissions to the given identity */ - grantPublish(identity?: iam.IPrincipal): void; + grantPublish(identity: iam.IGrantable): iam.Grant; } /** @@ -270,14 +270,13 @@ export abstract class TopicBase extends cdk.Construct implements ITopic { /** * Grant topic publishing permissions to the given identity */ - public grantPublish(identity?: iam.IPrincipal) { - if (!identity) { - return; - } - - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.topicArn) - .addActions('sns:Publish')); + public grantPublish(grantee: iam.IGrantable) { + return iam.Grant.addToPrincipalOrResource({ + grantee, + actions: ['sns:Publish'], + resourceArns: [this.topicArn], + resource: this, + }); } /** diff --git a/packages/@aws-cdk/aws-sqs/lib/queue-base.ts b/packages/@aws-cdk/aws-sqs/lib/queue-base.ts index 1c207b622884b..1a281b5691fdd 100644 --- a/packages/@aws-cdk/aws-sqs/lib/queue-base.ts +++ b/packages/@aws-cdk/aws-sqs/lib/queue-base.ts @@ -53,9 +53,9 @@ export interface IQueue extends cdk.IConstruct, s3n.IBucketNotificationDestinati * - sqs:GetQueueAttributes * - sqs:GetQueueUrl * - * @param identity Principal to grant consume rights to + * @param grantee Principal to grant consume rights to */ - grantConsumeMessages(identity?: iam.IPrincipal): void; + grantConsumeMessages(grantee: iam.IGrantable): iam.Grant; /** * Grant access to send messages to a queue to the given identity. @@ -67,9 +67,9 @@ export interface IQueue extends cdk.IConstruct, s3n.IBucketNotificationDestinati * - sqs:GetQueueAttributes * - sqs:GetQueueUrl * - * @param identity Principal to grant send rights to + * @param grantee Principal to grant send rights to */ - grantSendMessages(identity?: iam.IPrincipal): void; + grantSendMessages(grantee: iam.IGrantable): iam.Grant; /** * Grant an IAM principal permissions to purge all messages from the queue. @@ -80,19 +80,19 @@ export interface IQueue extends cdk.IConstruct, s3n.IBucketNotificationDestinati * - sqs:GetQueueAttributes * - sqs:GetQueueUrl * - * @param identity Principal to grant send rights to + * @param grantee Principal to grant send rights to * @param queueActions additional queue actions to allow */ - grantPurge(identity?: iam.IPrincipal): void; + grantPurge(grantee: iam.IGrantable): iam.Grant; /** * Grant the actions defined in queueActions to the identity Principal given * on this SQS queue resource. * - * @param identity Principal to grant right to + * @param grantee Principal to grant right to * @param queueActions The actions to grant */ - grant(identity?: iam.IPrincipal, ...queueActions: string[]): void; + grant(grantee: iam.IGrantable, ...queueActions: string[]): iam.Grant; } /** @@ -214,10 +214,10 @@ export abstract class QueueBase extends cdk.Construct implements IQueue { * - sqs:GetQueueAttributes * - sqs:GetQueueUrl * - * @param identity Principal to grant consume rights to + * @param grantee Principal to grant consume rights to */ - public grantConsumeMessages(identity?: iam.IPrincipal) { - this.grant(identity, + public grantConsumeMessages(grantee: iam.IGrantable) { + return this.grant(grantee, 'sqs:ReceiveMessage', 'sqs:ChangeMessageVisibility', 'sqs:ChangeMessageVisibilityBatch', @@ -237,10 +237,10 @@ export abstract class QueueBase extends cdk.Construct implements IQueue { * - sqs:GetQueueAttributes * - sqs:GetQueueUrl * - * @param identity Principal to grant send rights to + * @param grantee Principal to grant send rights to */ - public grantSendMessages(identity?: iam.IPrincipal) { - this.grant(identity, + public grantSendMessages(grantee: iam.IGrantable) { + return this.grant(grantee, 'sqs:SendMessage', 'sqs:SendMessageBatch', 'sqs:GetQueueAttributes', @@ -256,11 +256,11 @@ export abstract class QueueBase extends cdk.Construct implements IQueue { * - sqs:GetQueueAttributes * - sqs:GetQueueUrl * - * @param identity Principal to grant send rights to + * @param grantee Principal to grant send rights to * @param queueActions additional queue actions to allow */ - public grantPurge(identity?: iam.IPrincipal) { - this.grant(identity, + public grantPurge(grantee: iam.IGrantable) { + return this.grant(grantee, 'sqs:PurgeQueue', 'sqs:GetQueueAttributes', 'sqs:GetQueueUrl'); @@ -270,17 +270,16 @@ export abstract class QueueBase extends cdk.Construct implements IQueue { * Grant the actions defined in queueActions to the identity Principal given * on this SQS queue resource. * - * @param identity Principal to grant right to - * @param queueActions The actions to grant + * @param grantee Principal to grant right to + * @param actions The actions to grant */ - public grant(identity?: iam.IPrincipal, ...queueActions: string[]) { - if (!identity) { - return; - } - - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.queueArn) - .addActions(...queueActions)); + public grant(grantee: iam.IGrantable, ...actions: string[]) { + return iam.Grant.addToPrincipalOrResource({ + grantee, + actions, + resourceArns: [this.queueArn], + resource: this, + }); } } diff --git a/packages/@aws-cdk/aws-ssm/lib/parameter.ts b/packages/@aws-cdk/aws-ssm/lib/parameter.ts index de096ba82fb40..47de0226b1fbf 100644 --- a/packages/@aws-cdk/aws-ssm/lib/parameter.ts +++ b/packages/@aws-cdk/aws-ssm/lib/parameter.ts @@ -26,14 +26,14 @@ export interface IParameter extends cdk.IConstruct { * * @param grantee the role to be granted read-only access to the parameter. */ - grantRead(grantee: iam.IPrincipal): void; + grantRead(grantee: iam.IGrantable): iam.Grant; /** * Grants write (PutParameter) permissions on the SSM Parameter. * * @param grantee the role to be granted write access to the parameter. */ - grantWrite(grantee: iam.IPrincipal): void; + grantWrite(grantee: iam.IGrantable): iam.Grant; } /** @@ -124,18 +124,20 @@ export abstract class ParameterBase extends cdk.Construct implements IParameter }); } - public grantRead(grantee: iam.IPrincipal): void { - grantee.addToPolicy(new iam.PolicyStatement() - .allow() - .addActions('ssm:DescribeParameters', 'ssm:GetParameter', 'ssm:GetParameterHistory') - .addResource(this.parameterArn)); + public grantRead(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions: ['ssm:DescribeParameters', 'ssm:GetParameter', 'ssm:GetParameterHistory'], + resourceArns: [this.parameterArn], + }); } - public grantWrite(grantee: iam.IPrincipal): void { - grantee.addToPolicy(new iam.PolicyStatement() - .allow() - .addAction('ssm:PutParameter') - .addResource(this.parameterArn)); + public grantWrite(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions: ['ssm:PutParameter'], + resourceArns: [this.parameterArn], + }); } } diff --git a/packages/@aws-cdk/runtime-values/lib/rtv.ts b/packages/@aws-cdk/runtime-values/lib/rtv.ts index 12811be13a3f3..ac60a15dc3a81 100644 --- a/packages/@aws-cdk/runtime-values/lib/rtv.ts +++ b/packages/@aws-cdk/runtime-values/lib/rtv.ts @@ -74,15 +74,12 @@ export class RuntimeValue extends cdk.Construct { * Grants a principal read permissions on this runtime value. * @param principal The principal (e.g. Role, User, Group) */ - public grantRead(principal?: iam.IPrincipal) { + public grantRead(grantee: iam.IGrantable) { + return iam.Grant.addToPrincipal({ + grantee, + resourceArns: [this.parameterArn], + actions: RuntimeValue.SSM_READ_ACTIONS - // sometimes "role" is optional, so we want `rtv.grantRead(role)` to be a no-op - if (!principal) { - return; - } - - principal.addToPolicy(new iam.PolicyStatement() - .addResource(this.parameterArn) - .addActions(...RuntimeValue.SSM_READ_ACTIONS)); + }); } } diff --git a/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts b/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts index 81e8b7d4d48f6..0c45cf9b4eb33 100644 --- a/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts +++ b/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts @@ -27,7 +27,7 @@ class TestStack extends cdk.Stack { // this line adds read permissions for this SSM parameter to the policies associated with // the IAM roles of the Lambda function and the EC2 fleet - queueUrlRtv.grantRead(fn.role); + queueUrlRtv.grantRead(fn); // adds the `RTV_STACK_NAME` to the environment of the lambda function // and the fleet (via user-data) diff --git a/packages/aws-cdk/lib/init-templates/app/java/src/main/java/com/myorg/HelloConstruct.java b/packages/aws-cdk/lib/init-templates/app/java/src/main/java/com/myorg/HelloConstruct.java index 7a2b629a6135b..3b57fa5725f4a 100644 --- a/packages/aws-cdk/lib/init-templates/app/java/src/main/java/com/myorg/HelloConstruct.java +++ b/packages/aws-cdk/lib/init-templates/app/java/src/main/java/com/myorg/HelloConstruct.java @@ -1,7 +1,7 @@ package com.myorg; import software.amazon.awscdk.Construct; -import software.amazon.awscdk.services.iam.IPrincipal; +import software.amazon.awscdk.services.iam.IGrantable; import software.amazon.awscdk.services.s3.Bucket; import software.amazon.awscdk.services.s3.BucketProps; @@ -24,9 +24,9 @@ public HelloConstruct(final Construct parent, final String name, final HelloCons /** * Given an principal, grants it READ access on all buckets. - * @param principal The principal (User, Group, Role) + * @param grantee The principal (User, Group, Role) */ - public void grantRead(final IPrincipal principal) { - buckets.forEach(b -> b.grantRead(principal, "*")); + public void grantRead(final IGrantable grantee) { + buckets.forEach(b -> b.grantRead(grantee, "*")); } } diff --git a/packages/cdk/package.json b/packages/cdk/package.json index cf2567a8498e8..9877c45e71427 100644 --- a/packages/cdk/package.json +++ b/packages/cdk/package.json @@ -29,6 +29,7 @@ }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { + "build": "echo Nothing to build", "package": "mkdir -p dist/js && cd dist/js && npm pack ../../" }, "engines": { diff --git a/scripts/foreach.sh b/scripts/foreach.sh index 514064f4c633f..47072ad0b3edf 100755 --- a/scripts/foreach.sh +++ b/scripts/foreach.sh @@ -37,11 +37,18 @@ function success { printf "\e[32;5;81m$@\e[0m\n" } +if [[ "${1:-}" == "--reset" ]]; then + rm -f ~/.foreach.* + success "state cleared. you are free to start a new command." + exit 0 +fi + + if [ -f "${statefile}" ] && [ -f "${commandfile}" ]; then command="$(cat ${commandfile})" if [ ! -z "${command_arg}" ] && [ "${command}" != "${command_arg}" ]; then error "error: there is still an active session for: \"${command}\". to reset:" - error " rm -f ~/.foreach.*" + error " $0 --reset" exit 1 fi fi @@ -61,7 +68,7 @@ fi next="$(head -n1 ${statefile})" if [ -z "${next}" ]; then success "done (queue is empty). to reset:" - success " rm -f ~/.foreach.*" + success " $0 --reset" exit 0 fi diff --git a/tools/awslint/lib/linter.ts b/tools/awslint/lib/linter.ts index 7c5628c0bbed8..0e80795e3e1d2 100644 --- a/tools/awslint/lib/linter.ts +++ b/tools/awslint/lib/linter.ts @@ -1,4 +1,5 @@ import reflect = require('jsii-reflect'); +import { PrimitiveType } from 'jsii-spec'; export interface LinterOptions { /** @@ -115,10 +116,16 @@ export class Evaluation { return this.assert(actual === expected, scope, ` (expected="${expected}",actual="${actual}")`); } + public assertTypesEqual(ts: reflect.TypeSystem, actual: TypeSpecifier, expected: TypeSpecifier, scope: string) { + const a = typeReferenceFrom(ts, actual); + const e = typeReferenceFrom(ts, expected); + return this.assert(a.toString() === e.toString(), scope, ` (expected="${e}",actual="${a}")`); + } + public assertSignature(method: reflect.Method, expectations: MethodSignatureExpectations) { const scope = method.parentType.fqn + '.' + method.name; if (expectations.returns) { - this.assertEquals(expectations.returns.toString(), expectations.returns, scope); + this.assertTypesEqual(method.system, method.returns, expectations.returns, scope); } if (expectations.parameters) { @@ -135,18 +142,7 @@ export class Evaluation { this.assertEquals(actualName, expectedName, pscope); } if (expect.type) { - const expectedType = expect.type; - const actualType = (() => { - if (actual.type.fqn) { - return actual.type.fqn.fqn; - } - if (actual.type.primitive) { - return actual.type.primitive; - } - return actual.type.toString(); - })(); - - this.assertEquals(actualType, expectedType, pscope); + this.assertTypesEqual(method.system, actual.type, expect.type, pscope); } } } @@ -205,9 +201,19 @@ export interface Rule { eval(linter: Evaluation): void; } +/** + * A type constraint + * + * Be super flexible about how types can be represented. Ultimately, we will + * compare what you give to a TypeReference, because that's what's in the JSII + * Reflect model. However, if you already have a real Type, or just a string to + * a user-defined type, that's fine too. We'll Do The Right Thing. + */ +export type TypeSpecifier = reflect.TypeReference | reflect.Type | string; + export interface MethodSignatureParameterExpectation { name?: string; - type?: string; + type?: TypeSpecifier; /** should this param be optional? */ optional?: boolean; @@ -215,7 +221,7 @@ export interface MethodSignatureParameterExpectation { export interface MethodSignatureExpectations { parameters?: MethodSignatureParameterExpectation[]; - returns?: string; + returns?: TypeSpecifier; } export enum DiagnosticLevel { @@ -232,3 +238,24 @@ export interface Diagnostic { scope: string; message: string; } + +/** + * Convert a type specifier to a TypeReference + */ +function typeReferenceFrom(ts: reflect.TypeSystem, x: TypeSpecifier): reflect.TypeReference { + if (isTypeReference(x)) { return x; } + + if (typeof x === 'string') { + if (x.indexOf('.') === -1) { + return new reflect.TypeReference(ts, { primitive: x as PrimitiveType }); + } else { + return new reflect.TypeReference(ts, { fqn: x }); + } + } + + return new reflect.TypeReference(ts, x); +} + +function isTypeReference(x: any): x is reflect.TypeReference { + return x instanceof reflect.TypeReference; +} \ No newline at end of file diff --git a/tools/awslint/lib/rules/resource.ts b/tools/awslint/lib/rules/resource.ts index 8c1f391fd2ffb..bc59f7efaad3c 100644 --- a/tools/awslint/lib/rules/resource.ts +++ b/tools/awslint/lib/rules/resource.ts @@ -11,6 +11,8 @@ export const resourceLinter = new Linter(assembly => { })); }); +const GRANT_RESULT_FQN = '@aws-cdk/aws-iam.Grant'; + interface ResourceLinterContext { readonly ts: reflect.TypeSystem; readonly resource: CfnResourceSpec; @@ -97,10 +99,10 @@ resourceLinter.add({ resourceLinter.add({ code: 'resource-interface-extends-construct', - message: 'resource interface must extends cdk.IConstruct', + message: 'resource interface must extend cdk.IConstruct', eval: e => { if (!e.ctx.resourceInterface) { return; } - e.assert(e.ctx.resourceInterface.interfaces.some(i => i.fqn === CONSTRUCT_INTERFACE_FQN), e.ctx.resourceInterface.fqn); + e.assert(e.ctx.resourceInterface.getInterfaces(true).some(i => i.fqn === CONSTRUCT_INTERFACE_FQN), e.ctx.resourceInterface.fqn); } }); @@ -149,7 +151,7 @@ resourceLinter.add({ } e.assertSignature(importMethod, { - returns: e.ctx.resourceInterface.fqn, + returns: e.ctx.resourceInterface, parameters: [ { name: 'scope', type: CONSTRUCT_FQN }, { name: 'id', type: 'string' }, @@ -173,8 +175,25 @@ resourceLinter.add({ if (!e.ctx.importPropsInterface) { return; } e.assertSignature(exportMethod, { - returns: e.ctx.importPropsInterface.fqn, + returns: e.ctx.importPropsInterface, parameters: [] }); } }); + +resourceLinter.add({ + code: 'grant-result', + message: `"grant" method must return ${GRANT_RESULT_FQN}`, + eval: e => { + if (!e.ctx.resourceClass) { return; } + + const grantResultType = e.ctx.ts.findFqn(GRANT_RESULT_FQN); + const grantMethods = e.ctx.resourceClass.getMethods(true).filter(m => m.name.startsWith('grant')); + + for (const grantMethod of grantMethods) { + e.assertSignature(grantMethod, { + returns: grantResultType + }); + } + } +}); \ No newline at end of file diff --git a/tools/awslint/lib/util.ts b/tools/awslint/lib/util.ts index 6302a19001770..01057d14e0644 100644 --- a/tools/awslint/lib/util.ts +++ b/tools/awslint/lib/util.ts @@ -12,4 +12,4 @@ export function isConstruct(c: reflect.ClassType) { const bases = c.getAncestors(); const root = bases[bases.length - 1]; return root === constructClass; -} +} \ No newline at end of file