8000 bug: API Gateway Lambda Invoke Deletes Stage Name from Request · Issue #12295 · localstack/localstack · GitHub
[go: up one dir, main page]

Skip to content
bug: API Gateway Lambda Invoke Deletes Stage Name from Request #12295
Closed
@hubbcaps

Description

@hubbcaps

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

With Localstack's implementation of API Gateway V1 REST API with a Lambda Integration, any request forwarded to the lambda will strip out the Stage Name from the request path to the lambda.

Expected Behavior

Within AWS, if you create a base path mapping for a custom domain using the API to leave the values of base path and stage blank, AWS automatically looks for the stage name as the first part of the path and properly forwards to a stage. This should be emulated in Localstack. Even without base path mapping, localstack is currently stripping the request URL if hitting the execute URL directly.

How are you starting LocalStack?

With a docker-compose file

Steps To Reproduce

Start up localstack using Docker Compose:

version: "3.8"

services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME:-localstack-main}"
    image: localstack/localstack-pro
    ports:
      - "127.0.0.1:4566:4566"            # LocalStack Gateway
      - "127.0.0.1:4510-4559:4510-4559"  # external services port range
    environment:
      # LocalStack configuration: https://docs.localstack.cloud/references/configuration/
      - DEBUG=1
      - LOCALSTACK_AUTH_TOKEN=${LOCALSTACK_AUTH_TOKEN- }
    volumes:
      - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"
    env_file:
      - .env

Use Terraform to setup an API Gateway integrating with a lambda based on a Hot Reload directory, base path mapping should have blank stage name and blank base path so that API Gateway will try to determine the stage base on the request path:

resource "aws_api_gateway_rest_api" "xxx-local" {
  name = "xxx-local"

  endpoint_configuration {
    types = ["REGIONAL"]
  }

  tags = {
    "_custom_id_" = "local"
  }
}

resource "aws_api_gateway_resource" "xxx-local" {
  parent_id   = aws_api_gateway_rest_api.xxx-local.root_resource_id
  rest_api_id = aws_api_gateway_rest_api.xxx-local.id
  path_part   = "{proxy+}"
}

resource "aws_api_gateway_method" "xxx-local" {
  rest_api_id      = aws_api_gateway_rest_api.xxx-local.id
  resource_id      = aws_api_gateway_resource.xxx-local.id
  http_method      = "ANY"
  authorization    = "NONE"
  api_key_required = false
}

resource "aws_api_gateway_stage" "loyalty-api-stage" {
  deployment_id = aws_api_gateway_deployment.xxx-local.id
  rest_api_id   = aws_api_gateway_rest_api.xxx-local.id
  stage_name    = "loyalty-api"

  variables = {
    function = "xxx-local"
  }
}

resource "aws_api_gateway_method_settings" "loyalty-api-stage-settings" {
  rest_api_id = aws_api_gateway_rest_api.xxx-local.id
  stage_name  = aws_api_gateway_stage.xxx-api-stage.stage_name
  method_path = "*/*"

  settings {
    metrics_enabled = true
    logging_level   = "INFO"
  }
}

resource "aws_api_gateway_integration" "loyalty-api" {
  rest_api_id = aws_api_gateway_rest_api.xxx-local.id
  resource_id = aws_api_gateway_resource.xxx-local.id
  http_method = aws_api_gateway_method.xxx-local.http_method

  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:000000000000:function:$${stageVariables.function}/invocations"
}

resource "aws_api_gateway_deployment" "xxx-local" {
  rest_api_id = aws_api_gateway_rest_api.xxx-local.id

  triggers = {
    redeployment = sha1(jsonencode([
      aws_api_gateway_resource.xxx-local.id,
      aws_api_gateway_method.xxx-local.id,
      aws_api_gateway_integration.xxx-api.id,
      aws_api_gateway_rest_api.xxx-local.body
    ]))
  }

  lifecycle {
    create_before_destroy = true
  }

  depends_on = [
    aws_api_gateway_integration.loyalty-api
  ]
}

resource "aws_api_gateway_domain_name" "xxx-local" {
  domain_name = "local"

  endpoint_configuration {
    types = ["REGIONAL"]
  }
}

resource "aws_api_gateway_base_path_mapping" "xxx-api" {
  api_id      = aws_api_gateway_rest_api.xxx-local.id
  domain_name = aws_api_gateway_domain_name.xxx-local.domain_name
}

Invoke via the localhost:4566/loyalty-api/users/v1/user endpoint, request comes through correctly to handlers.parse, but sometime after up to docker_runtime_exe, loyalty-api, the stage name, gets stripped from the request that is passed through to the lambda, causing a routing error since our FastAPI is expecting that stage name to determine which app flow to execute.

This happens even if calling the execute URL directly, not just the custom domain that is base path mapped

2025-02-20T21:26:31.796 DEBUG --- [et.reactor-9] l.s.a.n.execute_api.router : APIGW v1 Endpoint called
2025-02-20T21:26:31.796 DEBUG --- [et.reactor-9] l.s.a.n.e.handlers.parse   : Initializing $context='{'accountId': '000000000000', 'apiId': 'local', 'deploymentId': 'vgqdn5jxlw', 'domainName': 'local.execute-api.localhost.localstack.cloud:4566', 'domainPrefix': 'local', 'extendedRequestId': '1e29f4c4', 'httpMethod': 'GET', 'identity': {'accountId': None, 'accessKey': None, 'caller': None, 'cognitoAuthenticationProvider': None, 'cognitoAuthenticationType': None, 'cognitoIdentityId': None, 'cognitoIdentityPoolId': None, 'principalOrgId': None, 'sourceIp': '127.0.0.1', 'user': None, 'userAgent': None, 'userArn': None}, 'path': '/loyalty-api/users/v1/user', 'protocol': 'HTTP/1.1', 'requestId': '1ef0094f-c726-42c0-9ea4-21c56df2af68', 'requestTime': '20/Feb/2025:21:26:31 ', 'requestTimeEpoch': 1740086791796, 'stage': 'loyalty-api'}'
2025-02-20T21:26:31.796 DEBUG --- [et.reactor-9] l.s.a.n.e.handlers.parse   : Initializing $stageVariables='{'function': 'xxx-local'}'
2025-02-20T21:26:31.796 DEBUG --- [et.reactor-9] l.s.a.n.e.h.resource_route : Updating $context.resourcePath='/{proxy+}'
2025-02-20T21:26:31.796 DEBUG --- [et.reactor-9] l.s.a.n.e.h.resource_route : Updating $context.resourceId='erf3hkapil'
2025-02-20T21:26:31.796 DEBUG --- [et.reactor-9] l.s.a.n.e.header_utils     : Dropping headers: []
2025-02-20T21:26:31.798 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : Got an invocation for function arn:aws:lambda:us-east-1:000000000000:function:xxx-local:$LATEST with request_id 43f3addb-41d1-489d-b02f-400846fe3676
2025-02-20T21:26:31.799 DEBUG --- [et.reactor-5] l.s.l.i.docker_runtime_exe : Sending invoke-payload '{"invoke-id": "43f3addb-41d1-489d-b02f-400846fe3676", "invoked-function-arn": "arn:aws:lambda:us-east-1:000000000000:function:xxx-local", "payload": "{\"headers\": {\"xxx-platform\": \"android\", \"X-App-DeviceId\": \"something\", \"Authorization\": \"Bearer xxx\", \"Postman-Token\": \"xxx\", \"Host\": \"local.execute-api.localhost.localstack.cloud:4566\", \"X-Forwarded-For\": \"192.168.32.1\", \"X-Forwarded-Port\": \"4566\", \"X-Forwarded-Proto\": \"HTTP\", \"Accept\": \"application/json\", \"User-Agent\": \"AmazonAPIGateway_local\", \"X-Amzn-Trace-Id\": \"Root=1-67b79e07-ad28196df2bd9031ddaddb5a;Parent=1f26407f685ec136;Sampled=0\"}, \"multiValueHeaders\": {\"xxx-platform\": [\"android\"], \"X-App-DeviceId\": [\"something\"], \"Authorization\": [\"Bearer xxx' to executor 'xxx'
2025-02-20T21:26:31.886  INFO --- [t.reactor-15] localstack.request.aws     : AWS dynamodb.GetItem => 200
2025-02-20T21:26:31.895  INFO --- [et.reactor-6] localstack.request.aws     : AWS dynamodb.Query => 200
2025-02-20T21:26:31.986  INFO --- [t.reactor-13] localstack.request.aws     : AWS secretsmanager.GetSecretValue => 200
2025-02-20T21:26:32.117  INFO --- [t.reactor-11] localstack.request.http    : POST /_localstack_lambda/3a806aa4d25876b8af25f7bfa3375a16/invocations/43f3addb-41d1-489d-b02f-400846fe3676/logs => 202
2025-02-20T21:26:32.266  INFO --- [et.reactor-3] localstack.request.http    : POST /_localstack_lambda/3a806aa4d25876b8af25f7bfa3375a16/invocations/43f3addb-41d1-489d-b02f-400846fe3676/response => 202
2025-02-20T21:26:32.268 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : Got logs for invocation '43f3addb-41d1-489d-b02f-400846fe3676'
2025-02-20T21:26:32.268 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : [xxx-local-43f3addb-41d1-489d-b02f-400846fe3676] START RequestId: 43f3addb-41d1-489d-b02f-400846fe3676 Version: $LATEST
2025-02-20T21:26:32.268 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : [xxx-local-43f3addb-41d1-489d-b02f-400846fe3676] [INFO]	2025-02-20T21:26:31.863Z	43f3addb-41d1-489d-b02f-400846fe3676{'description': 'Authentication Middleware - Dispatch', 'url': 'HTTP://local.execute-api.localhost.localstack.cloud:4566/users/v1/user', 'hostname': 'LOGGER', 'objective': 'MESSAGE', 'context': {'platform': 'android', 'device_id': 'something', 'xxx-client-ip': '', 'correlation_id': 'fec98804-2da6-4565-b7e6-5ba123778532', 'FastAPIService': 'Loyalty API'}, 'environment': 'LOCAL'}
2025-02-20T21:26:32.268 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : [xxx-local-43f3addb-41d1-489d-b02f-400846fe3676] [INFO]	2025-02-20T21:26:31.871Z	43f3addb-41d1-489d-b02f-400846fe3676{'description': 'Authentication Middleware - Session authenticated for user', 'phone': 'xxx', 'hostname': 'LOGGER', 'objective': 'MESSAGE', 'context': {'platform': 'android', 'device_id': 'something', 'db-client-ip': '', 'correlation_id': 'fec98804-2da6-4565-b7e6-5ba123778532', 'FastAPIService': 'Loyalty API'}, 'environment': 'LOCAL'}
2025-02-20T21:26:32.268 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : [xxx-local-43f3addb-41d1-489d-b02f-400846fe3676] {"level": "INFO", "logger": "app.config.logger", "message": {"description": "Authentication Middleware - Dispatch", "url": "HTTP://local.execute-api.localhost.localstack.cloud:4566/users/v1/user", "hostname": "LOGGER", "objective": "MESSAGE", "context": {"platform": "android", "device_id": "something", "db-client-ip": "", "correlation_id": "fec98804-2da6-4565-b7e6-5ba123778532", "FastAPIService": "Loyalty API"}, "environment": "LOCAL"}}
2025-02-20T21:26:32.268 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : [xxx-local-43f3addb-41d1-489d-b02f-400846fe3676] {"level": "INFO", "logger": "app.config.logger", "message": {"description": "Authentication Middleware - Session authenticated for user", "phone": "+xxx", "hostname": "LOGGER", "objective": "MESSAGE", "context": {"platform": "android", "device_id": "something", "xxx-client-ip": "", "correlation_id": "fec98804-2da6-4565-b7e6-5ba123778532", "FastAPIService": "Loyalty API"}, "environment": "LOCAL"}}
2025-02-20T21:26:32.268 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : [xxx-local-43f3addb-41d1-489d-b02f-400846fe3676] [INFO]	2025-02-20T21:26:31.888Z	43f3addb-41d1-489d-b02f-400846fe3676{'description': 'No validation user found', 'phone': 'xxx', 'hostname': 'LOGGER', 'objective': 'MESSAGE', 'context': {'platform': 'android', 'device_id': 'something', 'xxx-client-ip': '', 'correlation_id': 'fec98804-2da6-4565-b7e6-5ba123778532', 'FastAPIService': 'Loyalty API', 'firebaseUID': 'xxx'}, 'environment': 'LOCAL'}
2025-02-20T21:26:32.268 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : [xxx-local-43f3addb-41d1-489d-b02f-400846fe3676] [INFO]	2025-02-20T21:26:31.897Z	43f3addb-41d1-489d-b02f-400846fe3676{'description': 'Found device lock entries for phone number and device', 'phone': 'xxx', 'device': 'something', 'devicelock': [{'phone': 'xxx', 'device': 'something', 'platform': 'android', 'username': 'xxx'}], 'hostname': 'LOGGER', 'objective': 'MESSAGE', 'context': {'platform': 'android', 'device_id': 'something', 'xxx-client-ip': '', 'correlation_id': 'fec98804-2da6-4565-b7e6-5ba123778532', 'FastAPIService': 'Loyalty API', 'firebaseUID': 'xxx'}, 'environment': 'LOCAL'}
2025-02-20T21:26:32.268 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : [xxx-local-43f3addb-41d1-489d-b02f-400846fe3676] {"level": "INFO", "logger": "app.config.logger", "message": {"description": "No validation user found", "phone": "xxx", "hostname": "LOGGER", "objective": "MESSAGE", "context": {"platform": "android", "device_id": "something", "xxx-client-ip": "", "correlation_id": "fec98804-2da6-4565-b7e6-5ba123778532", "FastAPIService": "Loyalty API", "firebaseUID": "xxx"}, "environment": "LOCAL"}}
2025-02-20T21:26:32.268 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : [xxx-local-43f3addb-41d1-489d-b02f-400846fe3676] {"level": "INFO", "logger": "app.config.logger", "message": {"description": "Found device lock entries for phone number and device", "phone": "xxx", "device": "something", "devicelock": [{"phone": "xxx", "device": "something", "platform": "android", "username": "xxx"}], "hostname": "LOGGER", "objective": "MESSAGE", "context": {"platform": "android", "device_id": "something", "xxx-client-ip": "", "correlation_id": "fec98804-2da6-4565-b7e6-5ba123778532", "FastAPIService": "Loyalty API", "firebaseUID": "xxx"}, "environment": "LOCAL"}}
2025-02-20T21:26:32.268 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : [xxx-local-43f3addb-41d1-489d-b02f-400846fe3676] [WARNING]	2025-02-20T21:26:31.993Z	43f3addb-41d1-489d-b02f-400846fe3676{'hostname': 'xxx-Proxy', 'status': 404, 'objective': 'APP RESPONSE', 'url': '/users/v1/user', 'responseBody': {'detail': 'Not Found'}, 'context': {'platform': 'android', 'device_id': 'something', 'xxx-client-ip': '', 'correlation_id': 'fec98804-2da6-4565-b7e6-5ba123778532', 'FastAPIService': 'Loyalty API', 'firebaseUID': 'xxx', 'phone': 'xxx', 'username': 'xxx', 'deviceLock': {'phone': 'xxx', 'device': 'something', 'platform': 'android', 'username': 'xxx'}}, 'environment': 'LOCAL'}
2025-02-20T21:26:32.268 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : [xxx-local-43f3addb-41d1-489d-b02f-400846fe3676] {"level": "WARNING", "logger": "app.config.logger", "message": {"hostname": "xxx-Proxy", "status": 404, "objective": "APP RESPONSE", "url": "/users/v1/user", "responseBody": {"detail": "Not Found"}, "context": {"platform": "android", "device_id": "something", "xxx-client-ip": "", "correlation_id": "fec98804-2da6-4565-b7e6-5ba123778532", "FastAPIService": "Loyalty API", "firebaseUID": "xxx", "phone": "xxx", "username": "xxx", "deviceLock": {"phone": "", "device": "something", "platform": "android", "username": "xxx"}}, "environment": "LOCAL"}}
2025-02-20T21:26:32.268 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : [xxx-local-43f3addb-41d1-489d-b02f-400846fe3676] END RequestId: 43f3addb-41d1-489d-b02f-400846fe3676
2025-02-20T21:26:32.268 DEBUG --- [et.reactor-5] l.s.l.i.version_manager    : [xxx-local-43f3addb-41d1-489d-b02f-400846fe3676] REPORT RequestId: 43f3addb-41d1-489d-b02f-400846fe3676	Duration: 250.77 ms	Billed Duration: 251 ms	Memory Size: 128 MB	Max Memory Used: 128 MB
2025-02-20T21:26:32.269 DEBUG --- [et.reactor-5] l.s.lambda_.provider       : Lambda invocation duration: 470.18ms
2025-02-20T21:26:32.271 DEBUG --- [et.reactor-9] l.s.a.n.e.h.method_respons : Remapping header: Content-Length
2025-02-20T21:26:32.271 DEBUG --- [et.reactor-9] l.s.a.n.e.header_utils     : Dropping headers: []
2025-02-20T21:26:32.271  INFO --- [et.reactor-9] localstack.request.http    : GET /loyalty-api/users/v1/user => 404

Unfortunately can't provide you lambda code to help get to this point, but I think the above illustrates what's going on currently.

Within AWS this is a perfectly valid setup that works as expected based on AWS documentation. Is there a particular reason localstack strips the stage name or is this something that can be fixed?

Environment

- OS: MacOS
- LocalStack:
  LocalStack version: 4.0.4.dev117
  LocalStack build date: 2025-01-22
  LocalStack build git hash: 648677eae

Anything else?

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_base_path_mapping

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-basepathmapping.html

https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-mappings.html

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

    0