AWS API Gateway with AWS IAM authorization
Abstract
AWS API Gateway is widely used in various integrations and architectures. One common use case for enhancing its authorization level is by incorporating an authorizer lambda or integrating it with AWS Cognito.
However, there is another interesting option that allows to control access to API method level using AWS IAM auth type. In this post we will create and deploy infra with terraform, secure endpoint with AWS IAM and will also take a look on sigv4 client algorithm.
Target Architecture for this post:
We are creating API Gateway with resource ../path1
that has method GET
: for proxying all requests to public https endpoint. For this post we will use amazon public endpoint with ip ranges - https://ip-ranges.amazonaws.com/ip-ranges.json
This endpoint will be secured with type AWS_IAM. So only instances that have attached role or CLI programmatic users with proper permissions can access this endpoint.
Code snippets are available on github:
Creating infrastructure with Terraform
API endpoint provisioning with terraform using openapi spec:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
resource "aws_api_gateway_rest_api" "sigv4_rest_api" {
body = jsonencode({
openapi = "3.0.1"
info = {
title = "SigV4 verification"
version = "1.0"
}
paths = {
"/path1" = {
get = {
security : [
{
sigv4 : []
}
]
x-amazon-apigateway-integration = {
httpMethod = "GET"
payloadFormatVersion = "1.0"
type = "HTTP_PROXY"
uri = "https://ip-ranges.amazonaws.com/ip-ranges.json"
}
}
}
}
}
)
description = "API Gateway"
name = "Regional API GW with 'AWS IAM' auth type"
endpoint_configuration {
types = ["REGIONAL"]
}
}
Now, let’s proceed to define AWS IAM authorization at the API Gateway level, alongside the path declaration. In the context of API Gateway, this authorization type is known as “awsSigv4”.
1
2
3
4
5
6
7
8
9
10
components = {
securitySchemes = {
sigv4 = {
type = "apiKey"
name = "Authorization"
in : "header"
x-amazon-apigateway-authtype = "awsSigv4"
}
}
}
At this moment we have created API GW, single path1
resource with GET
method that proxies all incoming requests.
Now lets define Deployment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Deployment
resource "aws_api_gateway_deployment" "prod-deployment" {
rest_api_id = aws_api_gateway_rest_api.sigv4_rest_api.id
triggers = {
redeployment = sha1(jsonencode(aws_api_gateway_rest_api.sigv4_rest_api.body))
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_api_gateway_stage" "PROD" {
deployment_id = aws_api_gateway_deployment.prod-deployment.id
rest_api_id = aws_api_gateway_rest_api.sigv4_rest_api.id
stage_name = "PROD"
xray_tracing_enabled = true
}
API gateway is deployed into stage PROD
it has autogenerated URL and is available for accessing:
Calling API GW without credentials:
As we have secured our resource and its HTTP method with the AWS IAM authentication type, when making a regular HTTP call, we will receive a 403 Forbidden
status code along with an error message.
1
2
3
{
"message": "Missing Authentication Token"
}
To authorize on API GW we need IAM programmatic user with proper permissions, let’s create it with Terraform:
Take a look at Resource = "arn:aws:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.sigv4_rest_api.id}/${aws_api_gateway_stage.PROD.stage_name}/GET/path1"
declaration it is dynamically generated with proper params from previously created resources.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Programmatic IAM user
resource "aws_iam_user" "user" {
name = "api-caller"
path = "/"
}
resource "aws_iam_user_policy" "apigw_invoke_policy_inline" {
name = "allow-invoke-get-method-policy"
user = aws_iam_user.user.name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"execute-api:Invoke",
]
Effect = "Allow"
Resource = "arn:aws:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.sigv4_rest_api.id}/${aws_api_gateway_stage.PROD.stage_name}/GET/path1"
},
]
})
}
As a result we will create IAM user
api-caller
with following allow-invoke-get-method-policy
policy:
1
2
3
4
5
6
7
8
9
10
11
12
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"execute-api:Invoke"
],
"Effect": "Allow",
"Resource": "arn:aws:execute-api:eu-central-1:123456789:irjzyov5a7/PROD/GET/path1"
}
]
}
AWS permission declaration for execute-api:Invoke AWS IAM auth
This permission once attached to user or role, allows the owner to access our http endpoint.
The full qualifier path of resource in the permission is defined as following expression: arn:aws:execute-api:region:account-id:api-id/stage-name/HTTP-VERB/resource-path-specifier
*
wildcard can be used in any part of this expression to make it more wide. But according to least privileged principle
try to keep it as narrow as possible (fully defining all parameters with values).
According to our deployment it will have the following values:
key | value |
---|---|
region | eu-central-1 |
account-id | 123456789 |
api-id | irjzyov5a7 |
stage-name | PROD |
HTTP-VERB | GET |
resource-path-specifier | path |
After user is provisioned we can access aws web console and generate ACCESS and SECRET key (that we will use when calling API).
Calling API GW with IAM
So when calling same API from postman, you should select AWS Signature
in Authorization type, define AccessKey
SecretKey
AWS Region
Service Name
, the request is successful:
AWS Access and Secret keys are not transfered over network, sig-v4 algorithm is used instead to generate the signature and sign all HTTP calls to AWS endpoints
To prevent tampering with a request while it’s in transit, some request elements are used to calculate a hash (digest) of the request, and the resulting hash value is included as part of the request. When an AWS service receives the request, it uses the same information to calculate a hash and matches it against the hash value in your request. If the values don’t match, AWS denies the request.
In most cases, a request must reach AWS within five minutes of the time stamp in the request. Otherwise, AWS denies the request.
In next post we will explore this algorithm in details using mitmproxy
interceptor and will assemble the sign hash.
References (Links):
- https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html
- https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-signing.html
- https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_rest_api
- https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-access-control-iam.html