API gateway returns 401 and doesn't invoke custom authorizer - node.js

I've implemented a custom 'REQUEST' type authorizer for an API gateway which validates a JWT token passed in the 'Authorization' header. I've tested the lambda independently and it works as expected. I've also attached the authorizer to my routes and I can test it in the AWS console - again, everything seems to work (see image):
successful invoke via console
However, when I try to invoke my endpoints with the token in the Authorization header, I always receive an UNAUTHORIZED response:
{
"errors": [
{
"category": "ClientError",
"code": "UNAUTHORIZED",
"detail": "Unauthorized",
"method": "GET",
"path": "/cases",
"requestId": "004eb254-a926-45ad-96a5-ce3527621c81",
"retryable": false
}
]
}
From what I have gathered, API gateway never invokes my Authorizer as i don't see any log events in its cloudwatch. I was able to enable cloudwatch logging of my API gateway, and the only log information I see is as follows:
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| timestamp | message |
|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1578275720543 | (dac0d4f6-1380-4049-bcee-bf776ca78e5c) Extended Request Id: F2v9WFfiIAMF-9w= |
| 1578275720543 | (dac0d4f6-1380-4049-bcee-bf776ca78e5c) Unauthorized request: dac0d4f6-1380-4049-bcee-bf776ca78e5c |
| 1578275720543 | (dac0d4f6-1380-4049-bcee-bf776ca78e5c) Extended Request Id: F2v9WFfiIAMF-9w= |
| 1578275720544 | (dac0d4f6-1380-4049-bcee-bf776ca78e5c) Gateway response type: UNAUTHORIZED with status code: 401 |
| 1578275720544 | (dac0d4f6-1380-4049-bcee-bf776ca78e5c) Gateway response body: {"errors": [{"category": "ClientError","code": "UNAUTHORIZED","detail": "Unauthorized","method": "GET","path": "/cases","requestId": "dac0d4f6-1380-4049-bcee-bf776ca78e5c","retryable": false }]} |
| 1578275720544 | (dac0d4f6-1380-4049-bcee-bf776ca78e5c) Gateway response headers: {} |
| 1578275720544 | (dac0d4f6-1380-4049-bcee-bf776ca78e5c) Gateway response type: UNAUTHORIZED with status code: 401 |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
At this point I am completely stuck and not sure how to debug this further. I'm assuming something must be configured wrong but the log information I can find doesn't given any indication of what the problem is. I've also pasted a copy of my authorizers configuration in the image below:
Authorizer Configuration
Screenshot of one endpoint configured to use the authorizer

I figured out the problem I was having:
I needed to set identitySource: method.request.header.Authorization in the authorizer field of the endpoint as well as in the CF stack.
Custom Authorizer definition in raw cloudformation:
service:
name: api-base
frameworkVersion: ">=1.2.0 <2.0.0"
plugins:
- serverless-plugin-optimize
- serverless-offline
- serverless-pseudo-parameters
- serverless-domain-manager
custom:
stage: ${self:provider.stage, 'dev'}
serverless-offline:
port: ${env:OFFLINE_PORT, '4000'}
false: false
cognitoStack: marley-auth
customDomain:
domainName: ${env:BE_HOST, ''}
enabled: ${env:EN_CUSTOM_DOMAIN, self:custom.false}
stage: ${self:provider.stage, 'dev'}
createRoute53Record: true
provider:
name: aws
runtime: nodejs10.x
versionFunctions: true
apiName: public
logs:
restApi: true
stackTags:
COMMIT_SHA: ${env:COMMIT_SHA, 'NO-SHA'}
environment:
USER_POOL_ID: ${cf:${self:custom.cognitoStack}-${self:custom.stage}.UserPoolId}
CLIENT_ID: ${cf:${self:custom.cognitoStack}-${self:custom.stage}.UserPoolClientId}
timeout: 30
iamRoleStatements:
- Effect: "Allow"
Action:
- "lambda:InvokeFunction"
Resource: "*"
functions:
authorizer:
handler: handler/authorize.handler
resources:
- Outputs:
ApiGatewayRestApiId:
Value:
Ref: ApiGatewayRestApi
Export:
Name: ${self:custom.stage}-${self:provider.apiName}-ApiGatewayRestApiId
ApiGatewayRestApiRootResourceId:
Value:
Fn::GetAtt:
- ApiGatewayRestApi
- RootResourceId
Export:
Name: ${self:custom.stage}-${self:provider.apiName}-ApiGatewayRestApiRootResourceId
SharedAuthorizerId:
Value:
Ref: SharedAuthorizer
Export:
Name: ${self:custom.stage}-${self:provider.apiName}-ApiGatewaySharedAuthorizerId
- Resources:
SharedAuthorizer:
Type: AWS::ApiGateway::Authorizer
Properties:
Name: public
AuthorizerUri: !Join
- ''
- - 'arn:aws:apigateway:'
- !Ref 'AWS::Region'
- ':lambda:path/2015-03-31/functions/'
- !GetAtt
- AuthorizerLambdaFunction
- Arn
- /invocations
RestApiId: !Ref 'ApiGatewayRestApi'
Type: REQUEST
IdentitySource: method.request.header.Authorization
AuthorizerResultTtlInSeconds: '300'
DependsOn: AuthorizerLambdaFunction
ApiAuthLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref AuthorizerLambdaFunction
Principal: apigateway.amazonaws.com
SourceArn: !Sub "arn:aws:execute-api:#{AWS::Region}:#{AWS::AccountId}:#{ApiGatewayRestApi}/authorizers/*"
DependsOn: ApiGatewayRestApi
Using the authorizer in another stack - note that I have specified IdentitySource here as well as in the definition of the authorizer - for some reason I had to do it in both places.
authorizer:
type: CUSTOM
authorizerId: ${cf:api-base-${self:custom.stage}.SharedAuthorizerId}
identitySource: method.request.header.Authorization

Related

AWS SAM Schedule Lambda is not triggering as per Schedule

I have a use case where I need to call one API every 2 minutes to check the updates and store the result into the database. For the same, I am trying the AWS Schedule lambda function using AWS SAM CLI using Python but my Lambda function is not getting triggered. Below is my code:
app.py
def lambda_schedule(event, context):
print("Lambda Schedule event started Successfully......")
print("Lambda function ARN:", context.invoked_function_arn)
print("CloudWatch log stream name:", context.log_stream_name)
print("CloudWatch log group name:", context.log_group_name)
print("Lambda Request ID:", context.aws_request_id)
print("Lambda Schedule event ended Successfully......")
template.yaml
CronLambdaFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_schedule
Runtime: python3.8
Events:
PullBalanceScheduleRule:
Type: AWS::Events::Rule
Properties:
EventPattern:
source:
- "aws.events"
PullBalanceScheduleRule:
Type: AWS::Events::Rule
Properties:
Description: "PullBalanceScheduleRule"
ScheduleExpression: "rate(2 minutes)"
State: "ENABLED"
Targets:
-
Arn: !GetAtt CronLambdaFunction.Arn
Id: "CronLambdaFunction"
PermissionForEventsToInvokeLambda:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref "PullBalanceScheduleRule"
Action: "lambda:InvokeFunction"
Principal: "events.amazonaws.com"
SourceArn:
-
Arn: !GetAtt PullBalanceScheduleRule.Arn
Id: "PullBalanceScheduleRule"
can anyone tell me, what is wrong in my code OR what is missing in my code?
I got my mistakes. Mistakes were in the Permission sections. I am posting here correct yaml configurations so it could help to someone who are new to AWS SAM CLI.
app.py
def lambda_schedule(event, context):
print("Lambda Schedule event started Successfully......")
print("Lambda function ARN:", context.invoked_function_arn)
print("CloudWatch log stream name:", context.log_stream_name)
print("CloudWatch log group name:", context.log_group_name)
print("Lambda Request ID:", context.aws_request_id)
print("Lambda Schedule event ended Successfully......")
template.yaml
CronLambdaFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_schedule
Runtime: python3.8
Events:
PullBalanceScheduleRule:
Type: AWS::Events::Rule
Properties:
EventPattern:
source:
- "aws.events"
PullBalanceScheduleRule:
Type: AWS::Events::Rule
Properties:
Description: "PullBalanceScheduleRule"
ScheduleExpression: "rate(2 minutes)"
State: "ENABLED"
Targets:
-
Arn: !GetAtt CronLambdaFunction.Arn
Id: "CronLambdaFunction"
PermissionForEventsToInvokeLambda:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref "CronLambdaFunction"
Action: "lambda:InvokeFunction"
Principal: "events.amazonaws.com"
SourceArn:
Fn::GetAtt:
- "PullBalanceScheduleRule"
- "Arn"

AWS Lambda SNS event is not binding to the correct SNS Topic ARN using Serverless yml

I have a serverless resource for SNS topic in the resources section in the serverless.yml something like this,
resources:
Resources:
SNSTopic:
Type: AWS::SNS::Topic
Properties:
DisplayName: SNS Topic
TopicName: ${self:service}-${self:provider.stage}-Topic
When I am trying to bind this SNS topic to my lambda event as given below, lambda is not triggering by the SNS event. When I check AWS console for that lambda function the SNS event bound with wrong ARN value.
Function:
handler: src/sample/file.lambdaHandler
role: s3FullAccessRole
events: SNSTopic
Properties:
Policies:
- AWSLambdaExecute
- Statement:
- Effect: Allow
Action:
- 'lambda:InvokeFunction'
I have tried with changing event with all the different ways mentioned in here, https://serverless.com/framework/docs/providers/aws/events/sns/. The only way I found is to hard code the SNS Topic ARN value in the lambda event, which is not ideal for my situation.
Any help is really appreciated.
You could actually create a variable in custom with the arn of the sns topic
custom:
region: ${opt:region, self:provider.region}
snsTopic: ${self:service}-${self:provider.stage}-Topic
snsTopicArn: { "Fn::Join" : ["", ["arn:aws:sns:${self:custom.region}:", { "Ref" : "AWS::AccountId" }, ":${self:custom.snsTopic}" ] ] }
then just use the arn on the places you need.
Or you can use the plugin https://github.com/silvermine/serverless-plugin-external-sns-events to basic reference the topic name.
If you have only 1 serverless.yml and don't want to have a separate cloudformation file I would use the first option
EDIT:
To use the arn follow the instructions on serverless https://serverless.com/framework/docs/providers/aws/events/sns#using-a-pre-existing-topic
functions:
dispatcher:
handler: <handler>
events:
- sns:
arn: ${self:custom.snsTopicArn}
since you have the sns topic on the same serverless.yml, you can even ignore the snsTopicArn variable and build it like one of the suggestions using !Ref which should be a better option for you:
functions:
dispatcher:
handler: <handler>
events:
- sns:
arn: !Ref SNSTopic
topicName: ${self:custom.snsTopic}
full example:
service: testsns
provider:
name: aws
runtime: nodejs12.x
region: eu-west-1
functions:
hello:
handler: handler.hello
events:
- sns:
arn: !Ref SuperTopic
topicName: MyCustomTopic
Properties:
Policies:
- AWSLambdaExecute
- Statement:
- Effect: Allow
Action:
- 'lambda:InvokeFunction'
resources:
Resources:
SuperTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: MyCustomTopic
Finally got it!
I end up removing my SNS TOPIC declaration from resource section of serverless.yml added under iamRoleStatements, something like this,
iamRoleStatements:
- Effect: Allow
Action:
- SNS:Publish
Resource: { "Fn::Join" : ["", ["arn:aws:sns:${self:provider.region}:", { "Ref" : "AWS::AccountId" }, ":${self:custom.mySnsTopic}" ] ] }
And added variables in the custom section
custom:
mySnsTopic: "${self:service}-${self:provider.stage}-sns-consume"
mySnsTopicArn: { "Fn::Join" : ["", ["arn:aws:sns:${self:provider.region}:", { "Ref" : "AWS::AccountId" }, ":${self:custom.mySnsTopic}" ] ] }
then mapped this to the lambda function events
Function:
handler: src/sample/file.lambdaHandler
role: s3FullAccessRole
events: ${self:custom.mySnsTopicArn}
Properties:
Policies:
- AWSLambdaExecute
For reference link

Lambda#edge not triggered by an origin-request event

I'm trying to configure Lambda#edge functions using CloudFormation. After deploying the template everything looks find in the console, however the lambda functions listening to origin-request events are not being triggered.
Strange enough, a viewer-request event does manage to trigger a function.
What am I doing wrong? How can I make the origin-request event to work?
Here's my template:
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Deployment of Lambda#edge functions
Parameters:
Stage:
Type: String
AllowedValues:
- staging
- production
Default: staging
Description: Stage that can be added to resource names
CodeBucket:
Type: String
Description: The S3 Bucket name for latest code upload
CodeKey:
Type: String
Description: The S3 Key for latest code upload
# Mappings:
# AliasMap:
# staging:
# Alias: "staging-app.achrafsouk.com"
# production:
# Alias: "app.achrafsouk.com"
Resources:
CFDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
Logging:
Bucket: XXX.s3.amazonaws.com
IncludeCookies: false
Prefix: !Sub ${Stage}
PriceClass: PriceClass_100
Comment:
!Sub "Lambda#Edge - ${Stage}"
# Aliases:
# - !FindInMap [AliasMap,
# Ref: Stage, Alias]
Origins:
- CustomOriginConfig:
OriginProtocolPolicy: https-only
DomainName: !Sub "${Stage}.defaultOrigin.example.com"
Id: defaultOrigin
OriginCustomHeaders:
- HeaderName: X-Node-Env
HeaderValue: !Sub "${Stage}"
- CustomOriginConfig:
OriginProtocolPolicy: https-only
DomainName: !Sub "${Stage}.another.example.com"
Id: rateLimitsOrigin
OriginCustomHeaders:
- HeaderName: X-Node-Env
HeaderValue: !Sub "${Stage}"
DefaultCacheBehavior:
ForwardedValues:
Headers:
- CloudFront-Viewer-Country
- X-Request-Host
- User-Agent
QueryString: true
ViewerProtocolPolicy: https-only
TargetOriginId: defaultOrigin
DefaultTTL: 0
MaxTTL: 0
LambdaFunctionAssociations:
# - EventType: viewer-request
# LambdaFunctionARN:
# Ref: ViewerRequestLambdaFunction.Version
- EventType: origin-request
LambdaFunctionARN:
Ref: OriginRequestLambdaFunction.Version
CacheBehaviors:
- ForwardedValues:
Headers:
- CloudFront-Viewer-Country
- X-Request-Host
- User-Agent
QueryString: true
ViewerProtocolPolicy: https-only
DefaultTTL: 60
MaxTTL: 60
TargetOriginId: rateLimitsOrigin
PathPattern: "/rate-limit*"
LambdaFunctionAssociations:
- EventType: origin-request
LambdaFunctionARN:
Ref: GetRateLimitLambdaFunction.Version
# ViewerRequestLambdaFunction:
# Type: AWS::Serverless::Function
# Properties:
# CodeUri:
# Bucket: !Sub ${CodeBucket}
# Key: !Sub ${CodeKey}
# Role: !GetAtt LambdaEdgeFunctionRole.Arn
# Runtime: nodejs10.x
# Handler: src/functions/viewerRequest.handler
# Timeout: 5
# AutoPublishAlias: live
OriginRequestLambdaFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri:
Bucket: !Sub ${CodeBucket}
Key: !Sub ${CodeKey}
Role: !GetAtt LambdaEdgeFunctionRole.Arn
Runtime: nodejs10.x
Handler: src/functions/originRequest.handler
Timeout: 5
AutoPublishAlias: live
GetRateLimitLambdaFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri:
Bucket: !Sub ${CodeBucket}
Key: !Sub ${CodeKey}
Role: !GetAtt LambdaEdgeFunctionRole.Arn
Runtime: nodejs10.x
Handler: src/functions/getRateLimit.handler
Timeout: 5
AutoPublishAlias: live
LambdaEdgeFunctionRole:
Type: "AWS::IAM::Role"
Properties:
Path: "/"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: "AllowLambdaServiceToAssumeRole"
Effect: "Allow"
Action:
- "sts:AssumeRole"
Principal:
Service:
- "lambda.amazonaws.com"
- "edgelambda.amazonaws.com"
Outputs:
# ViewerRequestLambdaFunctionVersion:
# Value: !Ref ViewerRequestLambdaFunction.Version
OriginRequestLambdaFunctionVersion:
Value: !Ref OriginRequestLambdaFunction.Version
GetRateLimitLambdaFunctionVersion:
Value: !Ref GetRateLimitLambdaFunction.Version
CFDistribution:
Description: Cloudfront Distribution Domain Name
Value: !GetAtt CFDistribution.DomainName
So, after painful hours of trial and error the solution was staring me in the face:
CloudFront distribution was emitting and error saying it could not connect to the origin, and it was right - because I've specified dummy domains in the origins definition of the CloudFront distribution.
My logic was that if my lambda#edge functions intercept the requests before reaching the origin, then it shouldn't matter if the origin's domains is real or not.
As it turns out, it's partially true. Because it seams that:
viewer-request event is triggered before trying to connect to the origin, and there for it can by fake.
origin-request event is triggered only after trying to connect to the origin, so you'd need to specify something real.
Hope my wasited hours will contribute to someone else exploring the same path of reasoning.
In case it helps someone in future with this problem - I noticed that origin-response (which I accidentally selected instead of origin-request) events were (mostly) working for me, but there were still a large number of OriginConnectError in the logs, and the "Percentage of Viewer Requests by Result Type" report was showing a high error rate.
It turns out it was only working at all because I had my origin connection set to https-only, but my origin (an empty s3 bucket) only accepted http connections - and for some reason the events were still being triggered.
Changing the origin connection to the correct http-only completely stopped things working for origin-response, until I also corrected the event type to origin-request
So, in general, check the OriginProtocolPolicy is http-only if you're using an s3 bucket origin. If it's wrong, things might still appear to work sometimes, but not always, and you'll end up with high error rates.

Invalid HTTP endpoint specified for URI in Amazon ApiGateway

I am trying to create a resource /user/devices with GET method for API Gateway using cloudformation template but it is giving me a below error
An error occurred: ApiGatewayRootMethod - Invalid HTTP endpoint specified for URI (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException; Request ID:xxxxxxxxxx)
Below is my cloudformation template,
AWSTemplateFormatVersion: 2018-11-13
Description: test user
resources:
Resources:
UserDeviceApiGateway:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "test-user-info"
Description: "Fetch the user"
UserResource:
Type: 'AWS::ApiGateway::Resource'
Properties:
ParentId:
Fn::GetAtt: ["UserDeviceApiGateway","RootResourceId"]
RestApiId:
Ref: "UserDeviceApiGateway"
PathPart: 'user'
Resource:
Type: 'AWS::ApiGateway::Resource'
Properties:
ParentId:
Ref: "UserResource"
RestApiId:
Ref: "UserDeviceApiGateway"
PathPart: 'devices'
ApiGatewayRootMethod:
Type: "AWS::ApiGateway::Method"
Properties:
AuthorizationType: "NONE"
HttpMethod: "GET"
Integration:
IntegrationHttpMethod: "GET"
Type: "HTTP"
Uri: Sub
- "arn:aws:apigateway:arn:aws:lambda:eu-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-1:xxxxxxxx:function:user-device-lambda/invocations"
ResourceId:
Fn::GetAtt: ["UserDeviceApiGateway","RootResourceId"]
RestApiId:
Ref: "UserDeviceApiGateway"
Deployment:
DependsOn:
- ApiGatewayRootMethod
Type: 'AWS::ApiGateway::Deployment'
Properties:
RestApiId:
Ref: "UserDeviceApiGateway"
StageName: dev
A little late to the party but...
You specify Type: "HTTP" for ApiGatewayRootMethod but HTTP takes the API endpoint URL. The URI formation that you specify is taken by Type: "AWS".
From AWS docs:
The Uniform Resource Identifier (URI) for the integration.
If you specify HTTP for the Type property, specify the API endpoint URL.
If you specify MOCK for the Type property, don't specify this property.
If you specify AWS for the Type property, specify an AWS service that follows this form: arn:aws:apigateway:region:subdomain.service|service:path|action/service_api. For example, a Lambda function URI follows this form: arn:aws:apigateway:region:lambda:path/path. The path is usually in the form /2015-03-31/functions/LambdaFunctionARN/invocations. For more information, see the uri property of the Integration resource in the Amazon API Gateway REST API Reference.
If you specified HTTP or AWS for the Type property, you must specify this property.
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-method-integration.html

Setting HTTP Proxy on AWS API Gateway via Cloudformation

Thanks for the help in advance.
Currently using cloudformation templates to deploy a simple API to AWS as part of a POC for moving from Azure to AWS API management.
I have got everything working except i have not been able to figure out the YAML AWS extension for setting the HTTP proxy checkbox for the HTTP request.
Sample YAML below. I know this will not set that checkbox (as i have tested it and it worked minus that problem), but on this page
https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-integration.html
i cannot see a extension that sets this option? Has AWS not done this yet
AWSTemplateFormatVersion: '2010-09-09'
Resources:
PlayersAPI:
Type: AWS::ApiGateway::RestApi
Properties:
Name: RAH API
Description: A demo API for testing
Body:
swagger: '2.0'
info:
title: test api
description: test api
version: 1.0.1
contact:
name: SH
email: test#mailinator.com
paths:
"/heartbeat":
get:
description: Checks the API is working
produces:
- application/json
responses:
'200':
description: API Response information
x-amazon-apigateway-integration:
type: http
responses:
default:
statusCode: '200'
httpMethod: GET
uri: https://api.example.com
This works for me:
resources:
Resources:
ProxyResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId:
Fn::GetAtt:
- ApiGatewayRestApi # our default Rest API logical ID
- RootResourceId
PathPart: "{proxy+}" # the endpoint in your API that is set as proxy
RestApiId:
Ref: ApiGatewayRestApi
ProxyMethod:
Type: AWS::ApiGateway::Method
Properties:
ResourceId:
Ref: ProxyResource
RestApiId:
Ref: ApiGatewayRestApi
HttpMethod: GET # the method of your proxy. Is it GET or POST or ... ?
MethodResponses:
- StatusCode: 200
Integration:
IntegrationHttpMethod: GET
Type: HTTP_PROXY
Uri: http://bucket.mybucket.co.s3.eu-west-1.amazonaws.com/{proxy} # the URL you want to set a proxy to
IntegrationResponses:
- StatusCode: 200
AuthorizationType: NONE

Resources