I currently have created an API Gateway using AWS. This gateway requires an API Key that is stored on AWS. From what I've seen, the correct way to pass this key in the header is to use x-api-key. For continuity, I was hoping there would be a way to pass the key in the header of the POST using key Authorization instead of x-api-key. From what I've read though, since this gateway is hosted in aws, x-api-key is the only way to pass the key. Is there a workaround to this?
Lambda Authorizer
IMO it is easier if you just stick with the convention, it's actually a bit of work to change this. You need to create a Lambda Authorizer. This will be a Lambda function that will return the iam policy for a given request to your gateway, but also an optional context. This context can contain what API key should be used, although you need to keep in mind that your Lambda function does the Authorization, this key is used only in the usage plan I believe.
So to summarize when you create your authorizer you will pick the header that is used Authorizaion in your case. In you Lambda function you reference this header here: event.authorizationToken. Using that header you construct your iam policy, this may mean you need to manually look up this API key if it does have access to your API. Your Lambda function will also need to set the the api-key to the $context.authorizer, which contains the api key you want to use, which will be event.authorizationToken in your case. That will apply the UsagePlan to your API.
CloudFront
Another option is to create a CloudFront distribution in-front of your API and add the header in manually yourself. This will probably cost more though because you will need a CloudFront distribution and then your Lambda#Edge will execute with every request. As opposed to a Lambda Authorizer which can be cached. You can find an example here.
Related
AWS have recently released the Lambda function URLs feature which allows a function to be invoked via a URL.
I would like to allow my function to be invoked via a URL but only via CloudFront.
I don't want people to be able to bypass CloudFront and invoke the function directly.
Is there a way to configure this? I am aware that I can restrict the function URL by setting the auth type to AWS_IAM but am not clear on how I then allow CloudFront to call it.
Currently, the only option I see is quite similar to how you would protect an ALB in a way that access is restricted to CloudFront:
Configure CloudFront to add a custom HTTP header to requests that it sends to the Application Load Balancer lambda function URL.
Configure the Application Load Balancer Lambda to only forward process requests that contain the custom HTTP header.
My thoughts on approaches that may not work when using lambda function URLs:
IAM auth (since I see no way to sign these requests origination from CloudFront, maybe that will change in the future when lambda function URLs become a first class citizen like S3-origins)
restricting access via security groups (because there are no SGs for lambda func URLs)
Confirmed with AWS support that there is currently no way to do this: "[with the] current design of CloudFront, it is not possible for CloudFront to relay IAM authenticated requests to Lambda URL origin.." There is a feature request for this (but they did not provide a timeframe for implementation and release) but hopefully they provide a solution similar to, and as straight forward as, the CloudFront integration with S3 via the Origin Access Identity.
Here's what I did to make it work on my side :
go to the CloudFront page
click on create a new distribution
In section Origin domain you have to paste in your lambda function URL
Make sure to adjust the caching policy depending on what your lambda function consumes
You might want to create a dedicated policy in you want the cache key to depend on the query string, the cookies, etc...
For my use case I created a new policy to take into account the query string
Let's say I have an AWS API Gateway. I have a resource in said gateway that requires the use of a gateway API Key. I would like my server to know which of my various keys are being used to authenticate (just for logging; there's no access control happening based on this).
How do I include the name of the gateway API Key in the request that is being sent to the server?
Firstly, you mentioned logging which keys are used for authentication. API GW API Keys are intended to be used with usage plans, not authentication/authorization (link).
For user authentication and authorization, don't use API keys. Use an IAM role, a Lambda authorizer, or an Amazon Cognito user pool.
I think it is because of this reason, AWS does not forward the key further to the backend. Furthermore, if you have used one of the authorizers like Lambda or Cognito, your backend will have a way to log the authenticated identity.
If you really want to log the API keys though, I think you can use a mapping template to explicitly tell API GW to forward the x-api-key header.
API keys attached to an API gateway have to be unique. From the docs:
API key values must be unique. If you try to create two API keys with different names and the same value, API Gateway considers them to be the same API key.
The is no such header to specify the name of the key. You can create your own custom header where you would add this information, but nothing would guarantee that the correct name is sent with a given API key. You would probably want to implement a call using the AWS SDK from your server to retrieve the name of the API key.
In case you are using a Lambda authorizer function, you can fetch the name of the API key in this function and forward it as a header to the backend server.
A use case that I currently have to set up with AWS has several constraints:
I have to use a Cognito User Pool as an authorizer with my API resources (so far no problem)
I also have to make the API only accessible through a CloudFront distribution (like an OAI for an S3 bucket)
To set up this use case, I was going to use a Lambda#Edge attached to the CloudFront to generate a request signature (SignV4), on the other hand I was going to use a Custom Authorizer, which would verify the signature of the request generated with the Lambda#Edge in addition to verifying the authentication with Cognito.
Only I don't know exactly how to do it, especially how to use Cognito in a custom authorizer (AWS SDK).
What do you think of this solution?
The only other solution I found is this one, but to me it should not be the usage for an API key, and the signed request solution seems, to my opinion, a better choice.
Do you know a better one?
Any help would be appreciated,
Thank you
Is it possible to get the source IP address of a request to your API Gateway in a 'Custom Authorizer' lambda function?
This is definitely possible with the actual integration of your API Gateway to a lambda function. Though it does not seem to be possible to get the requester's IP address in a Custom Authorizer function.
My goal is to do rate based blocking directly in APIG. A similar solution is described here. However, as I am only restricting access to one or two APIG endpoints, I'd rather do this in a custom authorizer function which simply adds the source address to the deny policy of the APIG when it reaches a rate limit.
EDIT: To clarify some potential confusion. I understand that I could do this through the regular integration as mentioned above, and in this other post. But I am trying to utilize the custom authorizer functionality, so that I don't have to write the same rate limiting code in all of my lambda functions.
You should look at
event.requestContext.identity.sourceIp
it will contain the original client IP.
When creating the Authorizer on the "Identity Sources" section add
Context: identity.sourceIp
and enable caching (default is 300 sec).
That way your authorizer lambda will not be called for each request, because it will cache the returned policy for that IP.
You can experiment yourself if you add logging of passed event parameter (just don't forget about caching, not all calls to API Gateway fire the authorizer lambda).
BTW, don't use "X-Forwarded-For" look at my comment on another #binshi's answer.
Custom Authorizers can now use so-called Enhanced Context.
You should be able to use the appropriate context variable to get that information (e.g. $context.identity.sourceIp).
You may also be able to use API Mappings, but I haven't tested that.
You can get the source ip as well as any proxy server ip in
events['headers']['X-Forwarded-For']
I'm currently using a Cognito User Pool as an authorizer for an API Gateway endpoint, through to a Lambda function.
Can I pass the Integrated Request on to Lambda and SECURELY allow or deny from inside Lambda based on a custom attribute?
Mapping:
"administrator" : "$context.authorizer.claims['custom:administrator']",
Lambda handler:
boolean isAdmin = Boolean.parseBoolean(request.getContext().get("administrator"));
if(isAdmin) etc...
To be clear, a user that is NOT an administrator should not have access to the same API endpoints that and Administrator does..
Do I need to do anything else before/after this point?
I am sending the initial request to the API Gateway with Javascript after the user has logged into Cognito, by including the Authorization: JWToken header.
Do I need to verify the signature of the token in the Lambda function? I presume that API Gateway has already done that.
Is there a better way to manage this in terms of security?
Ideally I would like to be able to limit access to the API Endpoint based on GROUPS within the User Pool, however I don't think this is possible.
The Groups documentation talks about limiting access/permissions via AWS Identity and Access Management. If I go down this path, how do I make a request to the API Gateway? Do I still use the JWToken Authorization header, and use Cognito as the Authorizer in API Gateway?
The custom attribute/claim support included with Cognito user pools is secure and can be used for use cases such as this when used correctly. There are a couple of caveats.
First, ensure that users aren't able to modify the custom attribute themselves. When adding the customer attribute, do not mark the attribute as mutable. Also, custom attributes can be can be marked as readable or writable for each application. For this use case, you'll want to set the attribute as readable for the application the users have access to. Details about custom attributes can be found here.
The other caveat is to ensure that your request body can never by-pass your mapping template which could allow an attacker a way to directly set the administrator attribute being passed to your Lambda function. To do this, edit your integration request and set "Request body passthrough" to "Never".
There are other alternatives you could use for this use case. The cleanest approach is to provide a completely separate API for your administrators. Then you can use a separate Cognito user pool for your administrators, or you could use IAM users or groups.