This chapter is included in the free preview

WAF

WAF is AWS's managed firewall solution. It is a separate service compatible with several API services. Its main function is to deny or allow requests based on rules and to give visibility into the requests that are sent to the protected API.

WAF allows/denies requests to the AppSync API based on rules

The rules have access to the request headers, the body, the IP address, and they can also implement per-IP rate limiting. Also, statements in rules can be combined with AND/OR logic and they can either allow (if they don't match the request is denied) or block (if they don't match the request is allowed) functionality.

These rules are managed outside AppSync and you need to associate them with your API after you've configured them. You can define what ACL is effective for an API on the AppSync console:

Associate the WAF to the AppSync API

In this chapter, we'll see a couple of use-cases that you can implement using WAF.

Note

WAF comes with monthly prices as well as per-request prices. This means you'll have costs even if there are no requests to your API.

Disable introspection queries

GraphQL defines special queries that are used to get information about the schema called introspection. AppSync implements this functionality and allows reconstructing the schema.

For example, AppSync can return the types defined in the schema:

query MyQuery {
  __schema {
    types {
      name
    }
  }
}

This returns a list:

{
  "data": {
    "__schema": {
      "types": [
        {"name": "Query"},
        {"name": "User"},
        {"name": "ID"},
        ...
      ]
    }
  }
}

While introspection queries are protected by the authorization mechanism defined for the API (so there is no anonymous access to them), if you don't need them then it might be a good idea to disable them. Note that it does not provide a real security benefit: the schema isn't a secret, and there might be other ways to extract it from the API.

For a simple solution, you can add a rule that matches requests with the __schema string:

{
  "ByteMatchStatement": {
    "FieldToMatch": {
      "Body": {}
    },
    "PositionalConstraint": "CONTAINS",
    "SearchString": "__schema",
    "TextTransformations": [
      {
        "Type": "NONE",
        "Priority": 0
      }
    ]
  }
}

Visually:

A statement that denies introspection queries

Notice the warning: if the __schema does not appear in the first 8192 bytes of the request, WAF gives up and won't search further. To make it harder to evade this rule, add a second one that matches requests larger than this limit:

{
  "SizeConstraintStatement": {
    "FieldToMatch": {
      "Body": {}
    },
    "ComparisonOperator": "GT",
    "Size": 8192,
    "TextTransformations": [
      {
        "Type": "NONE",
        "Priority": 0
      }
    ]
  }
}

Visually:

A statement that denies requests larger than 8129 bytes

When associated with the API, WAF sends a 403 response without calling the API:

An introspection query is blocked by WAF
Note

Disabling introspection queries is useful in some cases, especially when you don't need it, but the above rule only provides a sense of security. For example, it does not disable all types of introspection, such as the __type query.

Rate limiting

WAF also allows rate limiting per originating IP address. This helps blocking enumeration and DoS attacks, as well as limit the cost for a misconfigured client. On the other hand, its rate limiting is per-IP, which means if the attack is distributed across many addresses, WAF still won't block that.

Rate limiting is implemented using a 5-minute sliding window and requests are blocked if the amount reaches the configured threshold.

A WAF rule can implement rate limiting per IP address

With a selection rule, you can also define more complex configuration. For example, you can rate limit requests from specific countries, or disable limiting for the company IP address range.

Note

Rate limiting is per-IP address, so it won't help much against a distributed attack.

Geo filter

A rule can also target countries. With this selection filter, you can deny requests from matching countries, or do the opposite and define an allowlist of countries that can reach the API. This is especially useful if all your users are in a well-defined geographical region.

A rule can use the country of the caller
Note

VPNs can circumvent this limitation.

IP filter

Similar to the geo filter, you can add individual IP addressess and CIDR blocks and define a rule based on this list. This is especially useful when you have dedicated addresses and want to allow traffic only from there. For example, a company might have a fixed IP and an IP filter can allow only requests from that IP.

A rule can use an IP address set

With rule combinations, you can also implement more complex scenarios. For example, you can block all requests containing the admin string in the body that are not from the company IP address.