Custom Origin Header not visible in the API - amazon-web-services

I have an application where the client is hosted on S3 with a CloudFront distribution. The API is behind an API Gateway with a WAF, and the client makes http requests to the API to fetch and post data.
I want to restrict the access to the API such that it's only available from the client, and it should return an error when someone tries to access the API directly.
The trick is that the API is exposed to a 3rd party, so I cannot use API Gateway authorizers, because they must have direct access.
I set up a Custom Origin Header (My-Secret-Header: 1234567890qwertyuiop) in CloudFront, and I thought that I could create a rule in WAF to allow requests with this header (plus the 3rd party based on other criteria, but that part is working well, and it's not an issue), and block everything else.
The problem is that My-Secret-Header never makes it to the WAF, and it doesn't get added to the http requests originated from the client application.
I also tried to add the custom header with Lambda#Edge, no success. I created heaps of logs in with Lambda#Edge, and the event.Records[0].cf.request.origin.s3.customHeaders shows My-Secret-Header (which is expected).
What is the best way to add a custom header to the client request, so that it would be possible to create a rule in WAF?

I want to restrict the access to the API such that it's only available from the client, and it should return an error when someone tries to access the API directly.
The short answer is: there is no way to do this. There is no way to tell if a request originates from JavaScript in the browser, a Postman call, a user typing the URL in the address bar, etc.
Custom Headers in CloudFront are not headers that are added onto API requests that the user makes from the served files. They are headers that CloudFront uses to retrieve the static source that it is serving. In the case that the source is in an S3 bucket, these custom headers are on the request that CloudFront uses to retrieve files from your S3 bucket.
Once a user has the files that CloudFront serves (HTML, CSS, JavaScript, assets, etc.), CloudFront is no longer a part of the process. Any API calls made on the frontend do not go through CloudFront.
There are a few very weak ways to do what you are trying to do, but all are easily bypassable and absolutely cannot be used when security is in any way necessary. For instance, you can make an API Key and hard-code it into the application, but it is completely exposed to anyone who can access the page. Same for hard-coded access key ID and secret access key.
Ultimately what you need is an authentication system, some way to make sure that users are allowed to make the API calls that they are making. I don't know if this fits your use case, but Amazon Cognito is an excellent service that handles user authentication and federation.

Related

How to securely access API Gateway from a frontend application hosted in AWS amplify?

I have the following:
A Vue.js frontend app hosted in AWS Amplify.
An API Gateway that triggers several Lambdas that make changes in a MongoDB hosted in an EC2 instance.
My idea is that the frontend approaches the API Gateway and GET/POST data.
The problem is that I would like to make the API Gateway accessible only from my App (nobody could make requests without authorization).
How should I handle it?
If I provide API Keys to the API Gateway, how do I inject them securely in the frontend app? Those will be accessible to anyone, right? If so, where should I put that API Key? Inside an .env file? Would that be secure enough?
Using API Gateway authorizers?
I've seen some examples where people place an intermediate backend in Amplify in order to do so, but I'd like to avoid that if possible.
This answer isn't prescriptive, but hopefully has enough detail and buzzwords to start you on your journey.
Your front-end requests will should an Authorization header with a JWT token from Cognito or your auth provider.
API Gateway can wire-up to Cognito (or you can wire-up a "custom authorizer") and only pass-through requests that have a valid token.
Your Lambda will know the user is validated (as a known user or guest) but must still determine if they are authorized to perform the action they're attempting. If using something like AppSync, you can pass the user's Authorization header though to the AppSync API and the resolvers can allow/reject based on the authorization rules in your schema. I'm not familiar with EC2 hosted MongoDB, but imagine you'll need to wire it up to your auth so it can behave similarly.
I wouldn't recommend API keys. You can't put them client-side, they need to be managed and rotated, you're letting the "lambda" have permissions instead of the "user", etc.
I do recommend Amplify. You may be able to ditch MongoDB and the EC2 (yuck, you don't want to manage that) for AppSync backed by DynamoDB. And if you also use Cognito, you can do much of the above with very little effort.
EDIT To address your comment:
While your website may be hosted on your servers (or your account on a cloud provider's servers), it runs on people's personal devices. The HTTP requests (e.g. REST) your server receives don't indicate they originated from your website and there is no 'link' that ties YOUR front-end to your backend. HTTP does have a Referer Header that indicates the webpage the request is associated with, but you can't trust it.
Because your site is public, your API will receive requests from anywhere and everywhere. There is no way to prevent that. You can put less expensive request handlers in front of your API handlers to catch and discard invalid requests (or return cached responses when appropriate).
Your server could require requests include a special header (e.g. an API-KEY) that only your website will include in requests. But anyone can look at your website code and/or the network traffic (even simply via the browser debugging tools) and learn about that secret header.
You can look into XSRF tokens. This is where the front-end provides a unique token when serving a page (usually in conjunction with a form), and it must be included when sending data back to the server or the data will be considered invalid.
Cognito / Amplify will generate tokens for GUEST/un-authenticated users as well. So they can be used for what you want. It doesn't guarantee the requests are coming from your websites's javascript, but it'd be annoying to work around.
You can use CORS in your server responses. That prevents other websites from directly calling your APIs. Your server would still be called and return data, but an unmodified browser will see the CORS header and throw away the data before making it available to the calling javascript.
At the end of the day if your APIs are on the public internet, anyone and everyone can poke them.

secure aws s3 objects (control access with authorizer like jwt, how a web app would normally do)

I need to secure my s3 bucket objects. In my web application I'm using aws-sdk to upload media to s3 bucket and get an http link back to access that object. This http link is public by default and I want to make it secure so that only authorized users can access the media. aws s3 allows to make the object private but it wont let anyone with the link access the object.
This link will be accessed from a mobile app where I dont want to use aws-sdk, Instead I want to execute some logic on aws side whenever someone tries to access the http link for the object.
What I would like to happen is, before the user gets access to s3 object, Some authorizer code would execute (like a jwt token authorizer) and depending on it user would be granted/denied access.
I'm currently looking into Amazon API Gateways, I believe they can be accessed as an http link and AWS Lambda could be used to secure them(where i would execute my jwt authorizer). Then these apis would have access to s3 internally.
If someone could point me in the right direction, If this is at all possible.
If I could use the same jwt token issued from my web-application to send along the request to Amazon API Gateway, that would be great.
I would make the bucket private, and place a CloudFront distribution in front of it. Using an Origin access identity to allow only CloudFront to directly access the S3 bucket.
Then to provide security I would use either CloudFront signed cookies, or Lambda#Edge with a custom JWT token validation.
The easiest solution to expose private objects in an S3 bucket is to create a pre-signed URL. Pre-signed URLs use the permissions from the service (which pre-signs the URL) to determine access and have only a limited duration in which they can be used. They can also be used to upload an object directly to S3 instead of having to proxy the upload through a lambda function.
For a download functionality and a smooth user experience, you can - for example - have a lambda function that generates a pre-signed URL and returns it as an HTTP 302 response, which should instruct the browser to automatically download the file from the new URL.
(Edit)
Following on what I've stated in the comments on this answer, if you're proxying the upload/download of the objects through services such as API Gateway or Lambda, you will be severely limited in the size of files that you are able to upload to S3. The payload size limit on an API Gateway is 10 MB and for requests to lambda your payload is capped at 6MB for synchronous invocations. If you want to upload something larger than 10 MB, you will need to use direct upload to S3 for which pre-signed URLs are the safest solution.
I know I am bit late here, but I wanted to give my opinion in case someone has the same problems.
Your mobile app should communicate with a server app (backend app) for authentication and authorization. let's say you are deploying your server app on AWS VPC. Now, it's simple to manage the files access by creating a policy which allow just your server app (IP, or VPC) to access the bucket. the authorization part will be managed on your application.

Simple lightweight authorization scheme for static website interacting with API Gateway + Lambda

I have a simple control panel hosted on AWS s3 as a static website. It simply has buttons that make AJAX POST requests to an API Gateway / Lambda endpoint and then displays the response. This Lambda function has authorization to launch new EC2 instances, terminate some instances, and run scripts on existing instances through SSM, so it's important that this control panel is password protected in some way.
Note that no sensitive data is actually transmitted so I don't think that using TLS is strictly required (and I'm not even sure how you'd do that on s3?). But the API requests made need to have some sort of authorization token.
I'm not really sure what to do here -- solutions like JWT seem quite overkill. I just want a single, static password that I can give to my friends so they can access this control panel and make valid requests. No users / registration required.
I think simply protecting the site itself isn't a solution because the API Gateway endpoint is still public (although maybe not -- can I set the API to only accept requests from the s3 site?). There needs to be some token sent with the POST requests that authorizes each individual request and the token needs to change to prevent replay attacks.
Thanks,
Two options to easily secure Api Gateway:
Simple Api Key: We can setup an Api Key and secure api with this. We just need to pass this key as X-API-Key to all the api calls. We can have users enter this.
Custom Authorizer with Basic Auth to secure Api Gateway. Users will then need to enter user id and password , which will be passed as Authorization header and will be validated by this custom authorizer.
We could do both these two, or either one of the two.

How to add default throttled API key for unauthenticated API Gateway requests to prevent abuse?

I'd like to add a default throttled API key for unauthenticated requests to prevent abuse.
How would I do this in API Gateway?
EDIT
To make it clearer what I need, how do I transform a request in API Gateway? Is this possible?
I would say using Cognito is the best way of authorizing API gateway.
If you want a default API key then you can go for custom API gateway authorizer. Please have a look on official documentation for the same here
You need to store the API Key in the Server Side of your application and shouldn't expose it to the Client Side (Although API Key is not considered as a security token, it can be used by malicious party to call your API).
There are couple of options you have based on the nature of your application consuming the API.
If it is a single page web application where front-end is hosted in S3, you can use AWS CloudFront to store the API Key in headers and forward it to the API Gateway, while also serving the frontend through the same CloudFront distribution. This will also remove the cross origin resource sharing problem between your web application and API Gateway.
If you have a web server, you can store the API Key at Web Server and use to proxy request to the API Gateway while setting the API Key header value.
Note: Don't use API Key for authentication which is not recommended.
This is how I would solve it.
Create Usage Plan with the throttle, burst and max limit on the request allowed.
http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/APIGateway.html#createUsagePlan-property
API Key:
Create API Key (createApiKey) and associate it (createUsagePlanKey) with Usage Plan already defined. That will allow the limit defined for the requests received.
http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/APIGateway.html#createApiKey-property
http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/APIGateway.html#createUsagePlanKey-property
Have a separate lambda to monitor the Generated API-Keys and cleanup once it is expired, so you will not flood API-Gateway with unused keys.
If you take it to CloudFront, you can create Self Signed URL, that will be valid for a given period of time. After that time limit URL will be invalid. This is to keep yourself time-limited for the user, so within the given timelimit, what resource they can access.
One more usecase, we worked on, you can authenticate the user only on certain urls with custom Authorizer. Any other urls that get invokes, will return unauthorized without any additional code.
Hope it helps.

How to secure the APIGateway generated URL?

I have a serverless backend that operates with APIGateway and Lambda. Here is my architecture:
Currently, anyone with my APIGateway's URL can query or mutate the data. How do I protect the URL, so that only the client(react app) can access it. So, here is my concern, anyone can open the network tab in chrome console and get my APIGateway's URL and can use it using curl or postman. I want to prevent that.
Solutions I had in my mind:
Set up a CORS, so that only the origin can access it. But, I have a different lambda that invokes this URL. So, CORS wont work out.
I am sure there are some methods with the APIGateway itself. I am not getting right search term to get it from AWS documentation. I would also like to know what are the best practices to prevent accessing the backend URL apart from the Client(React App)
Update after #Ashan answer:
Thank you #Ashan for the answer. In my case, I use Auth0, so custom authoriser should work for me. I just came across this https://www.youtube.com/watch?v=n4hsWVXCuVI, which pretty much explains all the authorization and authentication possible with APIGateway. I am aware that authentication is possible either by Cognito/Auth0, but I have some simple websites, that has form, whose backend is handled by APIGateway. I can prevent the abuse from scraping bots using captcha, but once the attacker has got the URL, header and request parameters, he can invoke that million times. One thing, we can do is having an API-Key, but it is a static string with no expiration. Once the headers are with him, he can abuse it. So, any idea, how to prevent this in APIGateway. If not any other service apart from AWS that I can look for? Would be glad, If I get an answer for this.
Currently API Gateway does not support private urls, so it will be publicly available.
To restrict access you need to use a authorizer to authenticate and authorize the request using IAM policies. There are two options available at the moment.
IAM authorizer
Custom authorizer
If your authentication flow can directly (AWS STS, IAM user access keys or roles) or indirectly(Using AWS Cognito Userpools or any other SSO provider) can get temporary security credentials, then you can use IAM authorizer. From API Gateway side no code involved and its a matter of selecting the IAM check box for each API Gateway resource. You can use the API Gateway SDKs to invoke API Gateway requests where the SDK will handle the heavy liftings in setting up authentication headers.
If you use your own authentication mechanism, then you can write a seperate Lambda function to validate the tokens. This Lambda function name can be specified at API Gateway with the http hearder name to access the custom token to verify the requests.
To control API usage by authorized consumers, using API Key is the only way native to AWS at the moment.
Since you are using S3 for the react app hosting, you can further reduce the attack surface by using AWS WAF and CloudFront infront your application stack. The API Key can be added to CloudFront headers to forward to your APIGateway origin and since CloudFront and APIGateway communication happens using SSL, its nearly impossible for someone to find the API key. Using AWS WAF you can limit malicious access for common attacks. This includes rate based blocking to limit someone from repeatedly invoking the API.