I’ve recently had the requirement to build a secure backend API layer in AWS that can be accessed both internally by services in AWS, but also by developers locally. We took the view that having the developers use a VPN to connect to the VPC was not the best idea, and that we should be able to use AWS to handle the authentication.
In case you’re not familiar (not sure how you wouldn’t considering you made it to this post) but anyway…. AWS API Gateway provides the ability to use IAM permissions to access the API Gateway (https://docs.aws.amazon.com/apigateway/latest/developerguide/security_iam_service-with-iam.html), and therefore block the request from making it to your backend Lambda or Load Balancer. This is really useful and used a lot in Serverless where people are using things like AWS Cognito for authentication.
Unfortunately, having server-side code call the AWS API Gateway isn’t that easy. It requires signing of requests, and the AWS SDK’s don’t make that easy to access. The signatures are a mixture of the hashes of some of the data in the request, HMACs and encryption using your AccessKey/Secret.
In this post, I hope to provide a simple example of how to call those APIs from Javascript/Node.
Signed Requests
In order to call an API Gateway endpoint that is secured with IAM permissions, you’ll need to create a “signature” in the AWS Signature 4 format (https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html).
Once you’ve created that signature, you can either append it to the querystring, or add it as a header. There will be multiple additional properties to add
- X-Amz-Security-Token
- X-Amz-Date
- Authorization
The code
The code bit of code is using the aws
4 npm package (https://github.com/mhart/aws4). This allows you to pass the credentials, and the pertinent parts of the request you want to make, and it will generate the right signatures in a headers object for you.
It’s an incredibly easy to use library, and I can’t thank Michael Hart enough for his efforts, give him a follow and, if you can, consider sponsoring his efforts.
Now, I’m not a javascript developer, so this may all be completely wrong, but I feel I can understand it, and reason about it, which was the goal, not a complete implementation in an application.
import fetch from 'node-fetch';
import aws4 from 'aws4';
const opts = {
method: "GET",
host: "",
path: "",
port: 0,
service: 'execute-api',
payload: "",
region: 'eu-west-2'
};
aws4.sign(opts);
const response = await fetch("https://" + opts.host + opts.path, {
headers: opts.headers
});
console.log(await response.json());
What this code does is build up an object that includes some important things that AWS signatures care about.
method
of the request (GET, POST, PUT, DELETE, etc.)host
of the request (the domain name) Note that this works when using custom domains for AWS API Gateway too.path
the full path with querystring of the endpointport
you’ll be calling if it’s non-default (if it’s default, omit it, or use0
)service
within AWS you’ll be calling. For AWS API Gateway you’ll needexecute-api
payload
for the body you’ll be passingregion
of the API Gateway you’ll be hitting (this is required)
That object is then passed to aws4
‘s sign
method, which appends a headers
object which includes the headers required, including a host
header. By default this will use the credentials from your process.env
, or you can pass them in. In a future post, I’ll expand this to include Assumed roles.
We can then attach these headers to the node-fetch
method, and pass in the url. For this, I’ve chosen to hardcode https
as, well, it’s 2022, and then take the properties from the opts
object so everything maps nicely.
Finally, we can write the output from the API. If your signing hasn’t work, you’ll get a JSON object that says forbidden
.
Conclusion
Finding all this information was quite hard, and as javascript isn’t my primary language, I found it hard to understand a few things. That said, I feel this is pretty neat solution and quite succinct. I hope this helps some people, and if you have feedback on the code, please do let me know.
You can find the full implementation as a Node program with Assumed roles here: https://gist.github.com/martinjt/98739421411ace776e84d53d331b2ddf
Leave a Reply