define API mappings for apigateway in CDK - amazon-web-services

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.

Related

AWS API Gateway Export is exporting an older version

I created an API Gateway with AWS CDK with a resource and a request model:
const api = new apigateway.RestApi(this, 'MyApiGateway', {
deploy: true,
retainDeployments: false
});
const createDto = api.addModel('CreateUserDto', {
modelName: 'CreateUserDto',
schema: ...
});
const users = api.root.addResource('users');
users.addMethod(
'POST',
createUserLambdaIntegration, {
operationName: 'Create User',
requestModels: {
'application/json': createDto
},
methodResponses: [
{
statusCode: '201'
}
]
}
);
then I changed it by adding a response model and changing the name of the request model:
const api = new apigateway.RestApi(this, 'MyApiGateway', {
deploy: true,
retainDeployments: false
});
const createUserRequest = api.addModel('CreateUserRequest', {
modelName: 'CreateUserRequest',
schema: ...
});
const createUserResponse = api.addModel('CreateUserResponse', {
modelName: 'CreateUserResponse',
schema: ...
});
const users = api.root.addResource('users');
users.addMethod(
'POST',
createUserLambdaIntegration, {
operationName: 'Create User',
requestModels: {
'application/json': createUserRequest
},
methodResponses: [
{
statusCode: '201',
responseModels: {
'application/json': createUserResponse
}
}
]
}
);
this all works somewhat fine, as I am able to see the updated models on the API Gateway console, but when I try exporting the API as a Swagger JSON, it always exports the first version I deployed. I tried exporting it using both the API Gateway console and the AWS CLI with the exact same result.
What am I doing wrong?

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.

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]
});

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