Post

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:

api-gw-iam.png

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.

aws-console-apigw.png

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:

img.png

Calling API GW without credentials:

img.png

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:

keyvalue
regioneu-central-1
account-id123456789
api-idirjzyov5a7
stage-namePROD
HTTP-VERBGET
resource-path-specifierpath

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:

img.png

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.

This post is licensed under CC BY 4.0 by the author.