I am trying to implement custom authorizer lambda function via java SDK. Can somebody tell me the exact format of the JSON response that is expected from my lambda function. Also in which format i should return the output (JSON object or policy object).
{
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Resource": [
"arn:aws:execute-api:us-east-1:1234567:myapiId/staging/POST/*"
],
"Effect": "Allow"
}
]
},
"principalId": "User123"
}
this is the format i am providing in output in JSONObject format but getting error
Mon Apr 10 09:42:35 UTC 2017 : Endpoint request body after
transformations:
{"type":"TOKEN","authorizationToken":"ABC123","methodArn":"arn:aws:execute-api:ap-southeast-1:007183653813:ohlqxu9p57/null/GET/"}
Mon Apr 10 09:42:36 UTC 2017 : Execution failed due to configuration
error: Authorizer function failed with response body:
{"errorMessage":"An error occurred during JSON serialization of
response","errorType":"java.lang.RuntimeException","stackTrace":[],"cause":{"errorMessage":"com.fasterxml.jackson.databind.JsonMappingException:
JsonObject (through reference chain:
com.google.gson.JsonObject[\"asString\"])","errorType":"java.io.UncheckedIOException","stackTrace":[],"cause":{"errorMessage":"JsonObject
(through reference chain:
com.google.gson.JsonObject[\"asString\"])","errorType":"com.fasterxml.jackson.databind.JsonMappingException","stackTrace":["com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:210)","com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:177)","com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:199)","com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:683)","com.f
[TRUNCATED] Mon Apr 10 09:42:36 UTC 2017 :
AuthorizerConfigurationException
Any help would be great. Thanks in advance
The issue you are facing is Lambda framework related.
Essentially, Lambda will invoke the handler function and pass a serialized JSON.
public class LambdaCustomAuthorizer implements RequestHandler<AuthorizationRequestDO, Object> {
public Object handleRequest(AuthorizationRequestDO input, Context context) { }
}
When you work with custom authorizer, API gateway passes following JSON to your lambda function:
{
"type":"TOKEN",
"authorizationToken":"",
"methodArn":"arn:aws:execute-api:::///"
}
you should have a custom DO AuthorizationRequestDO
which is a POJO::
public class AuthorizationRequestDO {
String authorizationToken;
String methodArn;
public String getAuthorizationToken() {
return authorizationToken;
}
public void setAuthorizationToken(String authorizationToken) {
this.authorizationToken = authorizationToken;
}
public String getMethodArn() {
return methodArn;
}
public void setMethodArn(String methodArn) {
this.methodArn = methodArn;
}
#Override
public String toString() {
return "AuthorizationRequestDO [authorizationToken=" + authorizationToken + ", methodArn=" + methodArn
+ ", getAuthorizationToken()=" + getAuthorizationToken() + ", getMethodArn()=" + getMethodArn() + "]";
}
}
Your Resource property should be a single string value.
{
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:us-east-1:1234567:myapiId/staging/POST/*",
"Effect": "Allow"
}
]
},
"principalId": "User123"
}
Related
I have a very simple step function.
Start -> Lambda Function -> DynamoDB PutItem -> End
The Lambda Function Exports This:
{
"statusCode": 200,
"responseTime": 0.5
}
This data is sent to DynamoDB:PutItem
API Parameters of DynamoDB:PutItem
{
"TableName": "MyTable",
"Item": {
"statusCode": {
"S": "$.statusCode"
}
}
}
The issue is with $.statusCode this is wrong as it just inputs the actual string "$.statusCode" instead of its value.
How do I pass across the statusCode from my Lambda function to this next step?
I found the answer myself.
{
"TableName": "MyTable",
"Item": {
"statusCode": {
"S.$": "$.statusCode"
}
}
}
You need to add a .$ after the key
I am trying to sign my HTTP request from my Lambda function to access my Elasticsearch endpoint as described here. I dont know is there a better way for doing this but I am getting status 403 error with the following response. How can i troubleshoot this error and identify the problem with my signature?
{
"message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details."
}
My Lambda function has IAM role (ROLE_X) with below permissions.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"es:ESHttpPost",
"es:ESHttpPut",
"dynamodb:DescribeStream",
"dynamodb:GetRecords",
"dynamodb:GetShardIterator",
"dynamodb:ListStreams",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
I am also allowing access to this role in my Elasticsearch domain by providing ROLE_X's arn as Custom Access Policy.
Here is my lambda function written in NodeJS
'use strict';
var AWS = require('aws-sdk');
var region = 'eu-central-1';
var domain = 'search-mydomain-XXXX.eu-central-1.es.amazonaws.com';
var index = 'images';
var type = 'image';
var credentials = new AWS.EnvironmentCredentials('AWS');
exports.handler = (event, context, callback) => {
var endpoint = new AWS.Endpoint(domain);
var request = new AWS.HttpRequest(endpoint, region);
request.headers['host'] = domain;
request.headers['Content-Type'] = 'application/json';
// Content-Length is only needed for DELETE requests that include a request
// body, but including it for all requests doesn't seem to hurt anything.
request.headers['Content-Length'] = Buffer.byteLength(request.body);
request.path += index + '/' + type + '/';
let count = 0;
event.Records.forEach((record) => {
const id = JSON.stringify(record.dynamodb.Keys.id.S);
request.path += id;
if (record.eventName == 'REMOVE') {
request.method = 'DELETE';
console.log('Deleting document');
}
else { // record.eventName == 'INSERT'
request.method = 'PUT';
request.body = JSON.stringify(record.dynamodb.NewImage);
console.log('Adding document' + request.body);
}
// Signing HTTP Requests to Elasticsearch Service
var signer = new AWS.Signers.V4(request, 'es');
signer.addAuthorization(credentials, new Date());
// Sending HTTP Request to Elasticsearch Service
var client = new AWS.HttpClient();
client.handleRequest(request, null, function(response) {
console.log('sending request to ES');
console.log(response.statusCode + ' ' + response.statusMessage);
var responseBody = '';
response.on('data', function(chunk) {
responseBody += chunk;
});
response.on('end', function(chunk) {
console.log('Response body: ' + responseBody);
});
}, function(error) {
console.log('ERROR: ' + error);
callback(error);
});
request.path = request.path.replace(id, "");
count += 1;
console.log("COUNT :" + count);
});
callback(null, `Successfully processed ${count} records.`);
};
You can use the http-aws-es library. It uses aws-sdk to handle the signing of requests before accessing your ES endpoint. You can try the following changes to your code using http-aws-es.
var es = require('elasticsearch');
var AWS = require('aws-sdk');
AWS.config.update({
credentials: new AWS.EnvironmentCredentials('AWS'),
region: 'yy-region-1'
});
const client = es.Client({
hosts: ['https://xxxx.yy-region-1.es.amazonaws.com/'],
connectionClass: require('http-aws-es'),
awsConfig: new AWS.Config({region: 'yy-region-1'})
});
await client.search(....)
{
"changeType": "created,updated,deleted",
"notificationUrl": "https://somewebsite.com/mswebhook/graph/notifications",
"resource": "/teams/{team-id}/channels/{channel-id}/messages",
"expirationDateTime": "2019-12-30T09:41:21Z",
"clientState": "",
"includeResourceData": true,
"encryptionCertificate": "TUlJQklUQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FRNEFNSUlCQ1FLQ0FRQlhFL0xOZjVYYVY4ZFlXbWFuSU9qUwphSnhyUG1tN0dHN0lWOGYwV2RjZDltbUkvaTVXQzhCNFBYaVBKREtKTnVFL2QvMXo5TndSbjR1UFBQQm9sN1BWCmdudVJ6UmZJSVEvUW1TZjYwUi9LUnYybHVlZzVwNW84Qk5UNDczdTRCbVZJVHNBWlF1eG43RGdnUi9JZlQzSVgKelpMdzNXWitDYzJyS1crblpmbUwwU0NiN21EL3RTZ3VqQUJPVEVvS0xBODkvNWhUamhuNzcvWHJzUTYrV1hMVgpKL3NtVDJvU3R3TzBnYXRIRUkwWkRSL0VYNkdVWDRRRVI4M0puS2hPSDJrZzZQNlZUMDFGUEw0Nk5WemZydWE4ClVob052Z0VlQStPY2xBOU5mUUpJQnVKUVFTbGZ0TmhYODJtVEdhVEtBTFN1bWZieUNac1ljQ3l4MXQ5RXF6Qi8KQWdNQkFBRT0=",
"encryptionCertificateId": "id",
"lifecycleNotificationUrl": "https://somewebsite.com/mswebhook/graph/notifications"
}
when this request is sent from postman It is giving
{
"error": {
"code": "InvalidRequest",
"message": "System.Security.Cryptography.CryptographicException: Cannot find the requested object.\r\n\r\n at System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr)\r\n at System.Security.Cryptography.X509Certificates.X509Utils._QueryCertBlobType(Byte[] rawData)\r\n at System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromBlob(Byte[] rawData, Object password, X509KeyStorageFlags keyStorageFlags)\r\n at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[] rawData)\r\n at Microsoft.Graph.Encryptor.Encryptor.ValidateCertificate(String certificate, String& errorMessage) in X:\\bt\\1070414\\repo\\src\\Dev\\Notifications\\Notifications.Azure\\Encryptor\\Encryptor.cs:line 293",
"innerError": {
"request-id": "e578bdba-2c47-41d7-a0da-5395b05e4203",
"date": "2019-12-31T06:57:59"
}
}
}
without includeResourceData property, subscription and notifications are working fine.
I am facing problem while choosing encryptionCertificate property.Currently I am passing Base64bit encoded 2048 bit RSA public key (online generated) in encryptionCertificate.
I'm trying to implement custom authorization on API Gateway, that would check user's permissions on each particular endpoint behind it, by reading them from the DynamoDB.
I associated the authorizer with the method in question (screenshot below)
The authorizer seems to be working ok, and it returns policy that looks fine to me (have a look underneath)
{
"policyDocument" : {
"Version" : "2012-10-17",
"Statement" : [
{
"Action" : "execute-api:Invoke",
"Effect" : "Deny",
"Resource" : "arn:aws:execute-api:us-east-2:111111111111:mkvhd2q179/*/GET/api/Test"
}
]
},
"principalId" : "*"
}
However, regardless of the Effect authorizer returned inside the policy document, API Gateway still let's all requests pass. I get the status 200 as well as the result set from the API endpoint underneath.
Any ideas as to why the API Gateway would ignore the policy?
P.S.
I tried with the explicit principalID (the username/subject from the token) prior to putting an asterisk there. It behaved the same.
P.P.S
Here's completely dummed down version of my Lambda function, currently set up to allways return Deny as policy Effect...
public class Function
{
public AuthPolicy FunctionHandler(TokenAuthorizerContext request, ILambdaContext context)
{
var token = request.AuthorizationToken;
var stream = token;
var handler = new JwtSecurityTokenHandler();
var jsonToken = handler.ReadToken(stream);
var tokenS = handler.ReadToken(token) as JwtSecurityToken;
return generatePolicy(tokenS.Subject, "Deny", "arn:aws:execute-api:us-east-2:111111111111:mkvhd2q179/*");
}
private AuthPolicy generatePolicy(string principalId, string effect, string resource)
{
AuthPolicy authResponse = new AuthPolicy();
authResponse.policyDocument = new PolicyDocument();
authResponse.policyDocument.Version = "2012-10-17";// default version
authResponse.policyDocument.Statement = new Statement[1];
authResponse.principalId = "*";
Statement statementOne = new Statement();
statementOne.Action = "execute-api:Invoke"; // default action
statementOne.Effect = effect;
statementOne.Resource = resource;
authResponse.policyDocument.Statement[0] = statementOne;
return authResponse;
}
}
public class TokenAuthorizerContext
{
public string Type { get; set; }
public string AuthorizationToken { get; set; }
public string MethodArn { get; set; }
}
public class AuthPolicy
{
public PolicyDocument policyDocument { get; set; }
public string principalId { get; set; }
}
public class PolicyDocument
{
public string Version { get; set; }
public Statement[] Statement { get; set; }
}
public class Statement
{
public string Action { get; set; }
public string Effect { get; set; }
public string Resource { get; set; }
}
TL;DR; Remove/change/check the "Resource Policy" set in the Gateway.
I had a similar problem.
Somehow I had a "allow * principal access to * resources" policy set in the Resource Policy on the Gateway which was being combined with whatever the Authorizer was returning. I ended up removing all resource policies and let the Authorizer decide.
I had this problem as well. Turns out that making the request from the API Gateway console screen (e.g. https://us-west-2.console.aws.amazon.com/apigateway/) doesn't appropriately invoke the authorizer.
I'm guessing its because your console session has its own IAM policy, which interferes with the authorizer policy.
The solution was to manually CURL the endpoint outside of the API Gateway console.
Additionally, do not forget to deploy your API after you make your changes! Otherwise your changes won't be taking effect:
I had a similar issue. The way our API gateway resource policy was set up to allow us to execute any API in the account level (arn:aws:execute-api:us-east-1:xxxxx:*).
Even though implemented fine-grained access where we return a policy to allow only particular arn the API gateway resource policy was taking precedence. So I have removed the resource policy and redeployed the API and it was allowing that particular API and denying the others. OR u can try vice versa based on how you configure your Effect and policy statement.
Initial Resource Policy:(I removed and redeployed)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:us-east-1:xxxxx:*",
"Condition": {
"IpAddress": {
"aws:SourceIp": [
"xx.xx.xx.xxx/24"
]
}
}
}
]
}
Final Lambda Auth Policy returned:
{
"principalId": "xxxxxxxxxx",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"execute-api:Invoke"
],
"Resource": [
"arn:aws:execute-api:us-east-1:xxxxx:bxxxx/*/POST/*/someresource"
]
}
]
}
}
The AWS Documentation is confusing... it seems that you still need to use the "callback" to do the trick and is not enough to return an "Deny" policy...
exports.authorizer = (event, context, callback) => {
if (invalidToken) {
callback("Unauthorized", null);
}
// create a valid policy
return validPolicy
}
I would like to invoke Lambda B (US_EAST_1) within Lambda A (US_WEST_1) across regions. So Lambda A and B are situated in different regions. I have deployed both Lambdas to their regions and adjusted the IAM role:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction",
"lambda:InvokeAsync"
],
"Resource": "*"
}
]
}
This is the Java code of Lambda A:
ObjectMapper mapper = new ObjectMapper();
String jsonString = "";
System.out.println("Start CreateLambdaProxy");
try {
jsonString = mapper.writeValueAsString(input);
String LambdaFunctionName = "arn:aws:lambda:us-east-1:bladiebla";
AWSLambdaClient lambdaClient = new AWSLambdaClient();
lambdaClient.withRegion(Regions.US_EAST_1);
InvokeRequest invokeRequest = new InvokeRequest();
invokeRequest.setFunctionName(LambdaFunctionName);
invokeRequest.setPayload(jsonString);
context.getLogger().log("Before Invoke");
ByteBuffer payload = lambdaClient.invoke(invokeRequest).getPayload();
context.getLogger().log("After Invoke");
context.getLogger().log(payload.toString());
context.getLogger().log("After Payload logger");
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("After");
I am able to invoke Lambda B with a test function. However, if I invoke it from Lambda A, nothing happens.
START RequestId: cc09cb06-ffb4-11e8-9c6b-7f1eee4bef0e Version: $LATEST
CreateLambdaProxy
Start CreateLambdaProxy
Before InvokeEND RequestId: cc09cb06-ffb4-11e8-9c6b-7f1eee4bef0e
REPORT RequestId: cc09cb06-ffb4-11e8-9c6b-7f1eee4bef0e Duration: 25061.35 ms Billed Duration: 25100 ms Memory Size: 128 MB Max Memory Used: 92 MB
I then tried invoking another Lambda that is in the same region as Lambda A (so no region difference) but that also did not give any result so I think it doesn't have anything to do with the region.