aws-cdk-lib#Duration TypeScript Examples

The following examples show how to use aws-cdk-lib#Duration. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.ts    From cloudstructs with Apache License 2.0 6 votes vote down vote up
constructor(scope: Construct, id: string, props: SlackTextractProps) {
    super(scope, id);

    const handler = new DetectFunction(this, 'handler', {
      timeout: Duration.seconds(30),
      logRetention: logs.RetentionDays.ONE_MONTH,
      environment: {
        SLACK_TOKEN: props.botToken.toString(),
      },
    });

    handler.addToRolePolicy(new iam.PolicyStatement({
      actions: ['textract:DetectDocumentText'],
      resources: ['*'],
    }));

    new SlackEvents(this, 'SlackEvents', {
      signingSecret: props.signingSecret,
    });

    const fileSharedRule = new events.Rule(this, 'SlackEventsRule', {
      eventPattern: {
        detail: {
          event: {
            type: ['file_shared'],
          },
        },
        resources: [props.appId],
        source: ['slack'],
      },
    });

    fileSharedRule.addTarget(new targets.LambdaFunction(handler, {
      event: events.RuleTargetInput.fromEventPath('$.detail.event'),
    }));
  }
Example #2
Source File: gitlab-runner-instance.ts    From cdk-gitlab-runner with Apache License 2.0 6 votes vote down vote up
/**
   * Add expire time function for spotfleet runner !!! .
   *
   * @param duration - Block duration.
   */
  public expireAfter(duration: Duration) {
    const date = new Date();
    date.setSeconds(date.getSeconds() + duration.toSeconds());
    this.validUntil = date.toISOString();
  }
Example #3
Source File: url-shortener.test.ts    From cloudstructs with Apache License 2.0 6 votes vote down vote up
test('UrlShortener', () => {
  const hostedZone = new route53.HostedZone(stack, 'HostedZone', { zoneName: 'cstructs.com' });

  new UrlShortener(stack, 'UrlShortener', {
    hostedZone,
    expiration: Duration.days(60),
  });

  expect(Template.fromStack(stack).toJSON()).toMatchSnapshot();
});
Example #4
Source File: toolkit-cleaner.test.ts    From cloudstructs with Apache License 2.0 6 votes vote down vote up
test('with retainAssetsNewerThan', () => {
  new ToolkitCleaner(stack, 'ToolkitCleaner', {
    retainAssetsNewerThan: Duration.days(90),
  });

  Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', {
    Environment: {
      Variables: Match.objectLike({
        RETAIN_MILLISECONDS: '7776000000',
      }),
    },
  });
});
Example #5
Source File: ecs-service-roller.test.ts    From cloudstructs with Apache License 2.0 6 votes vote down vote up
test('EcsServiceRoller with schedule', () => {
  new EcsServiceRoller(stack, 'EcsServiceRoller', {
    cluster,
    service,
    trigger: RollTrigger.fromSchedule(events.Schedule.rate(Duration.hours(5))),
  });

  expect(Template.fromStack(stack).toJSON()).toMatchSnapshot();
});
Example #6
Source File: codecommit-mirror.test.ts    From cloudstructs with Apache License 2.0 6 votes vote down vote up
test('CodeCommitMirror with a private GitHub repo', () => {
  const urlSecret = secretsmanager.Secret.fromSecretNameV2(stack, 'Secret', 'clone-url');

  new CodeCommitMirror(stack, 'Mirror', {
    cluster,
    repository: CodeCommitMirrorSourceRepository.private('private', ecs.Secret.fromSecretsManager(urlSecret)),
    schedule: events.Schedule.rate(Duration.hours(6)),
  });

  expect(Template.fromStack(stack).toJSON()).toMatchSnapshot();
});
Example #7
Source File: index.ts    From cloudstructs with Apache License 2.0 6 votes vote down vote up
/**
  * Best practice security headers used as default
  */
  public static defaultSecurityHeadersBehavior: cloudfront.ResponseSecurityHeadersBehavior = {
    contentTypeOptions: {
      override: true,
    },
    frameOptions: {
      frameOption: cloudfront.HeadersFrameOption.DENY,
      override: true,
    },
    referrerPolicy: {
      referrerPolicy: cloudfront.HeadersReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
      override: true,
    },
    strictTransportSecurity: {
      accessControlMaxAge: Duration.seconds(31536000),
      includeSubdomains: true,
      preload: true,
      override: true,
    },
    xssProtection: {
      protection: true,
      modeBlock: true,
      override: true,
    },
  };
Example #8
Source File: StaticWebsiteAbstract.ts    From lift with MIT License 6 votes vote down vote up
private errorResponse(): ErrorResponse {
        const errorPath = this.errorPath();

        // Custom error page
        if (errorPath !== undefined) {
            return {
                httpStatus: 404,
                ttl: Duration.seconds(0),
                responseHttpStatus: 404,
                responsePagePath: errorPath,
            };
        }

        /**
         * The default behavior is optimized for SPA: all unknown URLs are served
         * by index.html so that routing can be done client-side.
         */
        return {
            httpStatus: 404,
            ttl: Duration.seconds(0),
            responseHttpStatus: 200,
            responsePagePath: "/index.html",
        };
    }
Example #9
Source File: Storage.ts    From lift with MIT License 6 votes vote down vote up
constructor(scope: CdkConstruct, id: string, configuration: Configuration, private provider: AwsProvider) {
        super(scope, id);

        const resolvedConfiguration = Object.assign({}, STORAGE_DEFAULTS, configuration);

        const encryptionOptions = {
            s3: BucketEncryption.S3_MANAGED,
            kms: BucketEncryption.KMS_MANAGED,
        };

        this.bucket = new Bucket(this, "Bucket", {
            encryption: encryptionOptions[resolvedConfiguration.encryption],
            versioned: true,
            blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
            enforceSSL: true,
            lifecycleRules: [
                {
                    transitions: [
                        {
                            storageClass: StorageClass.INTELLIGENT_TIERING,
                            transitionAfter: Duration.days(0),
                        },
                    ],
                },
                {
                    noncurrentVersionExpiration: Duration.days(30),
                },
            ],
        });

        this.bucketNameOutput = new CfnOutput(this, "BucketName", {
            value: this.bucket.bucketName,
        });
    }
Example #10
Source File: 001_UserPool.ts    From flect-chime-sdk-demo with Apache License 2.0 6 votes vote down vote up
createUserPool = (scope: Construct, id: string) => {
    const userPool = new cognito.UserPool(scope, `${id}_UserPool`, {
        userPoolName: `${id}_UserPool`,
        selfSignUpEnabled: true,
        autoVerify: {
            email: true,
        },
        passwordPolicy: {
            minLength: 6,
            requireSymbols: false,
        },
        signInAliases: {
            email: true,
        },
    });

    const userPoolClient = new cognito.UserPoolClient(scope, id + "_UserPool_Client", {
        userPoolClientName: `${id}_UserPoolClient`,
        userPool: userPool,
        accessTokenValidity: Duration.minutes(1440),
        idTokenValidity: Duration.minutes(1440),
        refreshTokenValidity: Duration.days(30),
    });

    return { userPool, userPoolClient }
}
Example #11
Source File: ServerSideWebsite.ts    From lift with MIT License 6 votes vote down vote up
private createErrorResponses(): ErrorResponse[] {
        let responsePagePath = undefined;
        if (this.configuration.errorPage !== undefined) {
            responsePagePath = `/${this.getErrorPageFileName()}`;
        }

        return [
            {
                httpStatus: 500,
                // Disable caching of error responses
                ttl: Duration.seconds(0),
                responsePagePath,
            },
            {
                httpStatus: 504,
                // Disable caching of error responses
                ttl: Duration.seconds(0),
                responsePagePath,
            },
        ];
    }
Example #12
Source File: 002_lambdas.ts    From flect-chime-sdk-demo with Apache License 2.0 6 votes vote down vote up
createLambdas = (scope: Construct) => {
    // (1) Base Parameters
    const baseParameters = {
        code: lambda.Code.fromAsset(`${__dirname}/../dist`),
        runtime: lambda.Runtime.NODEJS_14_X,
        timeout: Duration.seconds(5),
        memorySize: 256,

    }

    // (2) Function
    //// (2-1) Auth
    const lambdaFuncRestAPIAuth = new lambda.Function(scope, "ChimeRESTAPIAuth", {
        ...baseParameters,
        handler: "rest_auth.authorize",
    });

    //// (2-2) Rest
    const lambdaFunctionForRestAPI: lambda.Function = new lambda.Function(scope, "funcHelloWorld", {
        ...baseParameters,
        handler: "index.handler",
    });

    //// (2-3) Slack Rest
    const lambdaFunctionForSlackFederationRestAPI: lambda.Function = new lambda.Function(scope, "funcSlackFederation", {
        ...baseParameters,
        handler: "slack.handler",
    });

    return { lambdaFuncRestAPIAuth, lambdaFunctionForRestAPI, lambdaFunctionForSlackFederationRestAPI }
}
Example #13
Source File: receiver.ts    From cloudstructs with Apache License 2.0 5 votes vote down vote up
constructor(scope: Construct, id: string, props: EmailReceiverProps) {
    super(scope, id);

    const receiptRule = new ses.ReceiptRule(this, 'ReceiptRule', {
      ruleSet: props.receiptRuleSet,
      recipients: props.recipients,
      after: props.afterRule,
    });

    const bucket = new s3.Bucket(this, 'Bucket', {
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      encryption: s3.BucketEncryption.S3_MANAGED,
      lifecycleRules: [{ expiration: Duration.days(1) }],
    });
    bucket.grantRead(props.function); // Download email

    const topic = new sns.Topic(this, 'Topic');

    // Actions
    if (props.sourceWhitelist) {
      const whitelistHandler = new WhitelistFunction(this, 'whitelist', {
        environment: {
          SOURCE_WHITELIST: props.sourceWhitelist,
        },
        logRetention: logs.RetentionDays.ONE_MONTH,
      });

      receiptRule.addAction(new actions.Lambda({
        function: whitelistHandler,
        invocationType: actions.LambdaInvocationType.REQUEST_RESPONSE,
      }));
    }

    receiptRule.addAction(new actions.S3({
      bucket,
      topic,
    }));

    const s3Handler = new S3Function(this, 's3', {
      logRetention: logs.RetentionDays.ONE_MONTH,
      onSuccess: new destinations.LambdaDestination(props.function, {
        responseOnly: true,
      }),
    });

    topic.addSubscription(new subscriptions.LambdaSubscription(s3Handler)); // Notify
  }
Example #14
Source File: index.ts    From cdk-ssm-document with Apache License 2.0 5 votes vote down vote up
private ensureLambda(): aws_lambda.Function {
    const stack = Stack.of(this);
    const constructName = 'SSM-Document-Manager-Lambda';
    const existing = stack.node.tryFindChild(constructName);
    if (existing) {
      return existing as aws_lambda.Function;
    }

    const policy = new aws_iam.ManagedPolicy(
      stack,
      'SSM-Document-Manager-Policy',
      {
        managedPolicyName: `${stack.stackName}-${cleanID}`,
        description: `Used by Lambda ${cleanID}, which is a custom CFN resource, managing SSM documents`,
        statements: [
          new aws_iam.PolicyStatement({
            actions: ['ssm:ListDocuments', 'ssm:ListTagsForResource'],
            resources: ['*'],
          }),
          new aws_iam.PolicyStatement({
            actions: ['ssm:AddTagsToResource', 'ssm:CreateDocument'],
            resources: ['*'],
            conditions: {
              StringLike: {
                'aws:RequestTag/CreatedByCfnCustomResource': ID,
              },
            },
          }),
          new aws_iam.PolicyStatement({
            actions: [
              'ssm:AddTagsToResource',
              'ssm:DeleteDocument',
              'ssm:DescribeDocument',
              'ssm:GetDocument',
              'ssm:ListDocumentVersions',
              'ssm:ModifyDocumentPermission',
              'ssm:RemoveTagsFromResource',
              'ssm:UpdateDocument',
              'ssm:UpdateDocumentDefaultVersion',
            ],
            resources: ['*'],
            conditions: {
              StringLike: {
                'ssm:ResourceTag/CreatedByCfnCustomResource': ID,
              },
            },
          }),
        ],
      }
    );

    const role = new aws_iam.Role(stack, 'SSM-Document-Manager-Role', {
      roleName: `${stack.stackName}-${cleanID}`,
      description: `Used by Lambda ${cleanID}, which is a custom CFN resource, managing SSM documents`,
      assumedBy: new aws_iam.ServicePrincipal('lambda.amazonaws.com'),
      managedPolicies: [
        policy,
        aws_iam.ManagedPolicy.fromAwsManagedPolicyName(
          'service-role/AWSLambdaBasicExecutionRole'
        ),
      ],
    });

    const fn = new aws_lambda.Function(stack, constructName, {
      functionName: `${stack.stackName}-${cleanID}`,
      role: role,
      description: 'Custom CFN resource: Manage SSM Documents',
      runtime: aws_lambda.Runtime.NODEJS_14_X,
      handler: 'index.handler',
      code: aws_lambda.Code.fromAsset(
        path.join(__dirname, '../lambda/code.zip')
      ),
      timeout: Duration.minutes(lambdaTimeout),
    });

    return fn;
  }
Example #15
Source File: 001_FrontendBucket.ts    From flect-chime-sdk-demo with Apache License 2.0 5 votes vote down vote up
createFrontendS3 = (scope: Construct, id: string, USE_CDN: boolean) => {
    const frontendBucket = new s3.Bucket(scope, "StaticSiteBucket", {
        bucketName: `${id}-Bucket`.toLowerCase(),
        removalPolicy: RemovalPolicy.DESTROY,
        publicReadAccess: true,
    });


    let frontendCdn: cloudfront.CloudFrontWebDistribution | null = null;
    if (USE_CDN) {
        const oai = new cloudfront.OriginAccessIdentity(scope, "my-oai");

        const myBucketPolicy = new iam.PolicyStatement({
            effect: iam.Effect.ALLOW,
            actions: ["s3:GetObject"],
            principals: [new iam.CanonicalUserPrincipal(oai.cloudFrontOriginAccessIdentityS3CanonicalUserId)],
            resources: [frontendBucket.bucketArn + "/*"],
        });
        frontendBucket.addToResourcePolicy(myBucketPolicy);

        // Create CloudFront WebDistribution
        frontendCdn = new cloudfront.CloudFrontWebDistribution(scope, "WebsiteDistribution", {
            viewerCertificate: {
                aliases: [],
                props: {
                    cloudFrontDefaultCertificate: true,
                },
            },
            priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
            originConfigs: [
                {
                    s3OriginSource: {
                        s3BucketSource: frontendBucket,
                        originAccessIdentity: oai,
                    },
                    behaviors: [
                        {
                            isDefaultBehavior: true,
                            minTtl: Duration.seconds(0),
                            maxTtl: Duration.days(365),
                            defaultTtl: Duration.days(1),
                            pathPattern: "my-contents/*",
                        },
                    ],
                },
            ],
            errorConfigurations: [
                {
                    errorCode: 403,
                    responsePagePath: "/index.html",
                    responseCode: 200,
                    errorCachingMinTtl: 0,
                },
                {
                    errorCode: 404,
                    responsePagePath: "/index.html",
                    responseCode: 200,
                    errorCachingMinTtl: 0,
                },
            ],
        });
    }
    return { frontendBucket, frontendCdn }
}
Example #16
Source File: aws-secure-bucket.ts    From amazon-ec2-image-builder-samples with MIT No Attribution 5 votes vote down vote up
constructor(scope: Construct, id: string, props?: AWSSecureBucketProps) {
    super(scope, id);

    this.encryptionKey = props?.encryptionKeyArn
      ? Key.fromKeyArn(this, `encryption-key-${id}`, props?.encryptionKeyArn)
      : new Key(this, `encryption-key-${id}`, {
          removalPolicy: props?.removalPolicy,
          enableKeyRotation: true,
        });

    this.bucket = new Bucket(this, `aws-${id}`, {
      ...props,
      encryptionKey: this.encryptionKey,
      encryption: BucketEncryption.KMS,
      blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
      enforceSSL: true,
      versioned: true,
      objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_PREFERRED,
      autoDeleteObjects: false,
      serverAccessLogsPrefix: "imageBuilder",
      lifecycleRules: [
        {
          abortIncompleteMultipartUploadAfter: Duration.days(300),
          transitions: [
            {
              storageClass: StorageClass.INFREQUENT_ACCESS,
              transitionAfter: Duration.days(30),
            },
            {
              storageClass: StorageClass.INTELLIGENT_TIERING,
              transitionAfter: Duration.days(60),
            },
            {
              storageClass: StorageClass.GLACIER,
              transitionAfter: Duration.days(180),
            },
            {
              storageClass: StorageClass.DEEP_ARCHIVE,
              transitionAfter: Duration.days(365),
            },
          ],
        },
      ],
    });

    if (props?.objectLockMode && props?.objectLockRetentionDays) {
      // Add Object Lock configuration to the bucket
      const bucket = this.bucket.node.defaultChild as CfnBucket;

      bucket.objectLockEnabled = true as boolean;
      bucket.objectLockConfiguration = {
        objectLockEnabled: "Enabled",
        rule: {
          defaultRetention: {
            days: props.objectLockRetentionDays,
            mode: props.objectLockMode,
          },
        },
      } as s3.CfnBucket.ObjectLockConfigurationProperty;
    }

    this.bucket.addToResourcePolicy(
      new PolicyStatement({
        sid: "HttpsOnly",
        resources: [`${this.bucket.bucketArn}/*`],
        actions: ["*"],
        principals: [new AnyPrincipal()],
        effect: Effect.DENY,
        conditions: {
          Bool: {
            "aws:SecureTransport": "false",
          },
        },
      })
    );
  }
Example #17
Source File: toolkit-cleaner.integ.ts    From cloudstructs with Apache License 2.0 5 votes vote down vote up
constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    new ToolkitCleaner(this, 'ToolkitCleaner', {
      dryRun: true,
      retainAssetsNewerThan: Duration.days(90),
    });
  }
Example #18
Source File: s3cloudfront.ts    From cdk-examples with MIT License 5 votes vote down vote up
constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    ///////////////////////////////
    // Part 2
    const bucket = new Bucket(this, 'websiteBucket', {
        removalPolicy: RemovalPolicy.DESTROY,
        bucketName: website_domain,
        autoDeleteObjects: true
    })
    
    new CfnOutput(this, 'websiteBucketArn', {
        value: bucket.bucketArn
    })
    ///////////////////////////////

    ///////////////////////////////
    // Part 3
    const certificate = Certificate.fromCertificateArn(this, 'websiteCert', websiteCertArn)
    
    const cachePolicy = new CachePolicy(this, 'examplePolicy', {
        defaultTtl: Duration.hours(24),
        minTtl: Duration.hours(24),
        maxTtl: Duration.hours(24),
        enableAcceptEncodingGzip: true,
        enableAcceptEncodingBrotli: true
    })

    const distribution = new Distribution(this, 'exampleDistribution', {
        defaultBehavior: {
            origin: new S3Origin(bucket),
            allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
            cachePolicy,
            compress: true,
            viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS
        },
        domainNames: [website_domain],
        certificate,
        minimumProtocolVersion: SecurityPolicyProtocol.TLS_V1_2_2021,
        defaultRootObject: 'index.html',
        enableIpv6: true,
        enabled: true,
        httpVersion: HttpVersion.HTTP2,
        priceClass: PriceClass.PRICE_CLASS_ALL
    })
    ///////////////////////////////

    ///////////////////////////////
    // Part 4
    const hostedZone = HostedZone.fromHostedZoneAttributes(this, 'hostedZoneWithAttrs', {
        hostedZoneId,
        zoneName: website_domain
    })
    
    new ARecord(this, 'aliasForCloudfront', {
        target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),
        zone: hostedZone,
        recordName: website_domain
    })
    ///////////////////////////////

    ///////////////////////////////
    // Part 5
    new HttpsRedirect(this, 'wwwToNonWww', {
        recordNames: ['www.example.com'],
        targetDomain: website_domain,
        zone:hostedZone
    })

    const repo = new Repository(this, 'reactSourceCode', {
        repositoryName: 'example',
        description: `react repo for ${website_domain}`
    })

    new CfnOutput(this, 'reactRepoArn', {
        value: repo.repositoryArn
    })
    ///////////////////////////////
  }
Example #19
Source File: 003_lamdaForWebsocket.ts    From flect-chime-sdk-demo with Apache License 2.0 5 votes vote down vote up
createLambdaForWebsocket = (scope: Construct) => {
    // (1) Base Parameters
    const baseParameters = {
        code: lambda.Code.fromAsset(`${__dirname}/../dist`),
        runtime: lambda.Runtime.NODEJS_14_X,
        timeout: Duration.seconds(30),
        memorySize: 256,
    }

    // (2) Functions
    // (2-1) connect
    const lambdaFuncMessageConnect = new lambda.Function(scope, "ChimeMessageAPIConnect", {
        ...baseParameters,
        handler: "message.connect",
    });

    // (2-2) disconnect
    const lambdaFuncMessageDisconnect = new lambda.Function(scope, "ChimeMessageAPIDisconnect", {
        ...baseParameters,
        handler: "message.disconnect",
    });

    // (2-3) message
    const lambdaFuncMessageMessage = new lambda.Function(scope, "ChimeMessageAPIMessage", {
        ...baseParameters,
        handler: "message.message",
    });

    // (2-4) auth
    const lambdaFuncMessageAuth = new lambda.Function(scope, "ChimeMessageAPIAuth", {
        ...baseParameters,
        handler: "message.authorize",
    });

    return {
        lambdaFuncMessageConnect,
        lambdaFuncMessageDisconnect,
        lambdaFuncMessageMessage,
        lambdaFuncMessageAuth
    }
}
Example #20
Source File: dns-validated-domain-identity.ts    From aws-cdk-ses-domain-identity with MIT License 5 votes vote down vote up
public constructor(scope: Construct, id: string, props: DnsValidatedDomainIdentityProps) {
    super(scope, id);

    const stack = Stack.of(this);

    const region = props.region ?? stack.region;
    const accountId = stack.account;

    this.domainName = props.domainName;
    this.dkim = props.dkim ?? false;
    this.identityArn = `arn:aws:ses:${region}:${accountId}:identity/${this.domainName}`;
    this.normalizedZoneName = props.hostedZone.zoneName;
    // Remove trailing `.` from zone name
    if (this.normalizedZoneName.endsWith(".")) {
      this.normalizedZoneName = this.normalizedZoneName.substring(0, this.normalizedZoneName.length - 1);
    }

    // Remove any `/hostedzone/` prefix from the Hosted Zone ID
    this.hostedZoneId = props.hostedZone.hostedZoneId.replace(/^\/hostedzone\//, "");

    const requestorFunction = new lambda.Function(this, "DomainIdentityRequestorFunction", {
      code: lambda.Code.fromAsset(path.resolve(__dirname, "..", "lambda-packages", "dns-validated-domain-identity-handler", "dist")),
      handler: "index.identityRequestHandler",
      runtime: lambda.Runtime.NODEJS_14_X,
      memorySize: 128,
      timeout: Duration.minutes(15),
      role: props.customResourceRole,
    });
    requestorFunction.addToRolePolicy(new iam.PolicyStatement({
      actions: [
        "ses:GetIdentityVerificationAttributes",
        "ses:GetIdentityDkimAttributes",
        "ses:SetIdentityDkimEnabled",
        "ses:VerifyDomainIdentity",
        "ses:VerifyDomainDkim",
        "ses:ListIdentities",
        "ses:DeleteIdentity",
      ],
      resources: ["*"],
    }));
    requestorFunction.addToRolePolicy(new iam.PolicyStatement({
      actions: ["route53:GetChange"],
      resources: ["*"],
    }));
    requestorFunction.addToRolePolicy(new iam.PolicyStatement({
      actions: [
          "route53:changeResourceRecordSets",
          "route53:ListResourceRecordSets",
      ],
      resources: [`arn:${Stack.of(requestorFunction).partition}:route53:::hostedzone/${this.hostedZoneId}`],
    }));

    const identity = new CustomResource(this, "IdentityRequestorResource", {
      serviceToken: requestorFunction.functionArn,
      properties: {
        DomainName: this.domainName,
        HostedZoneId: this.hostedZoneId,
        Region: region,
        DKIM: props.dkim,
      },
    });

    this.node.addValidation({
      validate: (): string[] => {
        const errors: string[] = [];
        // Ensure the zone name is a parent zone of the certificate domain name
        if (!Token.isUnresolved(this.normalizedZoneName) &&
          this.domainName !== this.normalizedZoneName &&
          !this.domainName.endsWith("." + this.normalizedZoneName)) {
          errors.push(`DNS zone ${this.normalizedZoneName} is not authoritative for SES identity domain name ${this.domainName}`);
        }

        return errors;
      },
    });
  }
Example #21
Source File: cloudfront.ts    From minwiz with BSD 2-Clause "Simplified" License 5 votes vote down vote up
constructor(scope: Construct, id: string, props: CloudfrontStackProps) {
    super(scope, id, props);

    this.websiteBucket = new Bucket(this, "websiteBucket", {
      removalPolicy: RemovalPolicy.DESTROY,
      bucketName: website_domain,
      autoDeleteObjects: true,
    });

    new CfnOutput(this, "websiteBucketArn", {
      value: this.websiteBucket.bucketArn,
    });

    const cachePolicy = new CachePolicy(this, "MinWizPolicy", {
      defaultTtl: Duration.hours(24),
      minTtl: Duration.hours(24),
      maxTtl: Duration.hours(24),
      enableAcceptEncodingGzip: true,
      enableAcceptEncodingBrotli: true,
    });

    const distribution = new Distribution(this, "MinWizDistribution", {
      defaultBehavior: {
        origin: new S3Origin(this.websiteBucket),
        allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
        cachePolicy,
        compress: true,
        viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
      },
      domainNames: [website_domain /*, `www.${website_domain}`*/],
      certificate: props.websiteCert,
      minimumProtocolVersion: SecurityPolicyProtocol.TLS_V1_2_2019,
      defaultRootObject: "index.html",
      enableIpv6: true,
      enabled: true,
      httpVersion: HttpVersion.HTTP2,
      priceClass: PriceClass.PRICE_CLASS_ALL,
    });

    new ARecord(this, "aliasForCloudfront", {
      target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),
      zone: props.hostedZone,
      recordName: website_domain,
    });

    new HttpsRedirect(this, "wwwToNonWww", {
      recordNames: [`www.${website_domain}`],
      targetDomain: website_domain,
      zone: props.hostedZone,
    });
  }
Example #22
Source File: index.ts    From cloudstructs with Apache License 2.0 4 votes vote down vote up
constructor(scope: Construct, id: string, props: UrlShortenerProps) {
    super(scope, id);

    const domainName = props.recordName ? `${props.recordName}.${props.hostedZone.zoneName}` : props.hostedZone.zoneName;

    // Table to save a counter
    const table = new dynamodb.Table(this, 'Table', {
      partitionKey: {
        name: 'key',
        type: dynamodb.AttributeType.STRING,
      },
      removalPolicy: RemovalPolicy.DESTROY,
    });

    // Bucket to save redirects
    const bucket = new s3.Bucket(this, 'Bucket', {
      lifecycleRules: [{
        expiration: props.expiration ?? Duration.days(365),
      }],
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      bucketName: props.bucketName ?? `cloudstructs-url-shortener-${domainName}`,
    });

    // Redirect function
    const redirectFunction = new RedirectFunction(this, 'Redirect');
    bucket.grantRead(redirectFunction);

    // CloudFront distribution
    const certificate = new acm.DnsValidatedCertificate(this, 'Certificate', {
      domainName,
      hostedZone: props.hostedZone,
      region: 'us-east-1',
    });
    const distribution = new cloudfront.Distribution(this, 'Distribution', {
      defaultBehavior: {
        origin: new origins.S3Origin(bucket),
        edgeLambdas: [
          {
            eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST,
            functionVersion: redirectFunction,
          },
        ],
      },
      certificate,
      domainNames: [domainName],
    });

    // Route53 records
    new route53.ARecord(this, 'ARecord', {
      zone: props.hostedZone,
      target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)),
      recordName: props.recordName,
    });
    new route53.AaaaRecord(this, 'AaaaRecord', {
      zone: props.hostedZone,
      target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)),
      recordName: props.recordName,
    });

    // Lambda function to increment counter and write redirect in bucket
    const shortenerFunction = new ShortenerFunction(this, 'Shortener', {
      logRetention: logs.RetentionDays.ONE_MONTH,
      environment: {
        DOMAIN_NAME: domainName,
        BUCKET_NAME: bucket.bucketName,
        TABLE_NAME: table.tableName,
      },
    });
    if (props.corsAllowOrigins) {
      shortenerFunction.addEnvironment('CORS_ALLOW_ORIGINS', props.corsAllowOrigins.join(' '));
    }
    bucket.grantPut(shortenerFunction);
    table.grant(shortenerFunction, 'dynamodb:UpdateItem');

    // API
    this.api = new apigateway.RestApi(this, `UrlShortener${props.hostedZone.zoneName}`, {
      endpointTypes: props.apiGatewayEndpoint ? [apigateway.EndpointType.PRIVATE] : undefined,
      policy: props.apiGatewayEndpoint
        ? new iam.PolicyDocument({
          statements: [
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: ['execute-api:Invoke'],
              principals: [new iam.AnyPrincipal()],
              resources: [Fn.join('', ['execute-api:/', '*'])],
              conditions: {
                StringEquals: { 'aws:SourceVpce': props.apiGatewayEndpoint.vpcEndpointId },
              },
            }),
          ],
        })
        : undefined,
      defaultCorsPreflightOptions: props.corsAllowOrigins
        ? { allowOrigins: props.corsAllowOrigins }
        : undefined,
    });

    this.api.root.addMethod('ANY', new apigateway.LambdaIntegration(shortenerFunction), {
      authorizer: props.apiGatewayAuthorizer,
    });
    this.api.root
      .addResource('{proxy+}')
      .addMethod('ANY', new apigateway.LambdaIntegration(shortenerFunction), {
        authorizer: props.apiGatewayAuthorizer,
      });

    this.apiEndpoint = this.api.url;
  }
Example #23
Source File: index.ts    From cdk-ec2-key-pair with Apache License 2.0 4 votes vote down vote up
private ensureLambda(): aws_lambda.Function {
    const stack = Stack.of(this);
    const constructName = 'EC2-Key-Name-Manager-Lambda';
    const existing = stack.node.tryFindChild(constructName);
    if (existing) {
      return existing as aws_lambda.Function;
    }

    const resources = [`arn:${stack.partition}:ec2:*:*:key-pair/*`];

    const policy = new aws_iam.ManagedPolicy(
      stack,
      'EC2-Key-Pair-Manager-Policy',
      {
        managedPolicyName: `${this.prefix}-${cleanID}`,
        description: `Used by Lambda ${cleanID}, which is a custom CFN resource, managing EC2 Key Pairs`,
        statements: [
          new aws_iam.PolicyStatement({
            actions: ['ec2:DescribeKeyPairs'],
            resources: ['*'],
          }),
          new aws_iam.PolicyStatement({
            actions: [
              'ec2:CreateKeyPair',
              'ec2:CreateTags',
              'ec2:ImportKeyPair',
            ],
            conditions: {
              StringLike: {
                'aws:RequestTag/CreatedByCfnCustomResource': ID,
              },
            },
            resources,
          }),
          new aws_iam.PolicyStatement({
            // allow delete/update, only if createdByTag is set
            actions: ['ec2:CreateTags', 'ec2:DeleteKeyPair', 'ec2:DeleteTags'],
            conditions: {
              StringLike: {
                'ec2:ResourceTag/CreatedByCfnCustomResource': ID,
              },
            },
            resources,
          }),

          new aws_iam.PolicyStatement({
            // we need this to check if a secret exists before attempting to delete it
            actions: ['secretsmanager:ListSecrets'],
            resources: ['*'],
          }),
          new aws_iam.PolicyStatement({
            actions: [
              'secretsmanager:CreateSecret',
              'secretsmanager:TagResource',
            ],
            conditions: {
              StringLike: {
                'aws:RequestTag/CreatedByCfnCustomResource': ID,
              },
            },
            resources: ['*'],
          }),
          new aws_iam.PolicyStatement({
            // allow delete/update, only if createdByTag is set
            actions: [
              'secretsmanager:DeleteResourcePolicy',
              'secretsmanager:DeleteSecret',
              'secretsmanager:DescribeSecret',
              'secretsmanager:GetResourcePolicy',
              'secretsmanager:GetSecretValue',
              'secretsmanager:ListSecretVersionIds',
              'secretsmanager:PutResourcePolicy',
              'secretsmanager:PutSecretValue',
              'secretsmanager:RestoreSecret',
              'secretsmanager:UntagResource',
              'secretsmanager:UpdateSecret',
              'secretsmanager:UpdateSecretVersionStage',
            ],
            conditions: {
              StringLike: {
                'secretsmanager:ResourceTag/CreatedByCfnCustomResource': ID,
              },
            },
            resources: ['*'],
          }),
        ],
      }
    );

    const role = new aws_iam.Role(stack, 'EC2-Key-Pair-Manager-Role', {
      roleName: `${this.prefix}-${cleanID}`,
      description: `Used by Lambda ${cleanID}, which is a custom CFN resource, managing EC2 Key Pairs`,
      assumedBy: new aws_iam.ServicePrincipal('lambda.amazonaws.com'),
      managedPolicies: [
        policy,
        aws_iam.ManagedPolicy.fromAwsManagedPolicyName(
          'service-role/AWSLambdaBasicExecutionRole'
        ),
      ],
    });

    const fn = new aws_lambda.Function(stack, constructName, {
      functionName: `${this.prefix}-${cleanID}`,
      role: role,
      description: 'Custom CFN resource: Manage EC2 Key Pairs',
      runtime: aws_lambda.Runtime.NODEJS_14_X,
      handler: 'index.handler',
      code: aws_lambda.Code.fromAsset(
        path.join(__dirname, '../lambda/code.zip')
      ),
      timeout: Duration.minutes(lambdaTimeout),
    });

    return fn;
  }
Example #24
Source File: gitlab-runner-instance.ts    From cdk-gitlab-runner with Apache License 2.0 4 votes vote down vote up
constructor(scope: Construct, id: string, props: GitlabContainerRunnerProps) {
    super(scope, id);
    const spotFleetId = id;

    const defaultProps = {
      gitlabRunnerImage: 'public.ecr.aws/gitlab/gitlab-runner:alpine',
      gitlaburl: 'https://gitlab.com/',
      ec2type: 't3.micro',
      tags: ['gitlab', 'awscdk', 'runner'],
    };
    const runnerProps = { ...defaultProps, ...props };

    const runnerBucket = new Bucket(this, 'runnerBucket', {
      removalPolicy: RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });
    const shell = UserData.forLinux();
    shell.addCommands(...this.createUserData(runnerProps, runnerBucket.bucketName));

    this.runnerRole =
      runnerProps.ec2iamrole ??
      new Role(this, 'runner-role', {
        assumedBy: new ServicePrincipal('ec2.amazonaws.com'),
        description: 'For Gitlab EC2 Runner Role',
      });
    this.validUntil = runnerProps.validUntil;
    const instanceProfile = new CfnInstanceProfile(this, 'InstanceProfile', {
      roles: [this.runnerRole.roleName],
    });
    runnerBucket.grantWrite(this.runnerRole);
    this.vpc =
      runnerProps.selfvpc ??
      new Vpc(this, 'VPC', {
        cidr: '10.0.0.0/16',
        maxAzs: 2,
        subnetConfiguration: [
          {
            cidrMask: 26,
            name: 'RunnerVPC',
            subnetType: SubnetType.PUBLIC,
          },
        ],
        natGateways: 0,
      });
    this.defaultRunnerSG = new SecurityGroup(this, 'SpotFleetSg', {
      vpc: this.vpc,
    });
    this.defaultRunnerSG.connections.allowFromAnyIpv4(Port.tcp(22));
    const spotOrOnDemand = runnerProps.spotFleet ?? false;
    if (spotOrOnDemand) {
      //throw new Error('yes new spotfleet');

      const imageId = MachineImage.latestAmazonLinux({
        generation: AmazonLinuxGeneration.AMAZON_LINUX_2,
      }).getImage(this).imageId;
      const lt = new CfnLaunchTemplate(this, 'LaunchTemplate', {
        launchTemplateData: {
          imageId,
          instanceType: runnerProps.ec2type,
          blockDeviceMappings: [
            {
              deviceName: '/dev/xvda',
              ebs: {
                volumeSize: runnerProps.ebsSize ?? 60,
              },
            },
          ],
          userData: Fn.base64(shell.render()),
          keyName: runnerProps.keyName,
          tagSpecifications: [
            {
              resourceType: 'instance',
              tags: [
                {
                  key: 'Name',
                  value: `${Stack.of(this).stackName
                  }/spotFleetGitlabRunner/${spotFleetId}`,
                },
              ],
            },
          ],
          instanceMarketOptions: {
            marketType: 'spot',
            spotOptions: {
              blockDurationMinutes:
                runnerProps.blockDuration ?? BlockDuration.ONE_HOUR,
              instanceInterruptionBehavior:
                runnerProps.instanceInterruptionBehavior ??
                InstanceInterruptionBehavior.TERMINATE,
            },
          },
          securityGroupIds: this.defaultRunnerSG.connections.securityGroups.map(
            (m) => m.securityGroupId,
          ),
          iamInstanceProfile: {
            arn: instanceProfile.attrArn,
          },
        },
      });

      const spotFleetRole = new Role(this, 'FleetRole', {
        assumedBy: new ServicePrincipal('spotfleet.amazonaws.com'),
        managedPolicies: [
          ManagedPolicy.fromAwsManagedPolicyName(
            'service-role/AmazonEC2SpotFleetTaggingRole',
          ),
        ],
      });

      const vpcSubnetSelection = runnerProps.vpcSubnet ?? {
        subnetType: SubnetType.PUBLIC,
      };
      const subnetConfig = this.vpc
        .selectSubnets(vpcSubnetSelection)
        .subnets.map((s) => ({
          subnetId: s.subnetId,
        }));

      const cfnSpotFleet = new CfnSpotFleet(this, id, {
        spotFleetRequestConfigData: {
          launchTemplateConfigs: [
            {
              launchTemplateSpecification: {
                launchTemplateId: lt.ref,
                version: lt.attrLatestVersionNumber,
              },
              overrides: subnetConfig,
            },
          ],
          iamFleetRole: spotFleetRole.roleArn,
          targetCapacity: 1,
          validUntil: Lazy.string({ produce: () => this.validUntil }),
          terminateInstancesWithExpiration: true,
        },
      });
      const onEvent = new lambda.Function(this, 'OnEvent', {
        code: lambda.Code.fromAsset(path.join(__dirname, '../assets/functions')),
        handler: 'index.on_event',
        runtime: lambda.Runtime.PYTHON_3_8,
        timeout: Duration.seconds(60),
      });

      const isComplete = new lambda.Function(this, 'IsComplete', {
        code: lambda.Code.fromAsset(path.join(__dirname, '../assets/functions')),
        handler: 'index.is_complete',
        runtime: lambda.Runtime.PYTHON_3_8,
        timeout: Duration.seconds(60),
        role: onEvent.role,
      });

      const myProvider = new cr.Provider(this, 'MyProvider', {
        onEventHandler: onEvent,
        isCompleteHandler: isComplete,
        logRetention: logs.RetentionDays.ONE_DAY,
      });

      onEvent.addToRolePolicy(
        new PolicyStatement({
          actions: ['ec2:DescribeSpotFleetInstances'],
          resources: ['*'],
        }),
      );

      const fleetInstances = new CustomResource(this, 'GetInstanceId', {
        serviceToken: myProvider.serviceToken,
        properties: {
          SpotFleetRequestId: cfnSpotFleet.ref,
        },
      });

      fleetInstances.node.addDependency(cfnSpotFleet);
      this.spotFleetInstanceId = Token.asString(
        fleetInstances.getAtt('InstanceId'),
      );
      this.spotFleetRequestId = Token.asString(
        fleetInstances.getAtt('SpotInstanceRequestId'),
      );
      new CfnOutput(this, 'InstanceId', { value: this.spotFleetInstanceId });
      new CfnOutput(this, 'SpotFleetId', { value: cfnSpotFleet.ref });
    } else {
      this.runnerEc2 = new Instance(this, 'GitlabRunner', {
        instanceType: new InstanceType(runnerProps.ec2type),
        instanceName: 'Gitlab-Runner',
        vpc: this.vpc,
        vpcSubnets: runnerProps.vpcSubnet ?? {
          subnetType: SubnetType.PUBLIC,
        },
        machineImage: MachineImage.latestAmazonLinux({
          generation: AmazonLinuxGeneration.AMAZON_LINUX_2,
        }),
        role: this.runnerRole,
        userData: shell,
        securityGroup: this.defaultRunnerSG,
        blockDevices: [
          {
            deviceName: '/dev/xvda',
            volume: BlockDeviceVolume.ebs(runnerProps.ebsSize ?? 60),
          },
        ],
      });
      new CfnOutput(this, 'Runner-Instance-ID', {
        value: this.runnerEc2.instanceId,
      });
    }

    const unregisterRunnerOnEvent = new lambda.Function(this, 'unregisterRunnerOnEvent', {
      code: lambda.Code.fromAsset(path.join(__dirname, '../assets/functions')),
      handler: 'unregister_runner.on_event',
      runtime: lambda.Runtime.PYTHON_3_8,
      timeout: Duration.seconds(60),
    });

    const unregisterRunnerProvider = new cr.Provider(this, 'unregisterRunnerProvider', {
      onEventHandler: unregisterRunnerOnEvent,
      logRetention: logs.RetentionDays.ONE_DAY,
    });

    const unregisterRunnerCR = new CustomResource(this, 'unregisterRunnerCR', {
      resourceType: 'Custom::unregisterRunnerProvider',
      serviceToken: unregisterRunnerProvider.serviceToken,
      properties: {
        BucketName: runnerBucket.bucketName,
        GitlabUrl: runnerProps.gitlaburl,
      },
    });

    runnerBucket.grantReadWrite(unregisterRunnerOnEvent);
    unregisterRunnerCR.node.addDependency(runnerBucket);
    this.runnerRole.addManagedPolicy(
      ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'),
    );

    new CfnOutput(this, 'Runner-Role-Arn', {
      value: this.runnerRole.roleArn,
    });
  }
Example #25
Source File: s3cloudfront.ts    From cdk-examples with MIT License 4 votes vote down vote up
constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    ///////////////////////////////
    // Part 2
    const bucket = new Bucket(this, 'websiteBucket', {
        removalPolicy: RemovalPolicy.DESTROY,
        bucketName: website_domain,
        autoDeleteObjects: true,
        websiteIndexDocument: 'index.html',
        websiteErrorDocument: '404.html'
    })
    
    bucket.addToResourcePolicy(new PolicyStatement({
        effect: Effect.ALLOW,
        principals: [new AnyPrincipal()],
        actions: ['s3:GetObject'],
        resources: ['arn:aws:s3:::example.com/*'],
        conditions: {
            "StringLike":{"aws:Referer":[bucketRefererSecret]}
          }
    }))

    new CfnOutput(this, 'websiteBucketArn', {
        value: bucket.bucketArn
    })

    new CfnOutput(this, 'websiteBucketUrl', {
        value: bucket.bucketWebsiteUrl
    })
    ///////////////////////////////

    ///////////////////////////////
    // Part 3
    const certificate = Certificate.fromCertificateArn(this, 'websiteCert', websiteCertArn)
    
    const cachePolicy = new CachePolicy(this, 'examplePolicy', {
        defaultTtl: Duration.hours(24),
        minTtl: Duration.hours(24),
        maxTtl: Duration.hours(24),
        enableAcceptEncodingBrotli: true,
        enableAcceptEncodingGzip: true
    })

    const distribution = new Distribution(this, 'exampleDistribution', {
        defaultBehavior: {
            origin: new HttpOrigin(bucketWebsiteUrl, {
                protocolPolicy: OriginProtocolPolicy.HTTP_ONLY,
                customHeaders: {
                    'Referer': bucketRefererSecret
                }
            }),
            allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
            cachePolicy,
            compress: true,
            viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS
        },
        domainNames: [website_domain],
        certificate,
        minimumProtocolVersion: SecurityPolicyProtocol.TLS_V1_2_2021,
        enableIpv6: true,
        enabled: true,
        httpVersion: HttpVersion.HTTP2,
        priceClass: PriceClass.PRICE_CLASS_ALL
    })
    ///////////////////////////////

    ///////////////////////////////
    // Part 4
    const hostedZone = HostedZone.fromHostedZoneAttributes(this, 'hostedZoneWithAttrs', {
        hostedZoneId,
        zoneName: website_domain
    })
    
    new ARecord(this, 'aliasForCloudfront', {
        target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),
        zone: hostedZone,
        recordName: website_domain
    })
    ///////////////////////////////

    ///////////////////////////////
    // Part 5
    new HttpsRedirect(this, 'wwwToNonWww', {
        recordNames: ['www.example.com'],
        targetDomain: website_domain,
        zone:hostedZone
    })

    const repo = new Repository(this, 'hugoSourceCode', {
        repositoryName: 'example',
        description: `hugo repo for ${website_domain}`
    })

    new CfnOutput(this, 'hugoRepoArn', {
        value: repo.repositoryArn
    })
    ///////////////////////////////
  }
Example #26
Source File: index.ts    From cloudstructs with Apache License 2.0 4 votes vote down vote up
constructor(scope: Construct, id: string, props: ToolkitCleanerProps = {}) {
    super(scope, id);

    // Dummy assets to reference S3 bucket and ECR repository
    const fileAsset = new Asset(this, 'FileAsset', {
      path: path.join(__dirname, '..', '..', 'assets', 'toolkit-cleaner', 'docker', 'dummy.txt'),
    });
    const dockerImageAsset = new DockerImageAsset(this, 'DockerImageAsset', {
      directory: path.join(__dirname, '..', '..', 'assets', 'toolkit-cleaner', 'docker'),
    });

    const getStackNamesFunction = new GetStackNamesFunction(this, 'GetStackNamesFunction');
    getStackNamesFunction.addToRolePolicy(new PolicyStatement({
      actions: ['cloudformation:DescribeStacks'],
      resources: ['*'],
    }));
    const getStackNames = new tasks.LambdaInvoke(this, 'GetStackNames', {
      lambdaFunction: getStackNamesFunction,
      payloadResponseOnly: true,
    });

    const stacksMap = new sfn.Map(this, 'StacksMap', {
      maxConcurrency: 1, // Avoid "Rate exceeded" error from CloudFormation
      resultSelector: {
        AssetHashes: sfn.JsonPath.stringAt('$'),
      },
    });

    const extractTemplateHashesFunction = new ExtractTemplateHashesFunction(this, 'ExtractTemplateHashesFunction', {
      timeout: Duration.seconds(30),
    });
    extractTemplateHashesFunction.addToRolePolicy(new PolicyStatement({
      actions: ['cloudformation:GetTemplate'],
      resources: ['*'],
    }));
    const extractTemplateHashes = new tasks.LambdaInvoke(this, 'ExtractTemplateHashes', {
      lambdaFunction: extractTemplateHashesFunction,
      payloadResponseOnly: true,
    }).addRetry({
      errors: ['Throttling'], // Avoid "Rate exceeded" error from CloudFormation
    });

    const flattenHashes = new tasks.EvaluateExpression(this, 'FlattenHashes', {
      expression: '[...new Set(($.AssetHashes).flat())]',
    });

    const cleanObjectsFunction = new CleanObjectsFunction(this, 'CleanObjectsFunction', {
      timeout: Duration.minutes(5),
    });
    cleanObjectsFunction.addEnvironment('BUCKET_NAME', fileAsset.bucket.bucketName);
    fileAsset.bucket.grantRead(cleanObjectsFunction);
    fileAsset.bucket.grantDelete(cleanObjectsFunction);
    const cleanObjects = new tasks.LambdaInvoke(this, 'CleanObjects', {
      lambdaFunction: cleanObjectsFunction,
      payloadResponseOnly: true,
    });

    const cleanImagesFunction = new CleanImagesFunction(this, 'CleanImagesFunction', {
      timeout: Duration.minutes(5),
    });
    cleanImagesFunction.addEnvironment('REPOSITORY_NAME', dockerImageAsset.repository.repositoryName);
    dockerImageAsset.repository.grant(cleanImagesFunction, 'ecr:DescribeImages', 'ecr:BatchDeleteImage');
    const cleanImages = new tasks.LambdaInvoke(this, 'CleanImages', {
      lambdaFunction: cleanImagesFunction,
      payloadResponseOnly: true,
    });

    if (!props.dryRun) {
      cleanObjectsFunction.addEnvironment('RUN', 'true');
      cleanImagesFunction.addEnvironment('RUN', 'true');
    }

    if (props.retainAssetsNewerThan) {
      const retainMilliseconds = props.retainAssetsNewerThan.toMilliseconds().toString();
      cleanObjectsFunction.addEnvironment('RETAIN_MILLISECONDS', retainMilliseconds);
      cleanImagesFunction.addEnvironment('RETAIN_MILLISECONDS', retainMilliseconds);
    }

    const sumReclaimed = new tasks.EvaluateExpression(this, 'SumReclaimed', {
      expression: '({ Deleted: $[0].Deleted + $[1].Deleted, Reclaimed: $[0].Reclaimed + $[1].Reclaimed })',
    });

    const stateMachine = new sfn.StateMachine(this, 'Resource', {
      definition: getStackNames
        .next(stacksMap.iterator(extractTemplateHashes))
        .next(flattenHashes)
        .next(new sfn.Parallel(this, 'Clean')
          .branch(cleanObjects)
          .branch(cleanImages))
        .next(sumReclaimed),
    });

    const rule = new Rule(this, 'Rule', {
      enabled: props.scheduleEnabled ?? true,
      schedule: props.schedule ?? Schedule.rate(Duration.days(1)),
    });
    rule.addTarget(new SfnStateMachine(stateMachine));
  }
Example #27
Source File: index.ts    From cloudstructs with Apache License 2.0 4 votes vote down vote up
constructor(scope: Construct, id: string, props: StateMachineCustomResourceProviderProps) {
    super(scope, id);

    const cfnResponseSuccessFn = this.createCfnResponseFn('Success');
    const cfnResponseFailedFn = this.createCfnResponseFn('Failed');

    const role = new iam.Role(this, 'Role', {
      assumedBy: new iam.ServicePrincipal('states.amazonaws.com'),
    });
    role.addToPolicy(new iam.PolicyStatement({
      actions: ['lambda:InvokeFunction'],
      resources: [cfnResponseSuccessFn.functionArn, cfnResponseFailedFn.functionArn],
    }));
    // https://docs.aws.amazon.com/step-functions/latest/dg/stepfunctions-iam.html
    // https://docs.aws.amazon.com/step-functions/latest/dg/concept-create-iam-advanced.html#concept-create-iam-advanced-execution
    role.addToPolicy(new iam.PolicyStatement({
      actions: ['states:StartExecution'],
      resources: [props.stateMachine.stateMachineArn],
    }));
    role.addToPolicy(new iam.PolicyStatement({
      actions: ['states:DescribeExecution', 'states:StopExecution'],
      resources: [Stack.of(this).formatArn({
        service: 'states',
        resource: 'execution',
        arnFormat: ArnFormat.COLON_RESOURCE_NAME,
        resourceName: `${Stack.of(this).splitArn(props.stateMachine.stateMachineArn, ArnFormat.COLON_RESOURCE_NAME).resourceName}*`,
      })],
    }));
    role.addToPolicy(new iam.PolicyStatement({
      actions: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'],
      resources: [Stack.of(this).formatArn({
        service: 'events',
        resource: 'rule',
        resourceName: 'StepFunctionsGetEventsForStepFunctionsExecutionRule',
      })],
    }));

    const definition = Stack.of(this).toJsonString({
      StartAt: 'StartExecution',
      States: {
        StartExecution: {
          Type: 'Task',
          Resource: 'arn:aws:states:::states:startExecution.sync:2', // with sync:2 the Output is JSON parsed
          Parameters: {
            'Input.$': '$',
            'StateMachineArn': props.stateMachine.stateMachineArn,
          },
          TimeoutSeconds: (props.timeout ?? Duration.minutes(30)).toSeconds(),
          Next: 'CfnResponseSuccess',
          Catch: [{
            ErrorEquals: ['States.ALL'],
            Next: 'CfnResponseFailed',
          }],
        },
        CfnResponseSuccess: {
          Type: 'Task',
          Resource: cfnResponseSuccessFn.functionArn,
          End: true,
        },
        CfnResponseFailed: {
          Type: 'Task',
          Resource: cfnResponseFailedFn.functionArn,
          End: true,
        },
      },
    });

    const stateMachine = new CfnResource(this, 'StateMachine', {
      type: 'AWS::StepFunctions::StateMachine',
      properties: {
        DefinitionString: definition,
        RoleArn: role.roleArn,
      },
    });
    stateMachine.node.addDependency(role);

    const startExecution = new lambda.Function(this, 'StartExecution', {
      code: lambda.Code.fromAsset(path.join(__dirname, 'runtime')),
      handler: 'index.startExecution',
      runtime: lambda.Runtime.NODEJS_14_X,
    });
    startExecution.addToRolePolicy(new iam.PolicyStatement({
      actions: ['states:StartExecution'],
      resources: [stateMachine.ref],
    }));
    startExecution.addEnvironment('STATE_MACHINE_ARN', stateMachine.ref);

    this.serviceToken = startExecution.functionArn;
  }
Example #28
Source File: ServerSideWebsite.ts    From lift with MIT License 4 votes vote down vote up
constructor(
        scope: Construct,
        private readonly id: string,
        readonly configuration: Configuration,
        private readonly provider: AwsProvider
    ) {
        super(scope, id);

        if (configuration.domain !== undefined && configuration.certificate === undefined) {
            throw new ServerlessError(
                `Invalid configuration in 'constructs.${id}.certificate': if a domain is configured, then a certificate ARN must be configured as well.`,
                "LIFT_INVALID_CONSTRUCT_CONFIGURATION"
            );
        }
        if (configuration.errorPage !== undefined && !configuration.errorPage.endsWith(".html")) {
            throw new ServerlessError(
                `Invalid configuration in 'constructs.${id}.errorPage': the custom error page must be a static HTML file. '${configuration.errorPage}' does not end with '.html'.`,
                "LIFT_INVALID_CONSTRUCT_CONFIGURATION"
            );
        }

        const bucket = new Bucket(this, "Assets", {
            // Assets are compiled artifacts, we can clear them on serverless remove
            removalPolicy: RemovalPolicy.DESTROY,
        });

        /**
         * We create custom "Origin Policy" and "Cache Policy" for the backend.
         * "All URL query strings, HTTP headers, and cookies that you include in the cache key (using a cache policy) are automatically included in origin requests. Use the origin request policy to specify the information that you want to include in origin requests, but not include in the cache key."
         * https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/controlling-origin-requests.html
         */
        const backendOriginPolicy = new OriginRequestPolicy(this, "BackendOriginPolicy", {
            originRequestPolicyName: `${this.provider.stackName}-${id}`,
            comment: `Origin request policy for the ${id} website.`,
            cookieBehavior: OriginRequestCookieBehavior.all(),
            queryStringBehavior: OriginRequestQueryStringBehavior.all(),
            headerBehavior: this.headersToForward(),
        });
        const backendCachePolicy = new CachePolicy(this, "BackendCachePolicy", {
            cachePolicyName: `${this.provider.stackName}-${id}`,
            comment: `Cache policy for the ${id} website.`,
            // For the backend we disable all caching by default
            defaultTtl: Duration.seconds(0),
            // Authorization is an exception and must be whitelisted in the Cache Policy
            // This is the reason why we don't use the managed `CachePolicy.CACHING_DISABLED`
            headerBehavior: CacheHeaderBehavior.allowList("Authorization"),
        });

        const apiId =
            configuration.apiGateway === "rest"
                ? this.provider.naming.getRestApiLogicalId()
                : this.provider.naming.getHttpApiLogicalId();
        const apiGatewayDomain = Fn.join(".", [Fn.ref(apiId), `execute-api.${this.provider.region}.amazonaws.com`]);

        // Cast the domains to an array
        this.domains = configuration.domain !== undefined ? flatten([configuration.domain]) : undefined;
        const certificate =
            configuration.certificate !== undefined
                ? acm.Certificate.fromCertificateArn(this, "Certificate", configuration.certificate)
                : undefined;

        this.distribution = new Distribution(this, "CDN", {
            comment: `${provider.stackName} ${id} website CDN`,
            defaultBehavior: {
                // Origins are where CloudFront fetches content
                origin: new HttpOrigin(apiGatewayDomain, {
                    // API Gateway only supports HTTPS
                    protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY,
                }),
                // For a backend app we all all methods
                allowedMethods: AllowedMethods.ALLOW_ALL,
                cachePolicy: backendCachePolicy,
                viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
                // Forward all values (query strings, headers, and cookies) to the backend app
                // See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html#managed-origin-request-policies-list
                originRequestPolicy: backendOriginPolicy,
                functionAssociations: [
                    {
                        function: this.createRequestFunction(),
                        eventType: FunctionEventType.VIEWER_REQUEST,
                    },
                ],
            },
            // All the assets paths are created in there
            additionalBehaviors: this.createCacheBehaviors(bucket),
            errorResponses: this.createErrorResponses(),
            // Enable http2 transfer for better performances
            httpVersion: HttpVersion.HTTP2,
            certificate: certificate,
            domainNames: this.domains,
        });

        // CloudFormation outputs
        this.bucketNameOutput = new CfnOutput(this, "AssetsBucketName", {
            description: "Name of the bucket that stores the website assets.",
            value: bucket.bucketName,
        });
        let websiteDomain = this.getMainCustomDomain();
        if (websiteDomain === undefined) {
            // Fallback on the CloudFront domain
            websiteDomain = this.distribution.distributionDomainName;
        }
        this.domainOutput = new CfnOutput(this, "Domain", {
            description: "Website domain name.",
            value: websiteDomain,
        });
        this.cnameOutput = new CfnOutput(this, "CloudFrontCName", {
            description: "CloudFront CNAME.",
            value: this.distribution.distributionDomainName,
        });
        this.distributionIdOutput = new CfnOutput(this, "DistributionId", {
            description: "ID of the CloudFront distribution.",
            value: this.distribution.distributionId,
        });
    }
Example #29
Source File: Queue.ts    From lift with MIT License 4 votes vote down vote up
constructor(
        scope: CdkConstruct,
        private readonly id: string,
        private readonly configuration: Configuration,
        private readonly provider: AwsProvider
    ) {
        super(scope, id);

        // This should be covered by the schema validation, but until it is enforced by default
        // this is a very common error for users
        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
        if (configuration.worker === undefined) {
            throw new ServerlessError(
                `Invalid configuration in 'constructs.${this.id}': no 'worker' defined. Queue constructs require a 'worker' function to be defined.`,
                "LIFT_INVALID_CONSTRUCT_CONFIGURATION"
            );
        }

        // The default function timeout is 6 seconds in the Serverless Framework
        const functionTimeout = configuration.worker.timeout ?? 6;

        // This should be 6 times the lambda function's timeout + MaximumBatchingWindowInSeconds
        // See https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html
        const visibilityTimeout = functionTimeout * 6 + this.getMaximumBatchingWindow();

        const maxRetries = configuration.maxRetries ?? 3;

        let delay = undefined;
        if (configuration.delay !== undefined) {
            if (configuration.delay < 0 || configuration.delay > 900) {
                throw new ServerlessError(
                    `Invalid configuration in 'constructs.${this.id}': 'delay' must be between 0 and 900, '${configuration.delay}' given.`,
                    "LIFT_INVALID_CONSTRUCT_CONFIGURATION"
                );
            }

            delay = Duration.seconds(configuration.delay);
        }

        let encryption = undefined;
        if (isNil(configuration.encryption) || configuration.encryption.length === 0) {
            encryption = {};
        } else if (configuration.encryption === "kmsManaged") {
            encryption = { encryption: QueueEncryption.KMS_MANAGED };
        } else if (configuration.encryption === "kms") {
            if (isNil(configuration.encryptionKey) || configuration.encryptionKey.length === 0) {
                throw new ServerlessError(
                    `Invalid configuration in 'constructs.${this.id}': 'encryptionKey' must be set if the 'encryption' is set to 'kms'`,
                    "LIFT_INVALID_CONSTRUCT_CONFIGURATION"
                );
            }
            encryption = {
                encryption: QueueEncryption.KMS,
                encryptionMasterKey: new Key(this, configuration.encryptionKey),
            };
        } else {
            throw new ServerlessError(
                `Invalid configuration in 'constructs.${this.id}': 'encryption' must be one of 'kms', 'kmsManaged', null, '${configuration.encryption}' given.`,
                "LIFT_INVALID_CONSTRUCT_CONFIGURATION"
            );
        }

        const baseName = `${this.provider.stackName}-${id}`;

        this.dlq = new CdkQueue(this, "Dlq", {
            queueName: configuration.fifo === true ? `${baseName}-dlq.fifo` : `${baseName}-dlq`,
            // 14 days is the maximum, we want to keep these messages for as long as possible
            retentionPeriod: Duration.days(14),
            fifo: configuration.fifo,
            ...encryption,
        });

        this.queue = new CdkQueue(this, "Queue", {
            queueName: configuration.fifo === true ? `${baseName}.fifo` : `${baseName}`,
            visibilityTimeout: Duration.seconds(visibilityTimeout),
            deadLetterQueue: {
                maxReceiveCount: maxRetries,
                queue: this.dlq,
            },
            fifo: configuration.fifo,
            deliveryDelay: delay,
            contentBasedDeduplication: configuration.fifo,
            ...encryption,
        });

        const alarmEmail = configuration.alarm;
        if (alarmEmail !== undefined) {
            const alarmTopic = new Topic(this, "AlarmTopic", {
                topicName: `${this.provider.stackName}-${id}-dlq-alarm-topic`,
                displayName: `[Alert][${id}] There are failed jobs in the dead letter queue.`,
            });
            new Subscription(this, "AlarmTopicSubscription", {
                topic: alarmTopic,
                protocol: SubscriptionProtocol.EMAIL,
                endpoint: alarmEmail,
            });

            const alarm = new Alarm(this, "Alarm", {
                alarmName: `${this.provider.stackName}-${id}-dlq-alarm`,
                alarmDescription: "Alert triggered when there are failed jobs in the dead letter queue.",
                metric: new Metric({
                    namespace: "AWS/SQS",
                    metricName: "ApproximateNumberOfMessagesVisible",
                    dimensionsMap: {
                        QueueName: this.dlq.queueName,
                    },
                    statistic: "Sum",
                    period: Duration.minutes(1),
                }),
                evaluationPeriods: 1,
                // Alert as soon as we have 1 message in the DLQ
                threshold: 0,
                comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
            });
            alarm.addAlarmAction({
                bind(): AlarmActionConfig {
                    return { alarmActionArn: alarmTopic.topicArn };
                },
            });
        }

        // CloudFormation outputs
        this.queueArnOutput = new CfnOutput(this, "QueueArn", {
            description: `ARN of the "${id}" SQS queue.`,
            value: this.queue.queueArn,
        });
        this.queueUrlOutput = new CfnOutput(this, "QueueUrl", {
            description: `URL of the "${id}" SQS queue.`,
            value: this.queue.queueUrl,
        });
        this.dlqUrlOutput = new CfnOutput(this, "DlqUrl", {
            description: `URL of the "${id}" SQS dead letter queue.`,
            value: this.dlq.queueUrl,
        });

        this.appendFunctions();
    }