Skip to content

(appsync): Lambda authorizer permission is not scoped to appsync api arn #31550

@vruffer

Description

@vruffer

Describe the bug

When using a lambda authorizer with a GraphqlAPI, the cdk automatically creates the AWS::Lambda::Permission required for the AppSync API to invoke the lambda authorizer. It does not however add a SourceArn.

This conflicts with the control tower policy [CT.LAMBDA.PR.2], and it is in general good practice to scope permissions.

Regression Issue

  • Select this option if this issue appears to be a regression.

Last Known Working CDK Version

No response

Expected Behavior

I expected that the AWS::Lambda::Permission was scoped to only allow the "parent" AppSync API to invoke the authorizer.

Current Behavior

The CDK outputs a AWS:Lambda::Permission without a SourceArn property.

Reproduction Steps

  1. Add a graphql api
  2. Add a lambda function
  3. Set defaultAuthorization (or additionalAuthorizationTypes it doesn't matter) to aws_appsync.AuthorizationType.LAMBDA and point to the created lambda function
  4. Inspect the created AWS::Lambda::Permission and notice that SourceArn is missing.
const authorizer = new aws_lambda_nodejs.NodejsFunction(
  this,
  'authorizerLambda',
  {},
);

new aws_appsync.GraphqlApi(this, 'api', {
  name: 'api',
  authorizationConfig: {
    defaultAuthorization: {
      authorizationType: aws_appsync.AuthorizationType.LAMBDA,
      lambdaAuthorizerConfig: {
        handler: authorizer,
      },
    },
  },
});

Possible Solution

Inspecting the aws_appsync package reveals that appsync adds this permission when discovering an authorizationType === AuthorizationType.LAMBDA.

The fix should be as simple as adding sourceArn: this.arn, but I don't have the time currently to setup local development and create a pull request.

Additional Information/Context

A workaround for now is to modify the permission with an Aspect:

export class AppsyncAuthorizerFixer implements IAspect {
  appsyncArn: string;
  constructor(appsyncArn: string) {
    this.appsyncArn = appsyncArn;
  }

  public visit(node: IConstruct): void {
    if (
      node instanceof aws_lambda.CfnPermission &&
      node.principal === 'appsync.amazonaws.com' &&
      !node.sourceArn
    ) {
      // The metadata is the only non-resolved value I could find that
      // contains information about which lambda function the permission is tied to
      const cdkPath = node.getMetadata('aws:cdk:path') as unknown;
      if (!cdkPath) {
        Annotations.of(node).addError('Permission metadata not present');
        return;
      }
      if (
        cdkPath.includes(`${idOfLambdaAuthorizer}/${idOfAppsyncApi}-appsync`)
      ) {
        console.log(
          `Found 'AWS::Lambda::Permission' for appsync authorizer with principal = 'appsync.amazonaws.com' and no sourceArn. Adding appsync arn...`,
          cdkPath,
        );

        node.addPropertyOverride('SourceArn', this.appsyncArn);
      }
    }
  }
}

CDK CLI Version

2.159.1 (build c66f4e3)

Framework Version

No response

Node.js Version

v20.13.1

OS

MacOS 14.6.1 (Sonoma)

Language

TypeScript

Language Version

Typescript (5.6.2)

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions