Skip to content

Commit

Permalink
feat(cloudfront-origins): allow custom originPath for apigateway.Rest…
Browse files Browse the repository at this point in the history
…Api constructs (#24023)

### Current behavior

- the `apigateway.RestApi` splits the `restApi.url` and uses the stage name as `originPath` / "Origin path - optional" option in the CloudFront Origin by default

### Issue / Limitation

- when using multiple Behaviors in my CloudFront Distribution:
    - S3 as the default behavior
    - Regional API Gateway as additional behavior and CloudFront Origin

My API Gateway receives an extra `/<api-gateway-stage>/` in its event path and doesn't trigger the correct lambda integration.

The below CDK example reproduces the behavior mentioned above:

```typescript
import * as cdk from "aws-cdk-lib";

const meLambda = new cdk.aws_lambda_nodejs.NodejsFunction(
  this,
  "meLambda",
  {
    entry: "handlers/me.ts",
  }
);

const s3_bucket = new cdk.aws_s3.Bucket(this, "somebucket");

const api = new cdk.aws_apigateway.RestApi(this, "somerestapi", {
      endpointConfiguration: {
        types: [apigw.EndpointType.REGIONAL],
      },
      defaultCorsPreflightOptions: {
        allowOrigins: ["*"],
      },
});
api.root
  .addResource("me")
  .addMethod("GET", new apigw.LambdaIntegration(meLambda));

const apiOrigin = new cdk.aws_cloudfront_origins.RestApiOrigin(api);

const cdn = new cdk.aws_cloudfront.Distribution(this, "websitecdn", {
    defaultBehavior: {
      origin: new cdk.aws_cloudfront_origins.S3Origin(s3_bucket),
      allowedMethods: cdk.aws_cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
      viewerProtocolPolicy: cdk.aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
    },
    additionalBehaviors: {
      "prod/*": {  // <<<---- The "prod/" is added in the CloudFront Origin Path, NOT DESIRED IS THIS CASE
        origin: apiOrigin,
        allowedMethods: cdk.aws_cloudfront.AllowedMethods.ALLOW_ALL,
        cachePolicy: cdk.aws_cloudfront.CachePolicy.CACHING_DISABLED,
        viewerProtocolPolicy: cdk.aws_cloudfront.ViewerProtocolPolicy.HTTPS_ONLY,
      },
    },
});
```

The above will have the following URLs:

- `https://<api-gateway-url>/<api-gateway-stage>`
- `https://<cloudfront-distribution-url>/some-image.png` => Default Behavior, S3 Bucket
- `https://<cloudfront-distribution-url>/<api-gateway-stage>` => API Gateway forward request

Because the CloudFront Origin created by `RestApiOrigin` appends the `<api-gateway-stage>` in its CloudFront Origin "Origin Path  - optional" option, when using `https://<cloudfront-distribution-url>/<api-gateway-stage>/me` the API Gateway receives `/<api-gateway-stage>/<api-gateway-stage>/me` instead of `/<api-gateway-stage>/me`

Removing the "Origin Path  - optional" value, fixes the problem.

![00](https://user-images.githubusercontent.com/829902/216874002-06338400-9d88-4c98-b393-da56cc63be02.png)

![01](https://user-images.githubusercontent.com/829902/216874042-643ef152-b2ad-465e-bdda-8a98c68c6dcd.png)


### Workaround

- To fix the problem described above, I need to use a custom `cdk.aws_cloudfront_origins.HttpOrigin` construct


Replace the `apiOrigin` construct in the example above by:

```typescript
const apiOrigin = new origins.HttpOrigin(
  `${api.restApiId}.execute-api.${cdk.Aws.REGION}.${cdk.Aws.URL_SUFFIX}`,
  {
    originSslProtocols: [cloudfront.OriginSslPolicy.TLS_V1_2],
    protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY,
  }
);
```

### Solution

- This PR allows the customization of `apigateway.RestApi` by extending its props from `cloudfront.OriginProps`
- Allowing the consumer to pass a `props.originPath` to the `RestApiOrigin` class
  • Loading branch information
oieduardorabelo authored Mar 23, 2023
1 parent f1fa09c commit bc3db02
Show file tree
Hide file tree
Showing 20 changed files with 1,828 additions and 4 deletions.
11 changes: 9 additions & 2 deletions packages/@aws-cdk/aws-cloudfront-origins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ new cloudfront.Distribution(this, 'myDist', {
## From an API Gateway REST API

Origins can be created from an API Gateway REST API. It is recommended to use a
[regional API](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-endpoint-types.html) in this case.
[regional API](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-endpoint-types.html) in this case. The origin path will automatically be set as the stage name.

```ts
declare const api: apigateway.RestApi;
Expand All @@ -131,4 +131,11 @@ new cloudfront.Distribution(this, 'Distribution', {
});
```

The origin path will automatically be set as the stage name.
If you want to use a different origin path, you can specify it in the `originPath` property.

```ts
declare const api: apigateway.RestApi;
new cloudfront.Distribution(this, 'Distribution', {
defaultBehavior: { origin: new origins.RestApiOrigin(api, { originPath: '/custom-origin-path' }) },
});
```
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { validateSecondsInRangeOrUndefined } from './private/utils';
/**
* Properties for an Origin for an API Gateway REST API.
*/
export interface RestApiOriginProps extends cloudfront.OriginOptions {
export interface RestApiOriginProps extends cloudfront.OriginProps {
/**
* Specifies how long, in seconds, CloudFront waits for a response from the origin, also known as the origin response timeout.
* The valid range is from 1 to 180 seconds, inclusive.
Expand Down Expand Up @@ -40,7 +40,7 @@ export class RestApiOrigin extends cloudfront.OriginBase {
// Splitting on '/' gives: ['https', '', '<rest-api-id>.execute-api.<region>.amazonaws.com', '<stage>']
// The element at index 2 is the domain name, the element at index 3 is the stage name
super(cdk.Fn.select(2, cdk.Fn.split('/', restApi.url)), {
originPath: `/${cdk.Fn.select(3, cdk.Fn.split('/', restApi.url))}`,
originPath: props.originPath ?? `/${cdk.Fn.select(3, cdk.Fn.split('/', restApi.url))}`,
...props,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":"30.1.0"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "30.1.0",
"files": {
"caae80484d7f18b3e0e3e74d50e278f1f6a6f6ad5d264fa03a7fe46851fc2d8c": {
"source": {
"path": "integ-cloudfront-rest-api-origin-custom-origin-path.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "caae80484d7f18b3e0e3e74d50e278f1f6a6f6ad5d264fa03a7fe46851fc2d8c.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
{
"Resources": {
"RestApi0C43BF4B": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"EndpointConfiguration": {
"Types": [
"REGIONAL"
]
},
"Name": "RestApi"
}
},
"RestApiCloudWatchRoleE3ED6605": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
}
}
],
"Version": "2012-10-17"
},
"ManagedPolicyArns": [
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
]
]
}
]
},
"UpdateReplacePolicy": "Retain",
"DeletionPolicy": "Retain"
},
"RestApiAccount7C83CF5A": {
"Type": "AWS::ApiGateway::Account",
"Properties": {
"CloudWatchRoleArn": {
"Fn::GetAtt": [
"RestApiCloudWatchRoleE3ED6605",
"Arn"
]
}
},
"DependsOn": [
"RestApi0C43BF4B"
],
"UpdateReplacePolicy": "Retain",
"DeletionPolicy": "Retain"
},
"RestApiDeployment180EC50368af6d4b358eff290c08cb2de07c4042": {
"Type": "AWS::ApiGateway::Deployment",
"Properties": {
"RestApiId": {
"Ref": "RestApi0C43BF4B"
},
"Description": "Automatically created by the RestApi construct"
},
"DependsOn": [
"RestApiGET0F59260B"
]
},
"RestApiDeploymentStageprod3855DE66": {
"Type": "AWS::ApiGateway::Stage",
"Properties": {
"RestApiId": {
"Ref": "RestApi0C43BF4B"
},
"DeploymentId": {
"Ref": "RestApiDeployment180EC50368af6d4b358eff290c08cb2de07c4042"
},
"StageName": "prod"
},
"DependsOn": [
"RestApiAccount7C83CF5A"
]
},
"RestApiGET0F59260B": {
"Type": "AWS::ApiGateway::Method",
"Properties": {
"HttpMethod": "GET",
"ResourceId": {
"Fn::GetAtt": [
"RestApi0C43BF4B",
"RootResourceId"
]
},
"RestApiId": {
"Ref": "RestApi0C43BF4B"
},
"AuthorizationType": "NONE",
"Integration": {
"Type": "MOCK"
}
}
},
"Distribution830FAC52": {
"Type": "AWS::CloudFront::Distribution",
"Properties": {
"DistributionConfig": {
"DefaultCacheBehavior": {
"CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
"Compress": true,
"TargetOriginId": "integcloudfrontrestapiorigincustomoriginpathDistributionOrigin1635825EA",
"ViewerProtocolPolicy": "allow-all"
},
"Enabled": true,
"HttpVersion": "http2",
"IPV6Enabled": true,
"Origins": [
{
"CustomOriginConfig": {
"OriginProtocolPolicy": "https-only",
"OriginSSLProtocols": [
"TLSv1.2"
]
},
"DomainName": {
"Fn::Select": [
2,
{
"Fn::Split": [
"/",
{
"Fn::Join": [
"",
[
"https://",
{
"Ref": "RestApi0C43BF4B"
},
".execute-api.",
{
"Ref": "AWS::Region"
},
".",
{
"Ref": "AWS::URLSuffix"
},
"/",
{
"Ref": "RestApiDeploymentStageprod3855DE66"
},
"/"
]
]
}
]
}
]
},
"Id": "integcloudfrontrestapiorigincustomoriginpathDistributionOrigin1635825EA",
"OriginPath": ""
}
]
}
}
}
},
"Outputs": {
"RestApiEndpoint0551178A": {
"Value": {
"Fn::Join": [
"",
[
"https://",
{
"Ref": "RestApi0C43BF4B"
},
".execute-api.",
{
"Ref": "AWS::Region"
},
".",
{
"Ref": "AWS::URLSuffix"
},
"/",
{
"Ref": "RestApiDeploymentStageprod3855DE66"
},
"/"
]
]
}
}
},
"Parameters": {
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [
{
"Assert": {
"Fn::Not": [
{
"Fn::Contains": [
[
"1",
"2",
"3",
"4",
"5"
],
{
"Ref": "BootstrapVersion"
}
]
}
]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "30.1.0",
"testCases": {
"rest-api-origin-custom-origin-path/DefaultTest": {
"stacks": [
"integ-cloudfront-rest-api-origin-custom-origin-path"
],
"assertionStack": "rest-api-origin-custom-origin-path/DefaultTest/DeployAssert",
"assertionStackName": "restapiorigincustomoriginpathDefaultTestDeployAssertCD227A2A"
}
}
}
Loading

0 comments on commit bc3db02

Please sign in to comment.