aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.65k stars 3.91k forks source link

(aws-apigateway): CDK will allow x-region lambda integration that APIG always fails for #17458

Open matdumsa opened 2 years ago

matdumsa commented 2 years ago

What is the problem?

In CDK Someone can model an application where an API Gateway lambda integration points to a lambda in a region different than the api gateway endpoint. APIG will even instantiate these, requests won't succeed though.

APIG will fail w/ Execution failed due to configuration error: Functions from 'us-west-2' are not reachable in this region ('us-east-1')

CDK should prevent users from shooting themselves in the foot wherever possible, build time error are easier to catch than runtime error.

Reproduction Steps

        let integration = new LambdaIntegration(lambda.Function.fromFunctionArn(this, "TenantFunction", `someARNFromADifferentRegion`));

        const api = new RestApi(this, `TenantAPI`, {
            defaultIntegration: integration
        });

What did you expect to happen?

CDK build to fail

What actually happened?

CDK Build Succeeds, CloudFormation synth succeeds

CDK CLI Version

1.131.0 (build 7560c79)

Framework Version

No response

Node.js Version

v12.22.5

OS

OSx

Language

Typescript

Language Version

No response

Other information

No response

otaviomacedo commented 2 years ago

Hi, @matdumsa

I tried to create a REST API in us-east-1 with an integration with a Lambda function is eu-west-1. The deployment failed with the following message:

Functions from 'eu-west-1' are not reachable in this region ('us-east-1') (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: xxx; Proxy: null)

Perhaps this validation hadn't been implemented in CloudFormation when you created the issue? In any case, this validation could be implemented in the CDK as well, improve the feedback loop. Marking this as a p2 feature request.

leantorres73 commented 2 years ago

This issue is still happening, it does not fail but it sets the region as the stack region instead of lambda region

jnd0 commented 6 months ago

Hello, this issue is still happening with the latest version (2.137.0).

In my case using aws-apigatewayv2

Here my code:


    const api = new HttpApiGateway(this, 'api', environment);

    const lambdaIntegration = Function.fromFunctionArn(this, 'searchLambda', 'lambdaArn');

    api.addIntegration('theintegration', [HttpMethod.GET], lambdaIntegration);

Error:

Functions from 'eu-central-1' are not reachable in this region ('eu-west-2') (Service: Lambda, Stat
us Code: 404
nmussy commented 6 months ago

I'll look into this 👍

nmussy commented 6 months ago

CloudFormation does indeed fail with the following setup, when the created Lambda is directly passed to the integration:

const app = new App();

const httpEnv = { region: "us-east-1" };
const lambdaEnv = { region: "eu-north-1" };

const httpStack = new Stack(app, "HttpStack", { env: httpEnv });
const httpApi = new HttpApi(httpStack, "HttpApi", {
  createDefaultStage: false,
});
const stage = new HttpStage(httpStack, "Stage", {
  httpApi,
  stageName: "dev",
  autoDeploy: true,
});

const lambdaStack = new Stack(app, "LambdaStack", { env: lambdaEnv });
const lambda = new LambdaFunction(lambdaStack, "Lambda", {
  functionName: "hello",
  code: AssetCode.fromInline(`exports.handler = async (_, { invokedFunctionArn }) => (
    { statusCode: 200, body: "Hello from " + invokedFunctionArn } 
  );`),
  handler: "index.handler",
  runtime: Runtime.NODEJS_20_X,
});

httpApi.addRoutes({
  path: "/hello",
  integration: new HttpLambdaIntegration("hello", lambda),
});

Resource handler returned message: "Functions from 'eu-north-1' are not reachable in this region ('us-east-1')

I am however able to set up a cross-region integration, by importing the Lambda via its ARN and manually setting up the permission:

const httpEnv = { region: "us-east-1" };
const lambdaEnv = { region: "eu-north-1" };

const httpStack = new Stack(app, "HttpStack", { env: httpEnv });
const httpApi = new HttpApi(httpStack, "HttpApi", {
  createDefaultStage: false,
});
const stage = new HttpStage(httpStack, "Stage", {
  httpApi,
  stageName: "dev",
  autoDeploy: true,
});

const lambdaStack = new Stack(app, "LambdaStack", { env: lambdaEnv });
const lambda = new LambdaFunction(lambdaStack, "Lambda", {
  functionName: "hello",
  code: AssetCode.fromInline(`exports.handler = async (_, { invokedFunctionArn }) => (
    { statusCode: 200, body: "Hello from " + invokedFunctionArn } 
  );`),
  handler: "index.handler",
  runtime: Runtime.NODEJS_20_X,
});

// Overly broad permissions for the sake of this example
// Please restrain by `sourceArn` in production
lambda.addPermission('permission', {
  principal: new ServicePrincipal('apigateway.amazonaws.com'),
});

const importedLambda = LambdaFunction.fromFunctionArn(
  httpStack,
  "LambdaFromArn",
  lambda.functionArn
);

httpApi.addRoutes({
  path: "/hello",
  integration: new HttpLambdaIntegration("hello", importedLambda),
});

Notice that the API Gateway in us-east-1 is able to invoke the Lambda in eu-north-1:

$ curl https://<api_id>.execute-api.us-east-1.amazonaws.com/dev/hello
Hello from arn:aws:lambda:eu-north-1:<account_id>:function:hello

This perhaps might even work with cross-account invocations.

I'm not sure how to feel about this. While I agree that this is probably a footgun in most cases, throwing an error here would be restraining a currently deployable and valid pattern.

jnd0 commented 6 months ago

Thanks @nmussy

I'm unable to replicate your setup without the error, even giving the permission manually:

Lambda stack (eu-central-1)

        const readDbLambda = new lambda.Function(this, 'readDbLambdar', {
            runtime: lambda.Runtime.NODEJS_20_X,
            handler: 'search.handler', // this is the entry point of your Lambda function
            code: lambda.Code.fromAsset(path.join(__dirname, '../src/api/lambda')), 
            timeout: cdk.Duration.seconds(10),
            vpc: defaultVpcEU, 
            securityGroups: [defaultSecurityGroup], 
            allowPublicSubnet: true,
            vpcSubnets: {
                subnetType: SubnetType.PUBLIC,
            },
        });

            readDbLambda.addPermission('permission', {
            principal: new ServicePrincipal('apigateway.amazonaws.com'),
        });

API GW Stack (eu-west-2)

    const api = new HttpApiGateway(this, 'ApiGw', environment);

    const searchLambda = Function.fromFunctionArn(this, 'searchLambda', 'arn:aws:lambda:eu-central-1:lambda-arn');

    api.addRoutes({
      path: '/search',
      methods: [HttpMethod.GET],
        integration: new HttpLambdaIntegration('search_integration', searchLambda),
    });
Resource handler returned message: "Functions from 'eu-central-1' are not reachable in this region ('eu-west-2') (Service: Lambda, Stat
us Code: 404,

I had to create a Lambda function in a different region (eu-central-1) for the following reasons:

While setting up this connection through the AWS console was easy and straightforward, I would much prefer to have all the infrastructure as code. This would automate the process for all my environments and eliminate manual configurations. Would that be something we can get support for in further versions?

nmussy commented 6 months ago

I'm not seeing the difference between our two examples. I initially thought it might be your deterministic string value vs. my Fn::Join preventing CloudFormation from running a check, but my deployment also works if I feed fromFunctionArn the Lambda ARN directly.

I think the recommended deployment method for multi-account/region apps is through CDK Pipelines, the README should cover what you need to get started. As I mentioned in the comment, my example is definitely not something to reproduce as is, especially the permission.

In regards to the CloudFormation error, I would open an issue in https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/

Sorry I can't be of more help 😅