cdk api gateway route53 lambda custom domain name not working - amazon-web-services

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,
})

Related

Making the WAF rule by CDK

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",
},
},

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.

define API mappings for apigateway in CDK

how can I deploy the following setting to my apigateway using cdk?
relevant part of my CDK-stack for my API:
....
const restApi = new apigateway.LambdaRestApi(this, "dyndns-api", {
handler: dyndnsLambda,
proxy: false,
domainName: {
securityPolicy: apigateway.SecurityPolicy.TLS_1_2,
domainName: siteDomain,
certificate: certificate,
endpointType: apigateway.EndpointType.REGIONAL
}
});
const methodResponse: apigateway.MethodResponse = {
statusCode: "200",
responseModels: {"application/json": apigateway.Model.EMPTY_MODEL}
}
const integrationResponse: apigateway.IntegrationResponse = {
statusCode: "200",
contentHandling: apigateway.ContentHandling.CONVERT_TO_TEXT
}
new route53.ARecord(this, "apiDNS", {
zone: zone,
recordName: siteDomain,
target: route53.RecordTarget.fromAlias(new route53Targets.ApiGateway(restApi)),
});
const requestTemplate = {
"execution_mode" : "$input.params('mode')",
"source_ip" : "$context.identity.sourceIp",
"set_hostname" : "$input.params('hostname')",
"validation_hash" : "$input.params('hash')"
}
const dnydnsIntegration = new apigateway.LambdaIntegration(dyndnsLambda, {
allowTestInvoke: true,
proxy: false,
integrationResponses: [integrationResponse],
passthroughBehavior: apigateway.PassthroughBehavior.WHEN_NO_TEMPLATES,
requestTemplates: { "application/json": JSON.stringify(requestTemplate) },
});
restApi.root.addMethod("GET", dnydnsIntegration, {
methodResponses: [methodResponse]
});
I know, that there is apigatewayv2.ApiMapping, but I have trouble implementing that into my stack. Also tried the deprecated class CfnApiMappingV2 with no success either.

CDK add mapping templates to LambdaIntegration

I have a Lambda function which can be accessed by api gateway.
How can I make CDK to add an Mapping template like in this Screenshot:
I tried multiple variants of this:
....
const restApi = new apigateway.LambdaRestApi(this, "dyndns-api", {
handler: dyndnsLambda,
proxy: false,
domainName: {
domainName: siteDomain,
certificate: certificate,
endpointType: apigateway.EndpointType.REGIONAL
}
});
const methodResponse: apigateway.MethodResponse = {
statusCode: "200",
responseModels: {"application/json": apigateway.Model.EMPTY_MODEL}
}
const requestTemplate = {
"execution_mode" : "$input.params('mode')",
"source_ip" : "$context.identity.sourceIp",
"set_hostname" : "$input.params('hostname')",
"validation_hash" : "$input.params('hash')"
}
const dnydnsIntegration = new apigateway.LambdaIntegration(dyndnsLambda, {
allowTestInvoke: true,
passthroughBehavior: apigateway.PassthroughBehavior.WHEN_NO_TEMPLATES,
requestTemplates: { "application/json": JSON.stringify(requestTemplate) },
});
restApi.root.addMethod("GET", dnydnsIntegration, {
methodResponses: [methodResponse]
});
But with not effect, it does not seem to arrive in the console, as I would expect.
solved it like this:
needed to add proxy setting to restApi, so the requestTemplate gets accepted. also needed integrationResponse.
const restApi = new apigateway.LambdaRestApi(this, "dyndns-api", {
handler: dyndnsLambda,
proxy: false,
domainName: {
securityPolicy: apigateway.SecurityPolicy.TLS_1_2,
domainName: siteDomain,
certificate: certificate,
endpointType: apigateway.EndpointType.REGIONAL
}
});
const methodResponse: apigateway.MethodResponse = {
statusCode: "200",
responseModels: {"application/json": apigateway.Model.EMPTY_MODEL}
}
const integrationResponse: apigateway.IntegrationResponse = {
statusCode: "200",
contentHandling: apigateway.ContentHandling.CONVERT_TO_TEXT
}
const requestTemplate = {
"execution_mode" : "$input.params('mode')",
"source_ip" : "$context.identity.sourceIp",
"set_hostname" : "$input.params('hostname')",
"validation_hash" : "$input.params('hash')"
}
const dnydnsIntegration = new apigateway.LambdaIntegration(dyndnsLambda, {
allowTestInvoke: true,
proxy: false,
integrationResponses: [integrationResponse],
passthroughBehavior: apigateway.PassthroughBehavior.WHEN_NO_TEMPLATES,
requestTemplates: { "application/json": JSON.stringify(requestTemplate) },
});
restApi.root.addMethod("GET", dnydnsIntegration, {
methodResponses: [methodResponse]
});

Migrate from apigateway to apigatewayv2

How to migrate from apigateway to apigatewayv2 using AWS-CDK?
Specifically: I am using LambdaRestApi and restApiId and deploymentStage from that resource.
// old
const apiGw = new apigateway.LambdaRestApi(this, 'MyAPI', {
handler: lambdaFrontend,
proxy: true,
binaryMediaTypes: ['*/*'],
});
// new
const apiGw2 = new apigateway.CfnApi(this as any, 'MyAPIV2', {
protocolType: "http",
target: lambdaFrontend.functionArn,
})
I am trying to get the OriginSource for CF like so:
const domainName = ${apiGw.restApiId}.execute-api.${this.region}.${this.urlSuffix};
First question: How can I retrieve the domainName with ApiGW2?
I also need the stageName. Currently I am retrieving it like so:
const originPath = '/' + apiGw.deploymentStage.stageName;
Second question: How can I retrieve the origin Path with ApiGW2?
Alternatively: Is there a better way to connect my ApiGW2 with CF?
const fecf = new cf.CloudFrontWebDistribution(this, "MyCF", {
originConfigs: [{
customOriginSource: {
domainName: `${apiGw.restApiId}.execute-api.${this.region}.${this.urlSuffix}`,
},
originPath: '/' + apiGw.deploymentStage.stageName,
...
}
This can now be solved quite easily since we have official documentation for this now.
If anybody out there wants to migrate to V2 now, this is the way:
const httpApiIntegration = new apigatewayv2Integrations.LambdaProxyIntegration({
handler: fn,
});
const httpApi = new apigatewayv2.HttpApi(this, "MyApiV2");
httpApi.addRoutes({
path: "/",
methods: [HttpMethod.ANY],
integration: httpApiIntegration,
});
new cloudfront.CloudFrontWebDistribution(this, "MyCf", {
defaultRootObject: "/",
originConfigs: [
{
customOriginSource: {
domainName: `${httpApi.httpApiId}.execute-api.${this.region}.${this.urlSuffix}`,
},
behaviors: [
{
isDefaultBehavior: true,
},
],
},
],
enableIpV6: true,
});
https://docs.aws.amazon.com/cdk/api/latest/docs/aws-apigatewayv2-readme.html
Steps:
create an integration (e.g. a lambda function), this comes from a dedicated package (apigatewayv2-integrations)
create an HttpApi, no options needed
New: Opposed to APIGWv1 you will have to add route handlers for your paths (httpApi.addRoutes).
Cloudfront config is very similar