Skip to content

Commit

Permalink
fix(apigatewayv2): incorrect arn function causing unwanted behavior (#…
Browse files Browse the repository at this point in the history
…33100)

fixes: #33218

### Reason for this change

In websocket APIs in `aws-apigatewayv2`, the function arnForExecuteApi has essentially the same exact functionality as a REST API, which is not appropriate for Websockets which are fundamentally different. 

The way I found this issue was I used arnForExecuteApi to put the arn of the api into an IAM Role. The reason for this was because I was trying to use an IAM authorizer, which from a React standpoint meant signing iam credentials from my Cognito id pool using Amplify lib. When doing this I used arnForExecuteApi from CDK to write the policy, I did not include any arguments, just the default. 

The issue was that this was not working. I spent time diving deep on the issue in case it was the method in which I was signing the credentials, since I was not too familiar with this process. I also got the assistance of a Cloud Support Engineer from AWS to try and identify the problem. 

Shout-out Mike Sacks.

The problem ended up being that that the resource policy was not correct. The policy that was generated by the function arnForExecuteApi was 

```
"Resource": "arn:aws:execute-api:us-east-1:acc:apiId/*/*/*",
```

This is because the function itself has 3 values, stage, method and path, so when all are left in default states, this indicates `all` or `*`. So when adding each value at default you get `/*/*/*`, 3 x /*. 

This is an issue because Websocket arns are not structured like this, and as it turns out **iam prevents access if you have too many wild cards than applicable**. This means the reason for getting access denied was not because of my signed url, but because having 1 extra /* means that you no longer have permissions. 

Websocket arns are structured like this 

```
arn:aws:execute-api:us-east-1:acc:apiId/*/$connect
```

In this example, * is the stage (this is what it shows on the console) and $connect is the `route`. 
You can add as many routes as you want, but the main ones by default are $connect, $disconnect and $default for no matching route. So if I want to grant an IAM role to have access to all routes and all stages, I would use this:

```
"Resource": "arn:aws:execute-api:us-east-1:acc:apiId/*/*",
```

Note 2 x /* instead of 3. 
Simply changing this by hand (deleting 2 characters) was enough to get the websocket to begin connecting via my signed url. 

### Description of changes

A re-write of the function for creating the arn. This is implemented as arnForExecuteApiV2, the original function has been changes to include the deprecated tag. This is to avoid making a breaking change since the new function only has 2 args and the original had 3.

```ts
  /**
   * Get the "execute-api" ARN.
   *
   * @default - The default behavior applies when no specific route, or stage is provided.
   * In this case, the ARN will cover all routes, and all stages of this API.
   * Specifically, if 'route' is not specified, it defaults to '*', representing all routes.
   * If 'stage' is not specified, it also defaults to '*', representing all stages.
   */
  public arnForExecuteApiV2(route?: string, stage?: string): string {
    return Stack.of(this).formatArn({
      service: 'execute-api',
      resource: this.apiId,
      arnFormat: ArnFormat.SLASH_RESOURCE_NAME,
      resourceName: `${stage ?? '*'}/${route ?? '*'}`,
    });
  }
```

I removed "Method" and "Path" entirely since these are not even appropriate to use as terms for websockets. I used Route instead.

### Description of how you validated changes

Updated Tests, there were 4 tests before:
* get arnForExecuteApi
  * is now using route, and intentionally uses a route with no `$` to check that the `$` is being added correctly. 
* get arnForExecuteApi with default values
  * is now using route
* get arnForExecuteApi with ANY method (removed)
  * doesn't make any sense here because "ANY" is not a term used with websockets, and method does not exist. Thus, removed this test
* throws when call arnForExecuteApi method with specifing a string that does not start with / for the path argument. (removed)
  * This test is checking for a specific format for path, which is not needed since the route can be anything. Also path does not exist.

This leaves 2 total tests now.

Added a new integ function, `integ.api-grant-invoke.ts` and used --update-on-failed with my personal account to bootstrap new snapshots to match. For this test I included an iam role and 2 arns, one with default settings and one with 
`.arnForExecuteApi('connect', 'prod')`
Intentionally left off the `$` to check that it's being added.

All tests and integ are passing.

### Checklist
- [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md)

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
IkeNefcy authored Jan 31, 2025
1 parent d32baf6 commit ffe9863
Show file tree
Hide file tree
Showing 12 changed files with 702 additions and 7 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
{
"Resources": {
"webocketapiD5DB5DB0": {
"Type": "AWS::ApiGatewayV2::Api",
"Properties": {
"Name": "webocket-api",
"ProtocolType": "WEBSOCKET",
"RouteSelectionExpression": "$request.body.action"
}
},
"websocketstageA39DAC37": {
"Type": "AWS::ApiGatewayV2::Stage",
"Properties": {
"ApiId": {
"Ref": "webocketapiD5DB5DB0"
},
"StageName": "prod"
}
},
"testiamrole05EFDD08": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
}
}
],
"Version": "2012-10-17"
},
"Policies": [
{
"PolicyDocument": {
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource": [
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":execute-api:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":",
{
"Ref": "webocketapiD5DB5DB0"
},
"/*/*"
]
]
},
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":execute-api:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":",
{
"Ref": "webocketapiD5DB5DB0"
},
"/prod/connect"
]
]
}
]
}
],
"Version": "2012-10-17"
},
"PolicyName": "webSocketAccess"
}
]
}
}
},
"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."
}
]
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit ffe9863

Please sign in to comment.