AWS Cloud Development Kit (CDK)
- AWS Cloud Development Kit (CDK)
- cdk.json
- CDK commands
- AWS Construct Library
- Things to note
- How to set-up more than one handler using cdk?
- Runtimes to use
- Common errors
- Permissions
- Exploring the resources after the Stack is created
- Testing through API Gateway
- Local Development Testing
- Stack And Resources
- Troubleshooting
- API Gateway “Forbidden” after Policy changes
- API Gateway is responding with 403 “Missing Authentication Token”
- Deployed a stack to AWS - How do I delete it
- Deploy Takes a long time and there are delete failures
- Testing Locally with SAM - Gives error after fetching container
- Analyze the Lambda Stack Size
AWS Cloud Development Kit (CDK)
- https://docs.aws.amazon.com/cdk/v2/guide/home.html
- https://catalog.us-east-1.prod.workshops.aws/workshops/10141411-0192-4021-afa8-2436f3c66bd8/en-US
- cdk project structure: https://catalog.us-east-1.prod.workshops.aws/workshops/10141411-0192-4021-afa8-2436f3c66bd8/en-US/6000-go-workshop/200-create-project/220-structure
cdk.json
The cdk.json
file tells the CDK Toolkit how to execute your app.
CDK commands
Command | Desription | Notes |
---|---|---|
cdk synth |
emits the synthesized CloudFormation template | https://catalog.us-east-1.prod.workshops.aws/workshops/10141411-0192-4021-afa8-2436f3c66bd8/en-US/6000-go-workshop/200-create-project/230-synth |
cdk diff |
compare deployed stack with current state | |
cdk bootstrap |
Bootstrapping an environment: The first time you deploy an AWS CDK app into an environment (account/region), you can install a “bootstrap stack”. This stack includes resources that are used in the toolkit’s operation. For example, the stack includes an S3 bucket that is used to store templates and assets during the deployment process. | https://catalog.us-east-1.prod.workshops.aws/workshops/10141411-0192-4021-afa8-2436f3c66bd8/en-US/6000-go-workshop/200-create-project/240-deploy |
cdk deploy |
deploy this stack to your default AWS account/region | https://catalog.us-east-1.prod.workshops.aws/workshops/10141411-0192-4021-afa8-2436f3c66bd8/en-US/6000-go-workshop/200-create-project/240-deploy |
AWS Construct Library
The AWS CDK is shipped with an extensive library of constructs called the AWS Construct Library. The construct library is divided into modules, one for each AWS service. For example, if you want to define an AWS Lambda function, we will need to use the AWS Lambda construct library.
Things to note
- The code in the cdk folder can be written in any language I want.
- The code in the src folder can be written in any language I want.
- The only important thing is to refer to the handlers in src code from the cdk functions.
How to set-up more than one handler using cdk?
Use api gateway.
See https://github.com/explorer436/programming-playground/tree/main/aws
Runtimes to use
The go1.x runtime is deprecated, but it doesn’t mean that golang executing in a lambda is deprecated. You just need to
- specify that you’re going to use a custom runtime like provided.al2 (See https://docs.aws.amazon.com/lambda/latest/dg/lambda-golang.html#golang-al1) and
- name your compiled binary “bootstrap” in the root of the zip that gets deployed
- or you can deploy using containers stored in ECR and specify any given entrypoint in the Dockerfile if you need more flexibility.
Common errors
[explorer436@explorer436-lenovo-legion-82b1 cdk-workshop-golang]$ cdk bootstrap
⏳ Bootstrapping environment aws://920372989293/us-east-1...
Trusted accounts for deployment: (none)
Trusted accounts for lookup: (none)
Using default execution policy of 'arn:aws:iam::aws:policy/AdministratorAccess'. Pass '--cloudformation-execution-policies' to customize.
❌ Environment aws://920372989293/us-east-1 failed bootstrapping: _ToolkitError: The stack named CDKToolkit is in a failed state. You may need to delete it from the AWS console : DELETE_FAILED (The following resource(s) failed to delete: [ContainerAssetsRepository]. )
Resolving the errors
- Manually delete the stack from the aws console.
- Update the permissions for the IAM user.
- Run the bootstrap command again.
Permissions
The IAM user needed these policies to be able to bootstrap the stack successfully.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:GenerateCredentialReport",
"iam:GenerateServiceLastAccessedDetails",
"iam:Get*",
"iam:List*",
"iam:SimulateCustomPolicy",
"iam:SimulatePrincipalPolicy",
"iam:CreateRole",
"iam:TagRole",
"iam:DeleteRole",
"iam:AttachRolePolicy",
"iam:PutRolePolicy",
"iam:DeleteRolePolicy",
"iam:DetachRolePolicy",
"iam:PassRole"
],
"Resource": "*"
},
{
"Sid": "ECR",
"Effect": "Allow",
"Action": [
"ecr:PutLifecyclePolicy",
"ecr:DeleteRepository",
"ecr:SetRepositoryPolicy",
"ecr:CreateRepository",
"ecr:DescribeRepositories"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"cloudformation:DescribeStacks",
"cloudformation:DescribeStackEvents",
"cloudformation:DescribeStackResource",
"cloudformation:DescribeStackResources",
"cloudformation:DeleteStack",
"cloudformation:CreateChangeSet",
"cloudformation:DescribeChangeSet",
"cloudformation:GetTemplate",
"cloudformation:DeleteChangeSet",
"cloudformation:ExecuteChangeSet"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ssm:Describe*",
"ssm:Get*",
"ssm:List*",
"ssm:PutParameter"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:CreateBucket",
"s3:PutEncryptionConfiguration",
"s3:PutLifecycleConfiguration",
"s3:PutBucketVersioning",
"s3:PutBucketPublicAccessBlock",
"s3:PutBucketPolicy",
"s3:DeleteBucketPolicy",
"s3:GetBucketLocation",
"s3:ListBucket",
"s3:PutObject",
"s3:GetObject"
],
"Resource": "*"
}
]
}
Exploring the resources after the Stack is created
- In the aws console, Navigate to CloudFormation > Stacks > CDKToolkit
- Look at the
Resources
,Outputs
andTemplate
tab.
Sample template that is generated
Description: This stack includes resources needed to deploy AWS CDK apps into this environment
Parameters:
TrustedAccounts:
Description: List of AWS accounts that are trusted to publish assets and deploy stacks to this environment
Default: ""
Type: CommaDelimitedList
TrustedAccountsForLookup:
Description: List of AWS accounts that are trusted to look up values in this environment
Default: ""
Type: CommaDelimitedList
CloudFormationExecutionPolicies:
Description: List of the ManagedPolicy ARN(s) to attach to the CloudFormation deployment role
Default: ""
Type: CommaDelimitedList
FileAssetsBucketName:
Description: The name of the S3 bucket used for file assets
Default: ""
Type: String
FileAssetsBucketKmsKeyId:
Description: Empty to create a new key (default), 'AWS_MANAGED_KEY' to use a managed S3 key, or the ID/ARN of an existing key.
Default: ""
Type: String
ContainerAssetsRepositoryName:
Description: A user-provided custom name to use for the container assets ECR repository
Default: ""
Type: String
Qualifier:
Description: An identifier to distinguish multiple bootstrap stacks in the same environment
Default: hnb659fds
Type: String
AllowedPattern: "[A-Za-z0-9_-]{1,10}"
ConstraintDescription: Qualifier must be an alphanumeric identifier of at most 10 characters
PublicAccessBlockConfiguration:
Description: Whether or not to enable S3 Staging Bucket Public Access Block Configuration
Default: "true"
Type: String
AllowedValues:
- "true"
- "false"
InputPermissionsBoundary:
Description: Whether or not to use either the CDK supplied or custom permissions boundary
Default: ""
Type: String
UseExamplePermissionsBoundary:
Default: "false"
AllowedValues:
- "true"
- "false"
Type: String
BootstrapVariant:
Type: String
Default: "AWS CDK: Default Resources"
Description: Describe the provenance of the resources in this bootstrap stack. Change this when you customize the template. To prevent accidents, the CDK CLI will not overwrite bootstrap stacks with a different variant.
Conditions:
HasTrustedAccounts:
Fn::Not:
- Fn::Equals:
- ""
- Fn::Join:
- ""
- Ref: TrustedAccounts
HasTrustedAccountsForLookup:
Fn::Not:
- Fn::Equals:
- ""
- Fn::Join:
- ""
- Ref: TrustedAccountsForLookup
HasCloudFormationExecutionPolicies:
Fn::Not:
- Fn::Equals:
- ""
- Fn::Join:
- ""
- Ref: CloudFormationExecutionPolicies
HasCustomFileAssetsBucketName:
Fn::Not:
- Fn::Equals:
- ""
- Ref: FileAssetsBucketName
CreateNewKey:
Fn::Equals:
- ""
- Ref: FileAssetsBucketKmsKeyId
UseAwsManagedKey:
Fn::Equals:
- AWS_MANAGED_KEY
- Ref: FileAssetsBucketKmsKeyId
ShouldCreatePermissionsBoundary:
Fn::Equals:
- "true"
- Ref: UseExamplePermissionsBoundary
PermissionsBoundarySet:
Fn::Not:
- Fn::Equals:
- ""
- Ref: InputPermissionsBoundary
HasCustomContainerAssetsRepositoryName:
Fn::Not:
- Fn::Equals:
- ""
- Ref: ContainerAssetsRepositoryName
UsePublicAccessBlockConfiguration:
Fn::Equals:
- "true"
- Ref: PublicAccessBlockConfiguration
Resources:
FileAssetsBucketEncryptionKey:
Type: AWS::KMS::Key
Properties:
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
- kms:GenerateDataKey
- kms:TagResource
- kms:UntagResource
Effect: Allow
Principal:
AWS:
Ref: AWS::AccountId
Resource: "*"
- Action:
- kms:Decrypt
- kms:DescribeKey
- kms:Encrypt
- kms:ReEncrypt*
- kms:GenerateDataKey*
Effect: Allow
Principal:
AWS: "*"
Resource: "*"
Condition:
StringEquals:
kms:CallerAccount:
Ref: AWS::AccountId
kms:ViaService:
- Fn::Sub: s3.${AWS::Region}.amazonaws.com
- Action:
- kms:Decrypt
- kms:DescribeKey
- kms:Encrypt
- kms:ReEncrypt*
- kms:GenerateDataKey*
Effect: Allow
Principal:
AWS:
Fn::Sub: ${FilePublishingRole.Arn}
Resource: "*"
Condition: CreateNewKey
UpdateReplacePolicy: Delete
DeletionPolicy: Delete
FileAssetsBucketEncryptionKeyAlias:
Condition: CreateNewKey
Type: AWS::KMS::Alias
Properties:
AliasName:
Fn::Sub: alias/cdk-${Qualifier}-assets-key
TargetKeyId:
Ref: FileAssetsBucketEncryptionKey
StagingBucket:
Type: AWS::S3::Bucket
Properties:
BucketName:
Fn::If:
- HasCustomFileAssetsBucketName
- Fn::Sub: ${FileAssetsBucketName}
- Fn::Sub: cdk-${Qualifier}-assets-${AWS::AccountId}-${AWS::Region}
AccessControl: Private
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
KMSMasterKeyID:
Fn::If:
- CreateNewKey
- Fn::Sub: ${FileAssetsBucketEncryptionKey.Arn}
- Fn::If:
- UseAwsManagedKey
- Ref: AWS::NoValue
- Fn::Sub: ${FileAssetsBucketKmsKeyId}
PublicAccessBlockConfiguration:
Fn::If:
- UsePublicAccessBlockConfiguration
- BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
- Ref: AWS::NoValue
VersioningConfiguration:
Status: Enabled
LifecycleConfiguration:
Rules:
- Id: CleanupOldVersions
Status: Enabled
NoncurrentVersionExpiration:
NoncurrentDays: 30
- Id: AbortIncompleteMultipartUploads
Status: Enabled
AbortIncompleteMultipartUpload:
DaysAfterInitiation: 1
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
StagingBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket:
Ref: StagingBucket
PolicyDocument:
Id: AccessControl
Version: "2012-10-17"
Statement:
- Sid: AllowSSLRequestsOnly
Action: s3:*
Effect: Deny
Resource:
- Fn::Sub: ${StagingBucket.Arn}
- Fn::Sub: ${StagingBucket.Arn}/*
Condition:
Bool:
aws:SecureTransport: "false"
Principal: "*"
ContainerAssetsRepository:
Type: AWS::ECR::Repository
Properties:
ImageTagMutability: IMMUTABLE
LifecyclePolicy:
LifecyclePolicyText: |
{
"rules": [
{
"rulePriority": 1,
"description": "Untagged images should not exist, but expire any older than one year",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 365
},
"action": { "type": "expire" }
}
]
}
RepositoryName:
Fn::If:
- HasCustomContainerAssetsRepositoryName
- Fn::Sub: ${ContainerAssetsRepositoryName}
- Fn::Sub: cdk-${Qualifier}-container-assets-${AWS::AccountId}-${AWS::Region}
RepositoryPolicyText:
Version: "2012-10-17"
Statement:
- Sid: LambdaECRImageRetrievalPolicy
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action:
- ecr:BatchGetImage
- ecr:GetDownloadUrlForLayer
Condition:
StringLike:
aws:sourceArn:
Fn::Sub: arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:*
- Sid: EmrServerlessImageRetrievalPolicy
Effect: Allow
Principal:
Service: emr-serverless.amazonaws.com
Action:
- ecr:BatchGetImage
- ecr:GetDownloadUrlForLayer
- ecr:DescribeImages
Condition:
StringLike:
aws:sourceArn:
Fn::Sub: arn:${AWS::Partition}:emr-serverless:${AWS::Region}:${AWS::AccountId}:/applications/*
FilePublishingRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:TagSession
Effect: Allow
Principal:
AWS:
Ref: AWS::AccountId
- Action: sts:AssumeRole
Effect: Allow
Principal:
AWS:
Ref: AWS::AccountId
- Fn::If:
- HasTrustedAccounts
- Action: sts:AssumeRole
Effect: Allow
Principal:
AWS:
Ref: TrustedAccounts
- Ref: AWS::NoValue
RoleName:
Fn::Sub: cdk-${Qualifier}-file-publishing-role-${AWS::AccountId}-${AWS::Region}
Tags:
- Key: aws-cdk:bootstrap-role
Value: file-publishing
ImagePublishingRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:TagSession
Effect: Allow
Principal:
AWS:
Ref: AWS::AccountId
- Action: sts:AssumeRole
Effect: Allow
Principal:
AWS:
Ref: AWS::AccountId
- Fn::If:
- HasTrustedAccounts
- Action: sts:AssumeRole
Effect: Allow
Principal:
AWS:
Ref: TrustedAccounts
- Ref: AWS::NoValue
RoleName:
Fn::Sub: cdk-${Qualifier}-image-publishing-role-${AWS::AccountId}-${AWS::Region}
Tags:
- Key: aws-cdk:bootstrap-role
Value: image-publishing
LookupRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:TagSession
Effect: Allow
Principal:
AWS:
Ref: AWS::AccountId
- Action: sts:AssumeRole
Effect: Allow
Principal:
AWS:
Ref: AWS::AccountId
- Fn::If:
- HasTrustedAccountsForLookup
- Action: sts:AssumeRole
Effect: Allow
Principal:
AWS:
Ref: TrustedAccountsForLookup
- Ref: AWS::NoValue
- Fn::If:
- HasTrustedAccounts
- Action: sts:AssumeRole
Effect: Allow
Principal:
AWS:
Ref: TrustedAccounts
- Ref: AWS::NoValue
RoleName:
Fn::Sub: cdk-${Qualifier}-lookup-role-${AWS::AccountId}-${AWS::Region}
ManagedPolicyArns:
- Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/ReadOnlyAccess
Policies:
- PolicyDocument:
Statement:
- Sid: DontReadSecrets
Effect: Deny
Action:
- kms:Decrypt
Resource: "*"
Version: "2012-10-17"
PolicyName: LookupRolePolicy
Tags:
- Key: aws-cdk:bootstrap-role
Value: lookup
FilePublishingRoleDefaultPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyDocument:
Statement:
- Action:
- s3:GetObject*
- s3:GetBucket*
- s3:GetEncryptionConfiguration
- s3:List*
- s3:DeleteObject*
- s3:PutObject*
- s3:Abort*
Resource:
- Fn::Sub: ${StagingBucket.Arn}
- Fn::Sub: ${StagingBucket.Arn}/*
Condition:
StringEquals:
aws:ResourceAccount:
- Fn::Sub: ${AWS::AccountId}
Effect: Allow
- Action:
- kms:Decrypt
- kms:DescribeKey
- kms:Encrypt
- kms:ReEncrypt*
- kms:GenerateDataKey*
Effect: Allow
Resource:
Fn::If:
- CreateNewKey
- Fn::Sub: ${FileAssetsBucketEncryptionKey.Arn}
- Fn::Sub: arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${FileAssetsBucketKmsKeyId}
Version: "2012-10-17"
Roles:
- Ref: FilePublishingRole
PolicyName:
Fn::Sub: cdk-${Qualifier}-file-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region}
ImagePublishingRoleDefaultPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyDocument:
Statement:
- Action:
- ecr:PutImage
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
- ecr:BatchCheckLayerAvailability
- ecr:DescribeRepositories
- ecr:DescribeImages
- ecr:BatchGetImage
- ecr:GetDownloadUrlForLayer
Resource:
Fn::Sub: ${ContainerAssetsRepository.Arn}
Effect: Allow
- Action:
- ecr:GetAuthorizationToken
Resource: "*"
Effect: Allow
Version: "2012-10-17"
Roles:
- Ref: ImagePublishingRole
PolicyName:
Fn::Sub: cdk-${Qualifier}-image-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region}
DeploymentActionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:TagSession
Effect: Allow
Principal:
AWS:
Ref: AWS::AccountId
- Action: sts:AssumeRole
Effect: Allow
Principal:
AWS:
Ref: AWS::AccountId
- Fn::If:
- HasTrustedAccounts
- Action: sts:AssumeRole
Effect: Allow
Principal:
AWS:
Ref: TrustedAccounts
- Ref: AWS::NoValue
Policies:
- PolicyDocument:
Statement:
- Sid: CloudFormationPermissions
Effect: Allow
Action:
- cloudformation:CreateChangeSet
- cloudformation:DeleteChangeSet
- cloudformation:DescribeChangeSet
- cloudformation:DescribeStacks
- cloudformation:ExecuteChangeSet
- cloudformation:CreateStack
- cloudformation:UpdateStack
- cloudformation:RollbackStack
- cloudformation:ContinueUpdateRollback
Resource: "*"
- Sid: PipelineCrossAccountArtifactsBucket
Effect: Allow
Action:
- s3:GetObject*
- s3:GetBucket*
- s3:List*
- s3:Abort*
- s3:DeleteObject*
- s3:PutObject*
Resource: "*"
Condition:
StringNotEquals:
s3:ResourceAccount:
Ref: AWS::AccountId
- Sid: PipelineCrossAccountArtifactsKey
Effect: Allow
Action:
- kms:Decrypt
- kms:DescribeKey
- kms:Encrypt
- kms:ReEncrypt*
- kms:GenerateDataKey*
Resource: "*"
Condition:
StringEquals:
kms:ViaService:
Fn::Sub: s3.${AWS::Region}.amazonaws.com
- Action: iam:PassRole
Resource:
Fn::Sub: ${CloudFormationExecutionRole.Arn}
Effect: Allow
- Sid: CliPermissions
Action:
- cloudformation:DescribeStackEvents
- cloudformation:GetTemplate
- cloudformation:DeleteStack
- cloudformation:UpdateTerminationProtection
- sts:GetCallerIdentity
- cloudformation:GetTemplateSummary
Resource: "*"
Effect: Allow
- Sid: CliStagingBucket
Effect: Allow
Action:
- s3:GetObject*
- s3:GetBucket*
- s3:List*
Resource:
- Fn::Sub: ${StagingBucket.Arn}
- Fn::Sub: ${StagingBucket.Arn}/*
- Sid: ReadVersion
Effect: Allow
Action:
- ssm:GetParameter
- ssm:GetParameters
Resource:
- Fn::Sub: arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter${CdkBootstrapVersion}
- Sid: Refactor
Effect: Allow
Action:
- cloudformation:CreateStackRefactor
- cloudformation:DescribeStackRefactor
- cloudformation:ExecuteStackRefactor
- cloudformation:ListStackRefactorActions
- cloudformation:ListStackRefactors
- cloudformation:ListStacks
Resource: "*"
Version: "2012-10-17"
PolicyName: default
RoleName:
Fn::Sub: cdk-${Qualifier}-deploy-role-${AWS::AccountId}-${AWS::Region}
Tags:
- Key: aws-cdk:bootstrap-role
Value: deploy
CloudFormationExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: cloudformation.amazonaws.com
Version: "2012-10-17"
ManagedPolicyArns:
Fn::If:
- HasCloudFormationExecutionPolicies
- Ref: CloudFormationExecutionPolicies
- Fn::If:
- HasTrustedAccounts
- Ref: AWS::NoValue
- - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess
RoleName:
Fn::Sub: cdk-${Qualifier}-cfn-exec-role-${AWS::AccountId}-${AWS::Region}
PermissionsBoundary:
Fn::If:
- PermissionsBoundarySet
- Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/${InputPermissionsBoundary}
- Ref: AWS::NoValue
CdkBoostrapPermissionsBoundaryPolicy:
Condition: ShouldCreatePermissionsBoundary
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
Statement:
- Sid: ExplicitAllowAll
Action:
- "*"
Effect: Allow
Resource: "*"
- Sid: DenyAccessIfRequiredPermBoundaryIsNotBeingApplied
Action:
- iam:CreateUser
- iam:CreateRole
- iam:PutRolePermissionsBoundary
- iam:PutUserPermissionsBoundary
Condition:
StringNotEquals:
iam:PermissionsBoundary:
Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region}
Effect: Deny
Resource: "*"
- Sid: DenyPermBoundaryIAMPolicyAlteration
Action:
- iam:CreatePolicyVersion
- iam:DeletePolicy
- iam:DeletePolicyVersion
- iam:SetDefaultPolicyVersion
Effect: Deny
Resource:
Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region}
- Sid: DenyRemovalOfPermBoundaryFromAnyUserOrRole
Action:
- iam:DeleteUserPermissionsBoundary
- iam:DeleteRolePermissionsBoundary
Effect: Deny
Resource: "*"
Version: "2012-10-17"
Description: Bootstrap Permission Boundary
ManagedPolicyName:
Fn::Sub: cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region}
Path: /
CdkBootstrapVersion:
Type: AWS::SSM::Parameter
Properties:
Type: String
Name:
Fn::Sub: /cdk-bootstrap/${Qualifier}/version
Value: "28"
Outputs:
BucketName:
Description: The name of the S3 bucket owned by the CDK toolkit stack
Value:
Fn::Sub: ${StagingBucket}
BucketDomainName:
Description: The domain name of the S3 bucket owned by the CDK toolkit stack
Value:
Fn::Sub: ${StagingBucket.RegionalDomainName}
FileAssetKeyArn:
Description: The ARN of the KMS key used to encrypt the asset bucket (deprecated)
Value:
Fn::If:
- CreateNewKey
- Fn::Sub: ${FileAssetsBucketEncryptionKey.Arn}
- Fn::Sub: ${FileAssetsBucketKmsKeyId}
Export:
Name:
Fn::Sub: CdkBootstrap-${Qualifier}-FileAssetKeyArn
ImageRepositoryName:
Description: The name of the ECR repository which hosts docker image assets
Value:
Fn::Sub: ${ContainerAssetsRepository}
BootstrapVersion:
Description: The version of the bootstrap resources that are currently mastered in this stack
Value:
Fn::GetAtt:
- CdkBootstrapVersion
- Value
Testing through API Gateway
Use your favorite REST client like Postman, Insomnia or ReadyAPI. Setup a new test case and enter the following information.
Example VPC Endpoint - vpce-0a65e5d27ad21a9d0-kmk0ra23.execute-api.us-east-1.vpce.amazonaws.com <br /> Example Host Header - 0rdq6lojwl.execute-api.us-east-1.amazonaws.com <br /> Example Stage - prod <br /> Example Resource - briefs <br />
POST - https://<VPC Endpoint>/<stage>/<resource>
Headers
- Host
- Content-Type application/json
Body
{
"firstName": "bob",
"lastName": "alice"
}
Where to find the VPC Endpoint
- Go To the AWS Console, Search for the VPC Dashboard <br />
- On the left hand menu, choose Endpoints <br/>
- Filter the list by “API” <br/>
- Click the checkbox beside the VPC Endpoint used by the code <br />
- Look for the Section DNS Names. Pick the first one.
Where to Find the Host Header, Stage and Resource
- Go To the AWS Console, Search for the API Gateway <br />
- In the Search Box, type the name of your API Gateway service, and click the link when you find it <br/>
- IN the left hand menu, choose Stages <br/>
- Expand the stage to see all the endpoints (Note this is the stage, and resources) <br />
- There is an info bubble containing the URL that can be used as the host header
Local Development Testing
Prerequisites
- Docker is Setup and running
- SAM is setup
if these are not setup, see the Setup document for more details
Testing locally - via API Gateway
Open a command line to the application folder. Run yarn start
. This will build the application and deploy it to the running docker instance.
After the command starts, the output in the console will list all of the endpoints that are exposed. You can take these endpoints and set them up in a testing tool of your choice (curl/insomnia/postman).
hitting https://127.0.0.1/health
in your browser should give you a message that the service is up and running.
The start command will also watch for any changes to the code and auto deploy the changes to the local running instance. Changes to the CDK stack or configuration files will require a restart of the application.
Testing locally - Isolate a Lambda
Most functions are exposed via the previous command. If you need to test, for example Authorization Lambda, you can not do so via api gateway. That endpoint is not exposed via api gateway and the SAM tool does not support Authorization via API gateway at this time. In order to test a lambda directly you will need to utilizes two commands.
npm run prestart
and sam local invoke <lambda to execute> <event>
prestart will run the build and generate the necessary template files for you. Running the sam local command does take some extra effort to setup.
After prestart
finishes, open up the template.yml
file that is generated at the root of the application. Search for AWS::Lambda::Function
. You will see a few search results that looks like the following
hello26396490:
Type: AWS::Lambda::Function
The first line is the name of the Lambda. Copy that name for later
Run the following command to execute the lambda (replace the name with the name of your lambda)
sam local invoke hello26396490 --no-event
The above command works by not sending any event details to the lambda. In most cases you will want to send event details to the lambda. Below are a few examples of sending event details to the lambda.
sam local invoke nameOfTheLambdaE3E69214 -e test/sam/testMyLambdaAPIGatewayEvent.json
echo '{"body": {"firstname": "John"}}' | sam local invoke hello26396490
The commands above are equivalent. In the first case the event details would be provided in a file. This is good when working with bigger data sets. If you are working with smaller data sets, the second option may be better as you do not have the overhead of creating a file.
Tip - Utilize the templates in the AWS console to help generate the event data.
Note - The first run of this command will take a few minutes as the docker container image is downloaded.
Testing locally with Environment Variables
To test locally using environment variables, perform the following steps
- Create a file called env.json in the folder test/sam (may already exist)
- In the file place the following contents
{ myLambdaNameE3E69214 : { } }
- Follow the instructions to invoke a lambda (see Testing locally - Isolate a Lambda) adding –env-vars to the command
or if using the start commandsam local invoke --env-vars test/sam/env.json myLambdaNameE3E69214
npm start -- --env-vars test/sam/env.json
Stack And Resources
- SAML (Security Assertion Markup Language)
Dynamically Getting AWS Environment Info Using CustomResources
- CustomResources are used in the Stack to dynamically get the VPC for each environment.
How we deploy to Multiple Environments
By default, the accelerator project that this application was generated from creates an application in AWS with the same name as the name of the application. In AWS terms, all of the components that are created, Lambdas, API Gateway etc, are stored under the umbrella of the application. There can only be one uniquely named application per region per account. So by default if the team wanted to deploy to the Development region (Note our Bamboo setup only support a Sandbox, Development and/or Production region) we could only deploy one stack. What we found was if we tried to setup multiple environments in the same classification, we would simply update and not create a new environment. This limited us to the number of environments we could create.
As a team we decided we would want to have Development, Test and Performance deploys. In order to do this we had to make a change to the unique name that is auto generated for us by the Accelerator.
The stack constructor is what defines the application name.
In order to deploy the 3 environments into one region we had to change the name that was being generated.
In the code bin/proof-of-insurance-cdk-app.ts
the following line was modified from
new ProofOfInsuranceCDKStack(app, StackConfiguration.stackName);
to
new ProofOfInsuranceCDKStack(app, StackConfiguration.appName);
The stack configuration class was also changed to add a new application name field. This property consists of the application name plus the environment name given in Bamboo (not to be confused with the classification in bamboo, which is Sandbox, Development or Production).
"appName": process.env.stack_name || "employee-application" + "-" + (process.env.bamboo_forge_deployment_key || "sandbox"),
With this change, the application name in AWS will now be, for example: employee-application-performance
Now when a new environment is stood up in the Bamboo Pipeline, we get a unique application name for it.
Reference Documents on how the AWS Stack works
- https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Stack.html
- https://docs.aws.amazon.com/cdk/latest/guide/stacks.html
How local Development works
AWS released a tool called SAM (Serverless Application Model) which works very similar to the CDK. SAM also includes tooling to work locally and simulate an AWS environment.
For SAM to work, Docker is required. When you initialize a lambda for the first time, SAM pulls down a docker container that will simulate a deployed instance of the lambda. SAM utilizes a generated template.yml
file which contains the definition of the stack.
Breaking down the main testing commands, the team utilizes npm start
npm prestart
and npm watch
for local testing
npm start
- This starts a local SAM server. The SAM server exposes API Gateway definitions. In turn, exposing the lambdas associated to them. The start method utilizes the start-api
mechanics of SAM. This is defined as
> Sets up a local endpoint you can use to test your API. Supports hot-reloading so you don’t need to restart this service when you make changes to your function.
Which means that any changes to the build/output folder will automatically be mapped and deployed to the running service. You do not have to start/stop the server after each change. This is achieved by the SAM docker container exposing a volume mapping /var/task
(internal) to the applications output folder dist/functions
.
One thing missing from testing locally is that Lambda authorization is not supported at this time. This allows you to bypass authorization checks when testing locally. This also means that you can not test Authorization lambdas locally by this command. You will want to look at a combination of npm prestart
and sam local invoke
commands if you need to test Lambdas not exposed by API gateway.
npm run prestart
- This command is the workhorse of this set of commands. Prestart runs the webpack build and then runs a synthesis of the stack in order to generate the necessary cloud formation template that SAM needs to run.
The most important flag in the prestart function is --no-staging
. This allows CDK and SAM to talk the same language. Without this flag the root folder would be flooded with asset.1.1.
folders and SAM would not startup properly.
When start
is run, prestart
is automatically run before it. As npm wraps all of its main commands with pre
and post
commands.
Utilize prestart individually when you want to test a lambda without api gateway
npm run watch
- Pairs really nicely with start. After starting the SAM service, run watch. Watch will auto build your lambda functions whenever there are changes and since they are auto mapped into the docker container you do not need to rebuild or restart any services.
The SAM service and this watch will need to be shut down and restarted whenever there are changes made to the stack.
Troubleshooting
API Gateway “Forbidden” after Policy changes
The team ran into an issue where we had created a Lambda with API gateway fronting the function. The team made the API gateway private, but were tweaking the security policy for the VPC. What we found was that making changes to the policy statement attached to the API gateway did not make the changes we expected.
Running cdk synth
and cdk diff
we would see our changes. When deployed to AWS using cdk deploy
(or via Bamboo Pipeline) we would also see the changes listed in API Gateway under the Resource Policy. But the changes never seemed to take effect.
It was found in another document that any changes to a Policy Document/Statement do not initialize a new deployment. Even though the new configuration can be seen in the AWS console, they still have to be deployed manually.
As reference here is a link to the document and and exert > If you update the resource policy after the API is created, you’ll need to deploy the API to propagate the changes after you’ve attached the updated policy. Updating or saving the policy alone won’t change the runtime behavior of the API.
To do a manual deployment, go to the AWS console -> pick api gateway -> find your resource -> actions -> Deploy API
API Gateway is responding with 403 “Missing Authentication Token”
This error occurs when you are attempting to hit an endpoint that has not been exposed. To resolve this issue, perform the following checks
- Are you using the correct VPC endpoint?
- Is the Host value set properly
- Check if you are calling a GET/POST/PUT
- Check the URL you are using
- Ensure the proper endpoints are exposed in the Policy Document/Statement
Deployed a stack to AWS - How do I delete it
If it is just the AWS Stack that needs to be deleted it can be done via the cloud forge console.
1.Warning1. :: If other resources like repositories and Bamboo pipelines need to be deleted, this is not the right guide.
-
Open Public Cloud
-
From the top menu choose AWS > Accounts
-
Pick the account for your stack. This is the account used to deploy the stack and can be found in the bamboo deploy logs.
-
Click on the Resources tab
-
Under management & Governance: choose CloudFormation Stacks
-
Find your stack, use the filter in the top right to find your stack
-
Click the stack for the application you want to delete
-
On the main screen you will see a button to delete the stack.
-
Click the button and follow the confirmation prompts
As the stack deletes, you can follow the log to see the status of the cleanup. The log can be found on the same page as the delete button. Scroll down on the page and you should see a list of resources and their current state.
Deploy Takes a long time and there are delete failures
When a delete is in progress it may take 5-15 minutes before it errors out. You will see a DELETE_IN_PROGRESS
which seems to hang while the deployment is occurring. The message in the log may look similar to
DELETE_FAILED | AWS::IAM::Role | helloServiceRole353607D3 Cannot delete entity, must detach all policies first.
or
DELETE_FAILED | AWS::EC2::SecurityGroup | defaultRouteSecurityGroupC8F16279 resource sg-0e5f515e48512e240 has a dependent object
The reason these deletes fail is due to Lambdas using VPCs. When a lambda is removed from a VPC or the name of the Lambda changes, so does the roles and security groups that are generated.
When a lambda is put in a VPC it creates a network interface. When the Lambda is removed from the VPC, the network interface is not deleted right away. It takes up to 4 hours for AWS to remove the network interface. This is a known issue in AWS.
Because the network interface takes time to delete the security group, which it is dependent on, can not be deleted. This causes the build to hold up.
Though the timing is poor for these deployments, there should be no impacts to your application. In the background after your deployment is compete, AWS seems to quietly clean up these resources for you after the network-interface is cleaned up. After the cleanup is completed by AWS, future deploys should be much faster.
To avoid this issue, use caution when removing Lambdas from VPCs or when changing the ID of any Lambda
Testing Locally with SAM - Gives error after fetching container
If you are on windows, you may not have setup docker correctly. Make sure to read the setup guide and pay attention to the “use linux container” and “shared drive” options. Without these turned on you will continue to get mount issues with the container as the container won’t be able to map your applications output folder to the internal docker volume.
The error you will get will be something like “can not mount to /var/task”
Analyze the Lambda Stack Size
Size and memory have a major impact on the performance of a Lambda. To perform an analysis on the bundle sizes run the following commands
npm install --g webpack-bundle-analyzer
npx webpack --profile --json > stats.json
npx webpack-bundle-analyzer stats.json
Running the 3rd command will pop open a browser window showing a breakdown of all the node modules and package sizes
Details about the webpack analyzer can be found here https://github.com/webpack-contrib/webpack-bundle-analyzer