import { Config, asset } from '@pulumi/pulumi'; import { acm, apigateway, cloudfront, iam, lambda, route53, s3 } from '@pulumi/aws'; import { RestAPI } from '@pulumi/aws-apigateway'; const config = new Config(); // Bucket Config const bucketName = config.require('bucketName'); const bucket = new s3.BucketV2('captura-backend-bucket', { name: bucketName, }); const bucketOwnershipControls = new s3.BucketOwnershipControls('captura-backend-bucket-ownership-control', { bucket: bucket.id, rule: { objectOwnership: "BucketOwnerPreferred", }, }); const bucketPublicAccessBlock = new s3.BucketPublicAccessBlock('captura-backend-bucket-access-block', { bucket: bucket.id, blockPublicAcls: false, blockPublicPolicy: false, ignorePublicAcls: false, restrictPublicBuckets: false, }); new s3.BucketLifecycleConfigurationV2('captura-backend-bucket-lifecycle', { bucket: bucket.id, rules: [{ id: 'expireAfter30Days', status: 'Enabled', expiration: { days: 30, }, }], }); new s3.BucketAclV2('captura-backend-bucket-acl', { bucket: bucket.id, acl: 'public-read', }, { dependsOn: [ bucketOwnershipControls, bucketPublicAccessBlock, ], }); const bucketCertificate = new acm.Certificate(`captura-backend-bucket-certificate`, { domainName: config.require('bucketDomain'), validationMethod: 'DNS' }); const bucketValidationRecord = new route53.Record(`captura-backend-bucket-certificate-validation-record`, { allowOverwrite: true, name: bucketCertificate.domainValidationOptions[0].resourceRecordName, records: [bucketCertificate.domainValidationOptions[0].resourceRecordValue], type: bucketCertificate.domainValidationOptions[0].resourceRecordType, ttl: 60, zoneId: config.require('zoneId') }); const bucketCertificateValidation = new acm.CertificateValidation(`captura-backend-bucket-certificate-validation`, { certificateArn: bucketCertificate.arn, validationRecordFqdns: [bucketValidationRecord.fqdn] }); const bucketCdn = new cloudfront.Distribution('captura-backend-bucket-cdn', { comment: 'captura-backend', enabled: true, isIpv6Enabled: true, defaultRootObject: 'index.html', aliases: [ config.require('bucketDomain') ], defaultCacheBehavior: { targetOriginId: bucket.arn, viewerProtocolPolicy: 'redirect-to-https', allowedMethods: [ 'GET', 'HEAD', 'OPTIONS' ], cachedMethods: [ 'GET', 'HEAD', 'OPTIONS' ], forwardedValues: { queryString: false, cookies: { forward: 'none' }, headers: [ 'Origin' ] }, compress: true, minTtl: 0, defaultTtl: 3600, maxTtl: 86400 }, customErrorResponses: [ { errorCode: 404, errorCachingMinTtl: 300, responseCode: 200, responsePagePath: '/index.html' } ], origins: [{ domainName: bucket.bucketRegionalDomainName, originId: bucket.arn }], restrictions: { geoRestriction: { restrictionType: 'none' } }, viewerCertificate: { acmCertificateArn: bucketCertificateValidation.certificateArn, sslSupportMethod: 'sni-only', minimumProtocolVersion: 'TLSv1.2_2021' } }); new route53.Record('captura-backend-bucket-domain', { zoneId: config.require('zoneId'), name: config.require('bucketDomain'), type: 'A', aliases: [{ name: bucketCdn.domainName, zoneId: bucketCdn.hostedZoneId, evaluateTargetHealth: true }] }); // API Config const lambdaRole = new iam.Role('captura-backend-lambda-role', { assumeRolePolicy: JSON.stringify({ Version: '2012-10-17', Statement: [ { Action: 'sts:AssumeRole', Principal: { Service: 'lambda.amazonaws.com', }, Effect: 'Allow', Sid: '', }, ], }), }); new iam.RolePolicy('captura-backend-lambda-s3-policy', { role: lambdaRole.id, policy: { Version: '2012-10-17', Statement: [{ Effect: 'Allow', Action: 's3:PutObject', Resource: bucket.id.apply((id) => `arn:aws:s3:::${id}/*`), }, { Effect: 'Allow', Action: 's3:PutObjectAcl', Resource: bucket.id.apply((id) => `arn:aws:s3:::${id}/*`), }], }, }); // Allow the API Gateway service to invoke the Lambda function new iam.RolePolicy('captura-backend-lambda-invoke-policy', { role: lambdaRole.id, policy: { Version: '2012-10-17', Statement: [{ Effect: 'Allow', Action: 'lambda:InvokeFunction', Resource: '*', }], }, }); new iam.RolePolicy('captura-backend-lambda-logs-policy', { role: lambdaRole.id, policy: { Version: '2012-10-17', Statement: [{ Effect: 'Allow', Action: [ 'logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents' ], Resource: 'arn:aws:logs:*:*:*' }], }, }); const integrationLambda = new lambda.Function('captura-backend-lambda', { code: new asset.AssetArchive({ '.': new asset.FileArchive('./src'), }), role: lambdaRole.arn, handler: 'index.handler', runtime: 'nodejs18.x', environment: { variables: { S3_BUCKET: bucket.id, DOMAIN_NAME: config.require('bucketDomain') } }, role: lambdaRole.arn }); const api = new RestAPI('captura-backend-api', { routes: [{ path: '/', method: 'POST', eventHandler: integrationLambda, }], }); const apiCertificate = new acm.Certificate(`captura-backend-api-certificate`, { domainName: config.require('apiDomain'), validationMethod: 'DNS' }); const apiValidationRecord = new route53.Record(`captura-backend-api-certificate-validation-record`, { allowOverwrite: true, name: apiCertificate.domainValidationOptions[0].resourceRecordName, records: [apiCertificate.domainValidationOptions[0].resourceRecordValue], type: apiCertificate.domainValidationOptions[0].resourceRecordType, ttl: 60, zoneId: config.require('zoneId') }); new acm.CertificateValidation(`captura-backend-api-certificate-validation`, { certificateArn: apiCertificate.arn, validationRecordFqdns: [apiValidationRecord.fqdn] }); const domain = new apigateway.DomainName('captura-backend-api-domain', { certificateArn: apiCertificate.arn, domainName: config.require('apiDomain') }); new apigateway.BasePathMapping('captura-backend-base-path', { restApi: api.api.id, stageName: api.stage.stageName, domainName: domain.domainName }); new route53.Record('captura-backend-dns-record', { zoneId: config.require('zoneId'), name: config.require('apiDomain'), type: 'A', aliases: [{ name: domain.cloudfrontDomainName, zoneId: domain.cloudfrontZoneId, evaluateTargetHealth: true }], });