Skip to content

Bug: API Gateway Console Test button fails validation with @parser envelope ApiGatewayEnvelope because requestContext -> identity -> sourceIp value is not a valid IPv4 or IPv6 network #2526

Closed
@Cihaan

Description

@Cihaan

Expected Behavior

We should be able to generate a test form the API Gateway console that allows the ApiGatewayProxyEventModel to be validated and parsed by Zod.

Current Behavior

When generating a test from the API Gateway console, the value for the sourceIp in requestContext -> identity is "test-invoke-source-ip". Since this value is defined to be an ip here And so test events from the API Gateway console fail with a 502 error because of the resulting Zod ValidationError.

Code snippet

  RestApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Environment
      Name: !Sub '${ProjectName}-${Environment}-api'
      OpenApiVersion: '3.0.1' 
      TracingEnabled: true
      EndpointConfiguration: REGIONAL
      Auth: 
        DefaultAuthorizer: NONE
        AddDefaultAuthorizerToCorsPreflight: false
        ApiKeyRequired: false
      Cors:
        AllowMethods: "'DELETE,GET,HEAD,OPTIONS,POST,PUT'"
        AllowHeaders: "'*'"
        AllowOrigin: "'*'"
      MethodSettings:
        - HttpMethod: '*'
          ResourcePath: '/*'
          LoggingLevel: INFO
          DataTraceEnabled: true
          MetricsEnabled: true
          ThrottlingBurstLimit: 500
          ThrottlingRateLimit: 1000
      AccessLogSetting:
        DestinationArn: !GetAtt ApiAccessLogGroup.Arn
        Format: >-
          {
          "status": "$context.status",
          "traceId": "$context.xrayTraceId",
          "requestId": "$context.requestId",
          "requestTime": "$context.requestTime",
          "httpMethod": "$context.httpMethod",
          "responseLength": "$context.responseLength",
          "resourcePath": "$context.resourcePath",
          "resourceId": "$context.resourceId",
          "sourceIp": "$context.identity.sourceIp",
          "userAgent": "$context.identity.userAgent",
          "accountId": "$context.identity.accountId",
          "caller": "$context.identity.caller",
          "user": "$context.identity.user",
          "userArn": "$context.identity.userArn"
          }
      Tags:
        APPLI: !Ref ApplicationName
        ProjectName: !Ref ProjectName
        ENV: !Ref Environment
        Owner: !Ref Owner
// Lambda parses input and returns back the metadata field with a 200 status code
  UploadFileFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub '${ProjectName}-${Environment}-upload-file'
      CodeUri: ../../../dist/packages/backend/upload-file/
      CodeSigningConfigArn: !If [IsSandbox, !Ref AWS::NoValue, !Ref SignedCodeSigningConfigArn]
      Events:
        Api:
          Type: Api
          Properties:
            Path: /upload
            Method: POST
            RestApiId: !Ref RestApi
      Environment:
        Variables:
          BUCKET_NAME: !Ref S3Bucket
      Policies:
        - S3ReadPolicy:
            BucketName: !Ref S3Bucket
        - S3WritePolicy:
            BucketName: !Ref S3Bucket
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        EntryPoints:
          - index.js
// index.ts
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
import { parser } from '@aws-lambda-powertools/parser';
import { ApiGatewayEnvelope } from '@aws-lambda-powertools/parser/envelopes';
import { Tracer } from '@aws-lambda-powertools/tracer';
import Logger from '@fec-module/logger';
import { z } from 'zod';

import { uploadFileSchema } from 'packages/backend/src/upload-file/schema';

const tracer = new Tracer();

type UploadInput = z.infer<typeof uploadFileSchema>;

type UploadResponse = {
  cookies?: string[];
  isBase64Encoded?: true | false;
  statusCode: number;
  headers?: { [header: string]: string | number | boolean };
  body?: string;
};

class Lambda implements LambdaInterface {
  @Logger.injectLambdaContext({ logEvent: true })
  @tracer.captureLambdaHandler()
  @parser({ schema: uploadFileSchema, envelope: ApiGatewayEnvelope })
  public async handler(event: UploadInput): Promise<UploadResponse> {
    Logger.info('Received context', event);

    return {
      statusCode: 200,
      body: JSON.stringify({
        message: 'Request Received successfully with body',
        metadata: event.metadata,
      }),
    };
  }
}

// schema.ts
const uploadFileSchema = z.object({
  metadata: z.object({
    source_system: z.string(),
    category: z.string(),
    filename: z.string(),
    display_name: z.string().optional(),
    external_id: z.string().optional(),
    description: z.string().optional(),
    ttl: z.number().optional(),
    application_metadata: z.record(z.unknown()).optional(),
  }),
});

Steps to Reproduce

  • deploy the Api Gateway and the Lambda using SAM
  • go to the Test tab of the /upload route in the Api Gateway AWS Console
  • place following headers :
{
  "metadata": {
    "source_system": "source_system",
    "category": "category",
    "filename": "filename",
    "display_name": "display_name",
    "external_id": "external_id",
    "description": "descrition",
    "ttl": 1,
    "application_metadata": {
        "other": "other"
    }
  }
}
  • run test

following conversation in the Discord: https://discord.com/channels/1006478942305263677/1006527385409179678/1239877018653560872
(related to aws-powertools/powertools-lambda-python/issues/1562)

Possible Solution

update the Api Gateway zod schema to be:

z.union([z.string().ip(), z.literal('test-invoke-source-ip')]).optional()

Powertools for AWS Lambda (TypeScript) version

2.1.1-beta

AWS Lambda function runtime

20.x

Packaging format used

npm

Execution logs

No response

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingcompletedThis item is complete and has been merged/shippedparserThis item relates to the Parser Utility

Type

No type

Projects

Status

Shipped

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions