Is there any way of specifying allowed scopes on an API Gateway method such that the allowed scopes are passed to a custom authorizer and validated against the scopes claim in an access token.
E.g. a get users endpoint might be available to all users but a create user endpoint only available to an users with the create:user scope. As well as ensuring the access token is valid the custom authorizer would check the scope claim in the token and compare it to the allowed scopes for the method.
Would prefer not to have to write a different authorizer function for each combination of required scopes.
I notice something like this is possible with Cognito, but my identity provider / token issuer is Auth0 so using Lambda function to validate the access token
Kind regards
You should be able to use single Lambda Authorizer to protect both endpoints based on the token scope. You will want to use Request based Enhanced Lambda Authorizer
You pass the access token in the Authorization header and verify the access token signature and expiration before processing the request.
An example of Event object received by the authorizer:
{
"methodArn": "arn:aws:execute-api:us-east-1:XXXXXXXXXX:xxxxxx/dev/GET/hello",
"resource": "/hello",
"requestContext": {
"resourceId": "xxxx",
"apiId": "xxxxxxxxx",
"resourcePath": "/hello",
"httpMethod": "GET",
"requestId": "9e04ff18-98a6-11e7-9311-ef19ba18fc8a",
"path": "/dev/hello",
"accountId": "XXXXXXXXXXX",
"identity": {
"apiKey": "",
"sourceIp": "58.240.196.186"
},
"stage": "dev"
},
"queryStringParameters": {},
"httpMethod": "GET",
"pathParameters": {},
"headers": {
"cache-control": "no-cache",
"x-amzn-ssl-client-hello": "AQACJAMDAAAAAAAAAAAAAAAAAAAAAAAAAAAA…",
"Accept-Encoding": "gzip, deflate",
"X-Forwarded-For": "54.240.196.186, 54.182.214.90",
"Accept": "*/*",
"User-Agent": "PostmanRuntime/6.2.5",
"Authorization": "hello"
},
"stageVariables": {},
"path": "/hello",
"type": "REQUEST"
}
You can identify the request by combination of event.requestContext.resourcePath and event.requestContext.httpMethod. Based on the request type and the scope defined in the token you can return Allowed or Denied policy. If, for example, request is for create user endpoint but access token don't include create:user scope then you will return policy to deny the request.
Related
I'm trying to integrate Google as an IdP in our existing Cognito UserPool. Everything is set up so far, and I can SignUp/SignIn using Google, which creates the new user. I'm using the PreSignUp Lambda trigger to Link an existing user or create a new native one if there's no existing one. Now I was expecting that the event.Request.UserAttributes['name'] contains the user's name as provided by Google or at least seeing the attribute in the id_token. But I see no possibility to get those values at the moment. We started using Cognito just as the store for username/password, and none of the userAttributes are filled nor marked as required.
I have set up the Google integration with the following scopes:
.../auth/userinfo.email
.../auth/userinfo.profile
openid
In the UserPoolClient I:
marked name as read- and writeable attribute (along with others)
Checked the following allowed OAuth scopes email, openid, and profile. Those are also defined in the Web-Client in charge of the OAuth flow.
In the Federation section, I configured the attribute mapping:
Testwise, I mapped the name attribute to a custom attribute I used to test stuff. But neither this nor the mapping name to name worked.
Payload I get in the event:
{{PreSignUp_ExternalProvider .... Google_11...} {map[cognito:email_alias: cognito:phone_number_alias: email:m...#...m email_verified:true] map[] map[]} {false false false}}
id_token content:
{
"at_hash": "..",
"sub": "52...",
"email_verified": true,
"iss": "https://cognito-idp.us-west-2.amazonaws.com/...",
"cognito:username": "52..",
"origin_jti": "..",
"aud": "...",
"identities": [
{
"userId": "11...",
"providerName": "Google",
"providerType": "Google",
"issuer": null,
"primary": "false",
"dateCreated": "1648828708886"
}
],
"token_use": "id",
"auth_time": 1648828717,
"exp": 1648830828,
"iat": 1648830228,
"jti": "...",
"email": "m...#...m"
}
access_token content:
{
"origin_jti": "02...",
"sub": "52...",
"token_use": "access",
"scope": "openid profile",
"auth_time": 1648828717,
"iss": "https://cognito-idp.us-west-2.amazonaws.com/....",
"exp": 1648829317,
"iat": 1648828717,
"version": 2,
"jti": "..",
"client_id": "...",
"username": "52..."
}
Now it's working, even though I cannot state the error. I recreated the whole test set again and ensured the correct values of the following:
attribute mappings
authorized scopes
Allowed OAuth scopes
Scopes selected by the frontend
I added the scopes email, profile, and openid to be sure, and now I get the name attribute within the pre signup trigger lambda and in the ID-Token.
I use AWS IoT-core Device Shadow REST API I have created an IAM user role and give all access
this is my API key and header and endpoint
URL: {{endpoint-url}}/things/thingName/shadow
Method: GET
Header: header pass with AWS signature
accessKey: "accessKey"
secretKey: "secretKey"
execute-api working fine this is API response
[
{
"id": 1,
"type": "dog",
"price": 249.99
},
{
"id": 2,
"type": "cat",
"price": 124.99
},
{
"id": 3,
"type": "fish",
"price": 0.99
}
]
but my IoT-core Shadow REST API not working
I follow this docs https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-rest-api.html
attached screenshot: https://i.stack.imgur.com/luBMa.png
I had the same issue, and the solution was to set the Service Name field in Postman AWS Signature settings used to sign the AWS Signature V4 auth header to iotdevicegateway as per the docs here: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/iot-data.html#IoTDataPlane.Client.get_thing_shadow
I'm trying to create an endpoint on Api Gateway that writes on AWS s3 bucket using a filename like data/$timestamp where timestamp is the $context.requestTimeEpoch variable that should be available in each request
My api gateway request integration is this
{
"type": "AWS",
"httpMethod": "PUT",
"uri": "arn:aws:apigateway:eu-west-1:bucketname.s3:path/data/{reqTimestamp}",
"credentials": "arn:aws:iam::xxxxx:role/ApiGatewayWriteToDemoS3Bucket",
"requestParameters": {
"integration.request.path.reqTimestamp": "context.requestTime"
},
"passthroughBehavior": "WHEN_NO_MATCH",
"timeoutInMillis": 29000,
"cacheNamespace": "5f92eajcg9",
"cacheKeyParameters": [],
"integrationResponses": {
"200": {
"statusCode": "200",
"responseParameters": {
"method.response.header.Access-Control-Allow-Origin": "'*'"
},
"responseTemplates": {
"application/json": null
}
}
}
}
However I get this error
Execution failed due to configuration error: Illegal character in path at index 49: https://bucketname.s3-eu-west-1.amazonaws.com/data/{reqTimestamp}
it worked only once, then I had an S3 authentication error but that's another story
You need to add a value in the URL Path Parameter configuration of the Integration Request in your API Gateway resource method, setting the name to reqTimestamp, and value to method.request.path.reqTimestamp.
This will map the value received by API Gateway in the URL to the path override parameter you defined.
I am trying to POST a json string to API Gateway and in turn have API Gateway send the JSON to an EC2 server.
My issue is I can't find good documentation from Amazon on how to accomplish this.
When I test the setup I get this
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Response><Errors><Error><Code>InvalidHttpRequest</Code><Message>The HTTP request is invalid. Reason: Unable to parse request</Message></Error></Errors><RequestID>1fa47f52-d75c-4ff8-8992-3eac11a79015</RequestID></Response>"
Which means very little to me. I assume it is an issue with API Gateway trying to send the request to EC2 and it can't so it generates this error. So perhaps I am setting up the EC2 AWS Service Proxy in API Gateway incorrectly. Which is likely because I have no idea what I am supposed to set 'Action' to right now I have it pointing to the EC2 instance, only cause i don't see any other place to put that info.
This really shouldn't be that hard I have successfully done this thing connecting to Lambda and have looked through all the documentation and all I can find is this: http://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-aws-proxy.html#getting-started-aws-proxy-add-resources
Which is less than helpful for this scenario. Any Ideas?
I think you confused AWS Service Proxy and HTTP Service proxy.
API Gateway can forward API calls to different type of backends:
- a lambda function
- an AWS Service (see http://docs.aws.amazon.com/apigateway/latest/developerguide/integrating-api-with-aws-services-s3.html for an example)
- an existing API, running on AWS or on premises (your use case)
When defining you API, be sure to define a POST verb and point the Endpoint URL to your EC2 instance URL
I just made a test using the JSON POST service available online at http://gurujsonrpc.appspot.com/ and it works as expected.
Here is the Swagger export of my test API.
{
"swagger": "2.0",
"info": {
"version": "2016-04-11T20:46:13Z",
"title": "test"
},
"host": "c22wfjg4d7.execute-api.eu-west-1.amazonaws.com",
"basePath": "/prod",
"schemes": [
"https"
],
"paths": {
"/": {
"post": {
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "200 response",
"schema": {
"$ref": "#/definitions/Empty"
}
}
},
"x-amazon-apigateway-integration": {
"responses": {
"default": {
"statusCode": "200"
}
},
"uri": "http://gurujsonrpc.appspot.com/guru",
"httpMethod": "POST",
"type": "http"
}
}
}
},
"definitions": {
"Empty": {
"type": "object"
}
}
}
I have stumbled upon a rather strange and, to my knowledge, undocumented behaviour of Amazon SNS. I am looking for a solution or settings to fix it.
SUMMARY
I have a SNS Topic, with an HTTPS subscription pointing to an Amazon API Gateway REST endpoint, backed with a Node.js Lambda function for executing the request.
Now, if I use SNS and Publish on the topic , the whole API Gateway mapping template gets ignored/short-circuited. The Lambda function ends-up receiving ONLY the original SNS JSON object.
However, if I use a web browser (or curl) to access the endpoint the API Gateway Mapping Translation gets called and the proper JSON data gets passed onto the Lambda function.
THE API Gateway Endpoint
The API Gateway (aka TheApi hereafter) is created with a sms resource under which a "path parameters" {phone}. Therefore, you can query https://TheApi/sms/111-222-3333 with either a POST or GET method.
Both methods have a generic Mapping Template which grabs all paths parameters, all headers parameters, all query parameters and the whole request body and translate that into one LARGE request body JSON object. Here is what the template looks like:
{
"resource-path" : "$context.resourcePath",
"http-method" : "$context.httpMethod",
"headers": {
#foreach($param in $input.params().header.keySet())
"$param": "$util.escapeJavaScript($input.params().header.get($param))" #if($foreach.hasNext),#end
#end
},
"query": {
#foreach($param in $input.params().querystring.keySet())
"$param": "$util.escapeJavaScript($input.params().querystring.get($param))" #if($foreach.hasNext),#end
#end
},
"paths": {
#foreach($param in $input.params().path.keySet())
"$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end
#end
},
"body" : $input.json('$')
}
This resulting object is then fed to the Lambda function as the event on which the lambda function operates. Here is the result of a simple API Gateway "Test":
Tue Feb 09 00:54:13 UTC 2016 : Endpoint request body after transformations:
{
"resource-path" : "/sms/{phone}",
"http-method" : "POST",
"headers": {
},
"query": {
},
"paths": {
"phone": "111-222-3333"
},
"body" : {"foo":"bar","Alice":"Bob"}
}
This endpoint and the called Lambda function work flawlessly when called from a Web Browser (or a curl call ). AWS Cloud Watch logs show that everything is good under the sun, the Lambda event received is the same as above, therefore the Mapping translation is called.
THE Problem
Now, if I use SNS and Publish on the topic (the one with the HTTPS subscription on the API Gateway endpoint listed at the top), the whole API Gateway mapping template gets ignored/short-circuited.
The Lambda function ends-up receiving ONLY the original SNS JSON object and none of that custom mapping I wrote. The Lambda function doesn't receive any information about the calling agent, the requested url, the headers.... nada!! Here what the Lambda event looks like, as shown in CloudWatch:
{
"Type": "Notification",
"MessageId": "d38077e1-406a-5122-8a57-38cecfc635fd",
"TopicArn": "arn:aws:sns:us-east-1:...:...",
"Subject": "Ceci est un test",
"Message": "Ceci est un message de test.",
"Timestamp": "2016-02-06T06:06:36.649Z",
"SignatureVersion": "1",
"Signature": "...",
"SigningCertURL": "...",
"MessageAttributes": {
"AWS.SNS.MOBILE.MPNS.Type": {
"Type": "String",
"Value": "token"
},
"AWS.SNS.MOBILE.MPNS.NotificationClass": {
"Type": "String",
"Value": "realtime"
},
"AWS.SNS.MOBILE.WNS.Type": {
"Type": "String",
"Value": "wns/badge"
}
}
}
As it can be seen, this JSON object is completely different.
FOOD for thoughts
Some may wonder: "Why I go to the trouble of making the API Gateway when I could forward SNS events directly to the Lambda functions?". The reason is quite simple, I need to attach additional information with the SNS message, in this case the phone number to send the message to. Using API Gateway I can create as many subscriptions to as many phone numbers without duplicating any code.
Others may wonder: "Why not using the SMS subscription built into SNS instead of making my own?". For one thing, I'm in Canada and Amazon SMS subscriptions no longer works in Canada. Secondly, I may wish to use another SMS service that Amazon's.
As it turns out, SNS topics can call Lambda functions directly. In which case, the SNS JSON object is exactly the same. Thus, it is as though AWS is detecting the HTTPS endpoint domain, resolving the underlying Lambda function and routing the call directly to the Lambda function without passing through the API Gateway services.
In fact, when I build another REST endpoint on another domain I control, I indeed receive the POST request with the SNS JSON body, which I can forward to the API Gateway endpoint and it gets translated just well.
Just like this:
{
"resource-path": "/sms/{phone}",
"http-method": "POST",
"headers": {
"Accept": "*/*",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "US",
"Content-Type": "application/json",
"Via": "1.1 c903e93e57c533ecd52152e4407a295e.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "Fy_dCf5yJbW1GOZWJMVJqhbz1qt6sLfNO0N33FqAtf56X1tB4py8Ig==",
"X-Forwarded-For": "69.65.27.156, 54.182.212.5",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"query": {},
"paths": {
"phone": "14184901585"
},
"body": {
"Type": "Notification",
"MessageId": "d38077e1-406a-5122-8a57-38cecfc635fd",
"TopicArn": "arn:aws:sns:us-east-1:...:...",
"Subject": "Ceci est un test",
"Message": "Ceci est un message de test.",
"Timestamp": "2016-02-06T06:06:36.649Z",
"SignatureVersion": "1",
"Signature": "...",
"SigningCertURL": "...",
"UnsubscribeURL": "...",
"MessageAttributes": {
"AWS.SNS.MOBILE.MPNS.Type": {
"Type": "String",
"Value": "token"
},
"AWS.SNS.MOBILE.MPNS.NotificationClass": {
"Type": "String",
"Value": "realtime"
},
"AWS.SNS.MOBILE.WNS.Type": {
"Type": "String",
"Value": "wns/badge"
}
}
}
}
CALL for help
Are there any hidden settings somewhere when I can make this SNS -> API Gateway -> Lambda work with the proper Mapping Translation?
The mapping template is applied based on the request's content type. If there is no content type specified in the request, it defaults to 'application/json'.
Based on your description, I would assume that your mapping template is set up with the content type 'application/json'. This works fine as long as the client doesn't specify a different content type in its request (which is the case for browsers for example).
Since SNS sends the request with a header 'Content-type: text/plain' (SNS Send Message Over HTTP), it doesn't match your mapping template's content type and hence will ignore it. To get working you could either change the content type in your current mapping or add another one which matches 'text/plain'.
For some more details you could also have a look here in the AWS forums: Default Content-Type for Mapping Template
Best,
Jurgen