- Newest
- Most votes
- Most comments
Turns out the problem was that API Gateway isn't able to talk directly to OpenSearch for some reason, so instead I went Web <-> API Gateway <-> Lambda <-> OpenSearch. Here's a CloudFormation that worked for others curious to replicate this:
AWSTemplateFormatVersion: '2010-09-09' Description: > Web -> API Gateway -> Lambda -> OpenSearch stack + Amplify-hosted frontend and S3-based ingestion Parameters: ProjectName: Type: String GitHubRepo: Type: String Description: "Format: owner/repo" GitHubBranch: Type: String Default: "main" GitHubToken: Type: String NoEcho: true OpenSearchDomainName: Type: String Description: "OpenSearch domain name" OpenSearchDomainEndpoint: Type: String Description: "OpenSearch domain endpoint (https://...)" LambdaCodeBucket: Type: String Description: "S3 bucket for Lambda code" IndexLambdaKey: Type: String SearchLambdaKey: Type: String Resources: # IAM Roles LambdaRole: Type: AWS::IAM::Role Properties: RoleName: !Sub "${ProjectName}-LambdaRole" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: LambdaPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - s3:GetObject # for S3 ingestion access Resource: "*" - Effect: Allow Action: - es:ESHttp* # for OpenSearch access Resource: !Sub "arn:${AWS::Partition}:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}/*" - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: "*" AmplifyServiceRole: Type: AWS::IAM::Role Properties: RoleName: !Sub "${ProjectName}-AmplifyServiceRole" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: amplify.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: AmplifySSRPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - cloudformation:* - s3:* - iam:PassRole - lambda:* - logs:* - ec2:Describe* - ec2:CreateNetworkInterface - ec2:DeleteNetworkInterface - ec2:DescribeNetworkInterfaces Resource: "*" APIGatewayRole: Type: AWS::IAM::Role Properties: RoleName: !Sub "${ProjectName}-APIGatewayRole" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: apigateway.amazonaws.com Action: sts:AssumeRole # S3 IngestBucket: Type: AWS::S3::Bucket DependsOn: LambdaInvokePermissionForIndexing Properties: NotificationConfiguration: LambdaConfigurations: - Event: "s3:ObjectCreated:*" Function: !GetAtt IndexLambda.Arn # Lambda LambdaInvokePermissionForIndexing: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref IndexLambda Action: lambda:InvokeFunction Principal: s3.amazonaws.com LambdaInvokePermissionForSearching: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref SearchLambda Action: lambda:InvokeFunction Principal: apigateway.amazonaws.com SourceArn: !Sub "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${Gateway}/*/*/*" IndexLambda: Type: AWS::Lambda::Function Properties: Runtime: nodejs22.x Handler: index.handler Role: !GetAtt LambdaRole.Arn Timeout: 30 Environment: Variables: OPENSEARCH_ENDPOINT: !Ref OpenSearchDomainEndpoint Code: S3Bucket: !Ref LambdaCodeBucket S3Key: !Ref IndexLambdaKey # Lambda to proxy requests to OpenSearch SearchLambda: Type: AWS::Lambda::Function Properties: Runtime: nodejs22.x Handler: search.handler Role: !GetAtt LambdaRole.Arn Timeout: 30 Environment: Variables: OPENSEARCH_ENDPOINT: !Ref OpenSearchDomainEndpoint Code: S3Bucket: !Ref LambdaCodeBucket S3Key: !Ref SearchLambdaKey # API Gateway Gateway: Type: AWS::ApiGateway::RestApi Properties: Name: !Sub "${ProjectName}-APIGateway" GatewaySearchResource: Type: AWS::ApiGateway::Resource Properties: ParentId: !GetAtt Gateway.RootResourceId PathPart: "search" RestApiId: !Ref Gateway GatewaySearchProxyResource: Type: AWS::ApiGateway::Resource Properties: ParentId: !Ref GatewaySearchResource PathPart: "{proxy+}" RestApiId: !Ref Gateway GatewaySearchMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE HttpMethod: ANY ResourceId: !Ref GatewaySearchProxyResource RestApiId: !Ref Gateway RequestParameters: method.request.path.proxy: true Integration: IntegrationHttpMethod: POST Type: AWS_PROXY Uri: !Sub "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SearchLambda.Arn}/invocations" GatewayIndexResource: Type: AWS::ApiGateway::Resource Properties: ParentId: !GetAtt Gateway.RootResourceId PathPart: "index" RestApiId: !Ref Gateway GatewayIndexProxyResource: Type: AWS::ApiGateway::Resource Properties: ParentId: !Ref GatewayIndexResource PathPart: "{proxy+}" RestApiId: !Ref Gateway GatewayIndexMethod: Type: AWS::ApiGateway::Method Properties: HttpMethod: ANY AuthorizationType: NONE ResourceId: !Ref GatewayIndexProxyResource RestApiId: !Ref Gateway RequestParameters: method.request.path.proxy: true Integration: IntegrationHttpMethod: POST Type: AWS_PROXY Uri: !Sub "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${IndexLambda.Arn}/invocations" GatewayDeployment: Type: AWS::ApiGateway::Deployment DependsOn: - GatewayIndexMethod - GatewaySearchMethod Properties: RestApiId: !Ref Gateway StageName: prod # Amplify AmplifyApp: Type: AWS::Amplify::App Properties: Name: !Ref ProjectName Repository: !Sub "https://github.com/${GitHubRepo}" OauthToken: !Ref GitHubToken IAMServiceRole: !GetAtt AmplifyServiceRole.Arn EnvironmentVariables: - Name: API_GATEWAY_ENDPOINT Value: !Sub "https://${Gateway}.execute-api.${AWS::Region}.amazonaws.com/prod" AmplifyBranch: Type: AWS::Amplify::Branch Properties: AppId: !GetAtt AmplifyApp.AppId BranchName: !Ref GitHubBranch Outputs: BucketName: Value: !Ref IngestBucket AmplifyURL: Value: !GetAtt AmplifyApp.DefaultDomain ApiInvokeURL: Value: !Sub "https://${Gateway}.execute-api.${AWS::Region}.amazonaws.com/prod" IndexLambdaName: Value: !Ref IndexLambda SearchLambdaName: Value: !Ref SearchLambda GatewayValue: Value: !Ref Gateway OpenSearchDomainEndpoint: Value: !Ref OpenSearchDomainEndpoint
answered 2 years ago
Based on your setup and the error message you're receiving, there are a few things to consider:
-
The error message suggests that the request is being treated as anonymous, which indicates that the API Gateway is not successfully signing the request to OpenSearch. This could be due to the way the integration is set up in your API Gateway method.
-
In your CloudFormation template, you're using an HTTP_PROXY integration type for the API Gateway method. However, for AWS services like OpenSearch that require request signing, you should use the AWS_PROXY integration type instead.
-
The IAM role for API Gateway (APIGatewayRole) has the correct permissions, but it's not being used effectively due to the integration type.
To resolve this issue, try the following changes to your CloudFormation template:
-
Change the Integration Type: In the GatewayProxyMethod resource, change the Integration Type from HTTP_PROXY to AWS_PROXY.
-
Update the Uri: Change the Uri to use the arn format for OpenSearch.
-
Remove the Credentials field: With AWS_PROXY, you don't need to specify the Credentials explicitly.
Here's how the updated GatewayProxyMethod should look:
GatewayProxyMethod: Type: AWS::ApiGateway::Method Properties: ResourceId: !Ref GatewayProxy RestApiId: !Ref Gateway HttpMethod: ANY AuthorizationType: NONE Integration: IntegrationHttpMethod: POST Type: AWS_PROXY Uri: !Sub "arn:aws:apigateway:${AWS::Region}:es:path/${OpenSearchDomainName}/{proxy}" RequestParameters: method.request.path.proxy: true
Also, ensure that your OpenSearch domain's access policy allows the API Gateway's execution role. You may need to update the Principal in your OpenSearch policy to include the API Gateway's execution role ARN.
These changes should allow API Gateway to properly sign the requests to OpenSearch using SigV4, resolving the authorization error you're encountering.
Sources
AWS::ApiGateway::Method - AWS CloudFormation
Amazon OpenSearch Service ML connectors for AWS services - Amazon OpenSearch Service
answered 2 years ago

Unfortunately, this won't work for me because the requests are originating with both Lambda and an Amplify web app.