Making the WAF rule by CDK - amazon-web-services

I want to make the WAF as IP whitelist with wafv2
Currently My code is here
import { aws_wafv2 as wafv2 } from 'aws-cdk-lib';
const wafacl = new wafv2.CfnWebACL(this, "MyCfnWebAll",{
name: `ee-${targetEnv}-waf`,
scope: "REGIONAL",
defaultAction: {
allow:{
customRequestHandling: {
insertHeaders: [{
name: 'my_allow_name',
value: 'my_allow_value',
}],
},
}
},
visibilityConfig:{
cloudWatchMetricsEnabled: false,
metricName: 'metricName',
sampledRequestsEnabled: false
}
});
new wafv2.CfnWebACLAssociation(this, 'WebACLAssociation', {
webAclArn: wafacl.attrArn,
resourceArn: lb.loadBalancerArn
})
const cfnIPSet = new wafv2.CfnIPSet(this, 'MyCfnIPSet', {
addresses: ['23.186.72.133/32','143.32.1.45/32'],
ipAddressVersion: 'IPV4',
scope: 'REGIONAL',
description: 'description',
name: `ss-${targetEnv}-ipset`,
});
It can make the WAF ,association with the LoadBalancer and ipSet
However it lacks rules
I am searching the samples to code rule , however I could't find the good clue.
What I want to do is equivalent to this,
Add my own rules and rule groups
choose doesn't match the statement(NOT)
set IPset
Any help is appreciated.
For now, my reference is these.
https://docs.aws.amazon.com/cdk/api/v1/docs/#aws-cdk_aws-wafv2.CfnWebACL.RuleProperty.html
https://docs.aws.amazon.com/cdk/api/v1/docs/#aws-cdk_aws-wafv2.CfnWebACL.html
I made the code like this below.
making the rule and try to set this in rules of wafv2.CfnWebACL
const ruleProperty: wafv2.CfnWebACL.RuleProperty = {
name: 'name',
priority: 123,
statement: {
ipSetReferenceStatement: {
arn: cfnIPSet.attrArn
}
},
visibilityConfig: {
cloudWatchMetricsEnabled: false,
metricName: 'metricName',
sampledRequestsEnabled: false,
}
}
const wafacl = new wafv2.CfnWebACL(this, "MyCfnWebAll",{
name: `ss-${targetEnv}-waf`,
scope: "REGIONAL",
rules:[ruleProperty], ## add here
There comes the error like this
Resource handler returned message: "Error reason: You have used none or multiple values for a field that requi
res exactly one value., field: RULE, parameter: Rule (Service: Wafv2, Status Code: 400, Request ID: ce79fc3b-c
b96-4856-9d9f-12ea39407091, Extended Request ID: null)" (RequestToken: f2ef3c98-382f-1b21-2351-e3861e418623, H
andlerErrorCode: InvalidRequest)

Please use this as a reference to write the rules.
https://github.com/aws/aws-cdk/issues/6056#issuecomment-581583976
IPSet is written like this.
        {
name: "CustomAllowIpSetRule",
priority: 1,
statement: {
ipSetReferenceStatement: {
arn: "xxxxxxx"
},
},
action: { allow: {} },
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: "CustomAllowIpSetRule",
},
},

Related

ECS Fargate Service with Multiple Target Groups. CDK

Using the AWS CDK2 I would like to create an ECS Cluster, ALB and multiple Fargate services. The Fargate services will have a task with two containers (beta, primary). Am I on the right track or would you suggest I do this differently?
I have had a few errors while trying different things. This is the latest message.
AWS::ElasticLoadBalancingV2::ListenerRule Validation exception
I could use some advice on what I have built thus far.
export const createALBStack = ({
app_name,
app_props,
domain_name,
scope,
vpc,
}: {
app_name: string;
app_props: cdk.StackProps;
domain_name: string;
scope: cdk.App;
vpc: ec2.IVpc;
}) => {
const stack = new cdk.Stack(scope, app_name + '-LOADBALANCER', app_props);
// create a security group that allows all traffic from the same security group
const security_group = new ec2.SecurityGroup(stack, app_name + '-SHARED-SG', {
allowAllOutbound: true,
vpc,
});
security_group.connections.allowFrom(security_group, ec2.Port.allTraffic());
// security_group.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS Traffic');
// security group for HTTP public access
const public_http_security_group = new ec2.SecurityGroup(stack, app_name + '-PUBLIC-HTTP-SG', {
allowAllOutbound: true,
vpc,
});
public_http_security_group.connections.allowFromAnyIpv4(ec2.Port.tcp(80));
// DNS
const zone = route53.HostedZone.fromLookup(stack, app_name + '-ALB53-ZONE', {
domainName: domain_name,
});
const domain_certificate_arn = `arn:aws:acm:${app_props?.env?.region}:${app_props?.env?.account}:certificate/${certificate_identifier}`;
const certificate = acm.Certificate.fromCertificateArn(
stack,
app_name + '-CERTIFICATE',
domain_certificate_arn,
);
const alb = new loadBalancerV2.ApplicationLoadBalancer(stack, app_name + '-ALB', {
internetFacing: true,
loadBalancerName: app_name + '-ALB',
securityGroup: public_http_security_group,
vpc,
});
const https_listener = alb.addListener(app_name + '-ALB_LISTENER', {
certificates: [loadBalancerV2.ListenerCertificate.fromArn(certificate.certificateArn)],
// defaultTargetGroups: [],
open: true,
port: 443,
});
https_listener.addAction(app_name + '-ALB_DEFAULT_RESPONSE', {
action: loadBalancerV2.ListenerAction.fixedResponse(404, {
messageBody: 'SEON DEVELOPMENT 404',
}),
});
createHTTPSRedirect(app_name + '-ALB_HTTTPSRedirect', stack, alb);
// Add a Route 53 alias with the Load Balancer as the target
new route53.ARecord(stack, app_name + `-ALIAS_RECORD`, {
recordName: app_name + `-ALIAS_RECORD`,
target: route53.RecordTarget.fromAlias(new route53targets.LoadBalancerTarget(alb)),
ttl: cdk.Duration.seconds(60),
zone,
});
new cdk.CfnOutput(stack, app_name + 'HTTP-LISTENER-ARN', {
exportName: app_name + 'HTTP-LISTENER-ARN',
value: https_listener.listenerArn,
});
return {
alb,
https_listener,
security_group,
zone,
};
};
ECS Stack
export const createECSServiceStack = ({
alb,
app_props,
cluster,
containers,
https_listener,
scope,
security_group,
service_name,
service_params,
sub_domain,
task_params,
vpc,
zone,
}: {
alb: loadBalancerV2.ApplicationLoadBalancer;
app_props: cdk.StackProps;
cluster: ecs.Cluster;
containers: TaskDefContainer[];
https_listener: loadBalancerV2.ApplicationListener;
scope: cdk.App;
security_group: ec2.SecurityGroup;
service_name: string;
service_params: ServiceParams;
sub_domain: string;
task_params: FargateTaskDefinitionProps;
vpc: ec2.IVpc;
zone: route53.IHostedZone;
}) => {
const stack = new cdk.Stack(scope, service_name, app_props);
const task_role = new iam.Role(stack, service_name + '-taskrole', {
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
});
const task_definition = new ecs.FargateTaskDefinition(stack, service_name + '-TASKDEF', {
cpu: task_params.cpu,
family: service_name + '-TASKDEF',
memoryLimitMiB: task_params.memoryLimitMiB,
taskRole: task_role,
// runtimePlatform
});
const execution_role_policy = new iam.PolicyStatement({
actions: [
'ecr:GetAuthorizationToken',
'ecr:BatchCheckLayerAvailability',
'ecr:GetDownloadUrlForLayer',
'ecr:BatchGetImage',
'logs:CreateLogStream',
'logs:PutLogEvents',
'dynamodb:GetItem',
'dynamodb:UpdateItem',
'xray:PutTraceSegments',
],
effect: iam.Effect.ALLOW,
resources: ['*'],
});
task_definition.addToExecutionRolePolicy(execution_role_policy);
// can add more than one container to the task
const sourced_containers = containers.map(container => {
const containerPort = parseInt(container.environment.HOST_PORT);
const ecr_repo = sourceECR({ ecr_name: container.name + '-ecr', stack });
task_definition
.addContainer(container.name, {
environment: container.environment,
image: ecs.ContainerImage.fromEcrRepository(ecr_repo),
logging: new ecs.AwsLogDriver({ streamPrefix: container.name }),
})
.addPortMappings({
containerPort,
protocol: ecs.Protocol.TCP,
});
return {
...container,
ecr: ecr_repo,
};
});
const ecs_service = new ecs.FargateService(stack, service_name, {
assignPublicIp: true,
capacityProviderStrategies: [
{
base: 1,
capacityProvider: 'FARGATE',
weight: 1,
},
{
capacityProvider: 'FARGATE_SPOT',
weight: 1,
},
],
circuitBreaker: { rollback: true },
cluster,
desiredCount: service_params.desiredCount,
maxHealthyPercent: service_params.maxHealthyPercent,
minHealthyPercent: service_params.minHealthyPercent,
securityGroups: [security_group],
serviceName: service_name,
taskDefinition: task_definition,
});
sourced_containers.map((sourced_container, index) => {
const target_group = new loadBalancerV2.ApplicationTargetGroup(
stack,
sourced_container.name + '-tg',
{
deregistrationDelay: cdk.Duration.seconds(30),
healthCheck: {
healthyHttpCodes: '200,301,302',
healthyThresholdCount: 5,
interval: cdk.Duration.seconds(300),
path: sourced_container.health_check_url,
port: sourced_container.environment.HOST_PORT,
timeout: cdk.Duration.seconds(20),
unhealthyThresholdCount: 2,
},
port: 443,
protocol: loadBalancerV2.ApplicationProtocol.HTTPS,
stickinessCookieDuration: cdk.Duration.hours(1), // todo ?
targets: [
ecs_service.loadBalancerTarget({
containerName: sourced_container.name,
containerPort: parseInt(sourced_container.environment.HOST_PORT),
}),
],
vpc,
},
);
const target_rule = new loadBalancerV2.CfnListenerRule(
stack,
sourced_container.name + '-target-rule',
{
actions: [
{
targetGroupArn: target_group.targetGroupArn,
type: 'forward',
},
],
conditions: [
{
field: 'host-header',
pathPatternConfig: {
values: [sub_domain],
},
},
{
field: 'path-pattern',
pathPatternConfig: {
values: [sourced_container.url_path],
},
},
],
listenerArn: https_listener.listenerArn,
priority: service_params.priority + index,
},
);
});
const scaling = ecs_service.autoScaleTaskCount({ maxCapacity: 6 });
const cpu_utilization = ecs_service.metricCpuUtilization();
/*
* scale out when CPU utilization exceeds 50%
* increase scale out speed if CPU utilization exceeds 70%
* scale in again when CPU utilization falls below 10%.
*/
scaling.scaleOnMetric(service_name + '-ASCALE_CPU', {
adjustmentType: aws_applicationautoscaling.AdjustmentType.CHANGE_IN_CAPACITY,
metric: cpu_utilization,
scalingSteps: [
{ change: -1, upper: 10 },
{ change: +1, lower: 50 },
{ change: +3, lower: 70 },
],
});
const pipeline = configurePipeline({
cluster,
service: ecs_service,
service_name,
sourced_containers,
stack,
});
new route53.ARecord(stack, service_name + `-ALIAS_RECORD_API`, {
recordName: sub_domain,
target: route53.RecordTarget.fromAlias(new route53targets.LoadBalancerTarget(alb)),
zone,
});
return {
ecs_service,
};
};
I suggest checking CDK Construct library for higher-level ECS Constructs for working code samples. They also have many TypeScript Examples. You might want to check the fargate-application-load-balanced-service sample code as a starting point.
Please point to any issues with a minimal reproducible example. Because now, it is almost impossible to understand what is wrong with your code. And why do you write need so much code instead of using ECS patterns?

AWS Glue - finding schema reference for table

Using AWS CDK 2 for creating schemas and tables, I seem to have problems linking the schemaReference
const schema = new glue.CfnSchema(this, "User", {
compatibility: "NONE",
dataFormat: "JSON",
name: "user",
schemaDefinition: JSON.stringify(userSchema),
});
new glue.CfnTable(
this,
"UserTable",
{
catalogId: this.account,
databaseName: "my_db",
tableInput: {
name: "users",
tableType: "EXTERNAL_TABLE",
storageDescriptor: {
location: "my_db.public.users",
schemaReference: schema,
},
parameters: {
classification: "postgresql",
typeOfData: "table",
connectionName: "rds_conn",
},
},
}
);
It seems like I'd expect schemaReference to be able to use the Cfn output in some way? I can only get this working by hard coding a schemaReference object with a schemaVersionId that I find in the console.
My solution was to lock the schema version in its definition, then to reference the schema by name. Example
new glue.CfnSchema(this, "User", {
name: "user",
// ...
checkpointVersion: {
versionNumber: 1,
},
});
new glue.CfnTable(
this,
"UserTable",
{
// ...
tableInput: {
// ...
storageDescriptor: {
// ...
schemaReference: {
schemaId: {
registryName: "default-registry",
schemaName: "user",
},
schemaVersionNumber: 1,
},
},
},
}
);
Though verbose, it has the advantage of being portable across stacks.

CloudFront not respecting Behavior Paths -> Origins

I'm using AWS CDK to construct a CloudFront Ingress for various other microservices. It seems super inconsistent in when it works, normally only the first request, then it completely fails.
I'm making sure to invalidate the entire distribution and making sure everything has updated before I test any changes, but I'm having no luck.
My goal is:
/graphql -> API Gateway
/app/* -> S3 Website Bucket (App Static SPA)
* (Default) -> S3 Website Bucket (Website Static SPA)
I'm using a CloudFront Function to rewrite requests for the non-default origins to remove the prefix:
/* eslint-disable */
function handler(event) {
var request = event.request;
request.uri = request.uri.replace(/^\/[^/]*\//, '/');
return request;
}
Which, when testing via Console, works as expected (/app/login becomes /login, etc.)
However what seems to happen is the origins aren’t respected after maybe 1 request, sometimes 0, and it defaults to the * behavior.
My main ingress CDK Stack is defined using this:
import { CloudFrontAllowedMethods, CloudFrontWebDistribution, SSLMethod, ViewerCertificate, SecurityPolicyProtocol, FunctionEventType, Function, FunctionCode, ViewerProtocolPolicy, Behavior, OriginProtocolPolicy, PriceClass } from '#aws-cdk/aws-cloudfront';
import { DnsValidatedCertificate } from '#aws-cdk/aws-certificatemanager';
import { ARecord, HostedZone, RecordTarget } from '#aws-cdk/aws-route53';
import { CloudFrontTarget } from '#aws-cdk/aws-route53-targets';
import { Metric } from '#aws-cdk/aws-cloudwatch';
import { Construct, Duration } from '#aws-cdk/core';
import { Aws, Stack } from '#aws-cdk/core';
import { createGraphQL } from '../graphql';
import { createSite } from '../site';
import { createApp } from '../app';
import type { StackProps } from './types';
export async function createIngress(scope: Construct, id: string, props: StackProps) {
const stack = new Stack(scope, `${id}-${props.environment}`, props);
const serviceProps = {
environment: props.environment,
root: props.root,
core: props.core,
};
// Create the various Services for the Ingress.
const app = await createApp(stack, 'app-resources', serviceProps);
const site = await createSite(stack, 'site-resources', serviceProps);
const graphql = await createGraphQL(stack, 'graphql-resources', serviceProps);
// Fetch the DNS Zone.
const zone = HostedZone.fromLookup(stack, 'ingress-zone', {
domainName: props.domainName,
});
// Ensure there is a Certificate and fetch the ARN.
const { certificateArn } = new DnsValidatedCertificate(stack, 'ingress-cert', {
domainName: props.domainName,
hostedZone: zone,
region: 'us-east-1', // Cloudfront only checks this region for certificates.
});
// Fetch the Viewer Certificate.
const viewerCertificate = ViewerCertificate.fromAcmCertificate({
certificateArn: certificateArn,
env: {
region: Aws.REGION,
account: Aws.ACCOUNT_ID,
},
node: stack.node,
stack,
metricDaysToExpiry: () => new Metric({
namespace: 'TLS Viewer Certificate Validity',
metricName: 'TLS Viewer Certificate Expired',
}),
}, {
sslMethod: SSLMethod.SNI,
securityPolicy: SecurityPolicyProtocol.TLS_V1_2_2021,
aliases: [props.domainName],
});
const rewriteFunction = new Function(stack, 'rewrite-function', {
functionName: 'origin-rewrite',
comment: 'Rewrites Microservice paths for origin',
code: FunctionCode.fromFile({
filePath: './functions/rewrite.js',
}),
});
// Create the CloudFront Ingress.
const distribution = new CloudFrontWebDistribution(stack, 'ingress-distribution', {
comment: 'Ingress',
viewerCertificate,
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
priceClass: PriceClass.PRICE_CLASS_ALL,
defaultRootObject: 'index.html',
originConfigs: [{
/**
* GraphQL
*/
customOriginSource: {
domainName: graphql.url,
originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY,
},
behaviors: [{
pathPattern: 'graphql',
defaultTtl: Duration.seconds(0),
minTtl: Duration.seconds(0),
maxTtl: Duration.seconds(0),
compress: true,
allowedMethods: CloudFrontAllowedMethods.GET_HEAD,
forwardedValues: {
queryString: true,
cookies: { forward: 'all' },
},
functionAssociations: [{
function: rewriteFunction,
eventType: FunctionEventType.VIEWER_REQUEST,
}],
}],
}, {
/**
* App
*/
customOriginSource: {
domainName: app.staticSite.bucket.bucketWebsiteDomainName,
originProtocolPolicy: OriginProtocolPolicy.HTTP_ONLY,
},
behaviors: [{
pathPattern: 'app/*',
compress: true,
allowedMethods: CloudFrontAllowedMethods.GET_HEAD,
forwardedValues: {
queryString: true,
cookies: { forward: 'all' },
},
functionAssociations: [{
function: rewriteFunction,
eventType: FunctionEventType.VIEWER_REQUEST,
}],
}],
}, {
/**
* Website (Fallback)
*/
customOriginSource: {
domainName: site.staticSite.bucket.bucketWebsiteDomainName,
originProtocolPolicy: OriginProtocolPolicy.HTTP_ONLY,
},
behaviors: [{
isDefaultBehavior: true,
compress: true,
allowedMethods: CloudFrontAllowedMethods.GET_HEAD,
forwardedValues: {
queryString: true,
cookies: { forward: 'all' },
},
}],
}],
});
// Create a DNS Record for the CloudFront Ingress.
new ARecord(stack, 'ingress-record', {
recordName: props.domainName,
target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),
zone,
});
return { stack, app, site, graphql };
}
Turns out, the response headers from S3 & API Gateway needed to be changed to account for Cache-Control - using a Lambda#Edge to modify them to not be cached, as well as the ttl configuration, solved the issue.

AWS CDK event bridge and api gateway AWS example does not work

I am following the instructions here to setup an event bridge: https://eventbus-cdk.workshop.aws/en/04-api-gateway-service-integrations/01-rest-api/rest-apis.html
Based on the error message, the error is coming from this line of code: languageResource.addMethod("POST", new apigw.Integration({
I am not sure what is causing this issue because this is an example given by AWS and should work, but it does not.
I can build it but it fails with the following error on cdk deploy:
CREATE_FAILED | AWS::ApiGateway::Method | MyRestAPI/Default/{language}/POST (MyRestAPIlanguagePOSTB787D51A) Invalid Resource identifier specified (Service: AmazonApiGateway; Status Code: 404; Error Code: NotFoundException;
The code is below:
const myLambda = new lambda.Function(this, "MyEventProcessor", {
code: new lambda.InlineCode("def main(event, context):\n\tprint(event)\n\treturn {'statusCode': 200, 'body': 'Hello, World'}"),
handler: "index.main",
runtime: lambda.Runtime.PYTHON_3_7
})
const bus = new events.EventBus(this, `pwm-${this.stage}-MdpEventBus`)
new cdk.CfnOutput(this, "PwmMdpEventBus", {value: bus.eventBusName})
new events.Rule(this, `PwmMdpEventBusRule`, {
eventBus: bus,
eventPattern: {source: [`com.amazon.alexa.english`]},
targets: [new targets.LambdaFunction(myLambda)]
})
const apigwRole = new iam.Role(this, "MYAPIGWRole", {
assumedBy: new iam.ServicePrincipal("apigateway"),
inlinePolicies: {
"putEvents": new iam.PolicyDocument({
statements: [new iam.PolicyStatement({
actions: ["events:PutEvents"],
resources: [bus.eventBusArn]
})]
})
}
});
const options = {
credentialsRole: apigwRole,
requestParameters: {
"integration.request.header.X-Amz-Target": "'AWSEvents.PutEvents'",
"integration.request.header.Content-Type": "'application/x-amz-json-1.1'"
},
requestTemplates: {
"application/json": `#set($language=$input.params('language'))\n{"Entries": [{"Source": "com.amazon.alexa.$language", "Detail": "$util.escapeJavaScript($input.body)", "Resources": ["resource1", "resource2"], "DetailType": "myDetailType", "EventBusName": "${bus.eventBusName}"}]}`
},
integrationResponses: [{
statusCode: "200",
responseTemplates: {
"application/json": ""
}
}]
}
const myRestAPI = new apigw.RestApi(this, "MyRestAPI");
const languageResource = myRestAPI.root.addResource("{language}");
languageResource.addMethod("POST", new apigw.Integration({
type: apigw.IntegrationType.AWS,
uri: `arn:aws:apigateway:${cdk.Aws.REGION}:events:path//`,
integrationHttpMethod: "POST",
options: options,
}),
{
methodResponses: [{
statusCode: "200"
}],
requestModels: {"application/json": model.getModel(this, myRestAPI) },
requestValidator: new apigw.RequestValidator(this, "myValidator", {
restApi: myRestAPI,
validateRequestBody: true
})
})
In the AWS example, they are encapsulating your code inside
export class MyCdkAppStack extends cdk.Stack {
...
}
Are you missing that encapsulation? I noticed your sample code didn't include it. Because when you execute const myRestAPI = new apigw.RestApi(this, "MyRestAPI"); the this should refer to the MyCdkAppStack instance.

cdk api gateway route53 lambda custom domain name not working

Similar questions has been made but none of them were able to help me fix the issue that I'm facing.
What I'm trying to do is to connect my api-gateway/lamnda function with a custom domain name and for some reason when calling the api/domain is not returning what I expected.
cdk version: 1.53.0
const lambdaFunction = new lambda.Function(this, 'LambdaApi', {
functionName: 'lambda-api',
handler: 'lambda.handler',
runtime: lambda.Runtime.NODEJS_12_X,
code: new lambda.AssetCode(join(process.cwd(), '../api/dist')),
memorySize: 128,
timeout: cdk.Duration.seconds(5),
})
const zone = route53.HostedZone.fromLookup(scope, 'Zone', {
'example.com',
privateZone: false,
})
const certificate = certificatemanager.Certificate.fromCertificateArn(
this,
'Certificate',
CERT_ARN,
)
const api = new apigateway.LambdaRestApi(this, 'LambdaApiGateway', {
handler: lambdaFunction,
proxy: true,
endpointTypes: [apigateway.EndpointType.EDGE],
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS,
},
options: {
restApiName: 'gateway-api',
domainName: {
domainName: 'api.example.com',
certificate,
},
deployOptions: {
stageName: 'prod',
metricsEnabled: true,
loggingLevel: apigateway.MethodLoggingLevel.INFO,
dataTraceEnabled: true,
},
},
})
new route53.ARecord(this, 'CustomDomainAliasRecord', {
zone,
recordName: 'api',
target: route53.RecordTarget.fromAlias(new targets.ApiGateway(api)),
})
The deployment process works fine, a ARecord is created on route53 that is pointing to the api-gateway domain name, the api mappings is created as well pointing to prod as specified on stageName but when calling the domain name it doesn’t work but when calling the api-gateway endpoint it does.
api.example.com/ping returns healthy
{id}.execute-api.us-east-1.amazonaws.com/prod/ping returns the current date
Been researching but I'm not able to find out why the api.example.com/ping is not working
For the most part we've done what you are doing there, but after the zone and certificate creation we've got something like this:
const customDomain = new DomainName(this, 'customDomain', {
domainName: 'api.example.com',
certificate: certificate,
endpointType: EndpointType.REGIONAL // yours may be Edge here
})
We also use basePathMapping so we don't have to use "dev|stg|prod" on the end of the domain.
new BasePathMapping(this, 'CustomBasePathMapping', {
domainName: customDomain,
restApi: api // again yours may differ here
})
I fixed with cloudfront distribution, here is the code.
const api = new apigateway.LambdaRestApi(
this,
'lambda-api-gateway',
{
handler: lambdaFunction,
proxy: true,
endpointTypes: [apigateway.EndpointType.EDGE],
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS,
allowMethods: apigateway.Cors.ALL_METHODS,
},
options: {
restApiName: 'gateway-api',
domainName: {
domainName,
certificate,
},
deployOptions: {
stageName: props.stageName,
metricsEnabled: true,
loggingLevel: apigateway.MethodLoggingLevel.INFO,
dataTraceEnabled: true,
},
},
},
)
const distribution = new cloudfront.CloudFrontWebDistribution(
this,
'api-cloudfront-distribution',
{
defaultRootObject: '/',
originConfigs: [
{
customOriginSource: {
domainName: `${api.restApiId}.execute-api.${this.region}.${this.urlSuffix}`,
},
originPath: `/${props.stageName}`,
behaviors: [
{
allowedMethods: cloudfront.CloudFrontAllowedMethods.ALL,
isDefaultBehavior: true,
forwardedValues: {
cookies: {
forward: 'all',
},
queryString: true,
},
},
],
},
],
enableIpV6: true,
viewerCertificate: cloudfront.ViewerCertificate.fromAcmCertificate(
certificate,
{
aliases: [domainName],
securityPolicy: cloudfront.SecurityPolicyProtocol.TLS_V1,
sslMethod: cloudfront.SSLMethod.SNI,
},
),
},
)
const zone = zoneFromLookUp(this, props.zoneDomainName)
const target = route53.RecordTarget.fromAlias(
new targets.CloudFrontTarget(distribution),
)
new route53.ARecord(this, 'arecord-api', {
zone,
recordName: domainName,
target,
})