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.
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:
In this chapter, we'll see a couple of use-cases that you can implement using WAF.
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.
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:
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:
When associated with the API, WAF sends a 403 response without calling the API:
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.
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.
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.
Rate limiting is per-IP address, so it won't help much against a distributed attack.
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.
VPNs can circumvent this limitation.
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.
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.