Convert AWS WAF ACLs to human-readable format

Run the attached script to download and convert ACLs

  1. ACL schema
  2. Converted schema
  3. And/OrStatement
  4. Nested And/OrStatement
  5. NotStatement
  6. String match

I regularly need to audit my company’s access control lists (ACLs) implemented in AWS WAF, as part of my job. Each ACL can be more than a thousand lines which is practically impossible to read. I wrote a script that downloads and summarises the ACLs into human-readable format; each one-thousand-line behemoth is transformed into a fifty-line summary that I can actually audit.

The script is available here. It currently only supports Cloudfront ACL, feel free to extend it to support regional ACL.

(Edit: 1 Sep 2021) regional ACL is now supported.

ACL schema §

The underlying format of a web ACL is JSON. In this use case, I’m only concern with two keys:

{
  "Name": "",
  "Rules": [
    {
      "Name": "",
      "Statement": {},
      "Action": {
        "Block": {}
      }
    },
    {
      "Name": "",
      "Statement": {},
      "Action": {
        "Allow": {}
      }
    }
  ]
}

The script names each ACL according to the value of “Name”. “Rules” is an array of objects, where each object represents a rule. Each rule has an action of count, allow or block.

In each rule, there is a statement and it functions as a matching condition. Each statement can contain one or match statements combined using logical rule (AND, NOT, OR).

Converted schema §

A converted ACL has an array of objects, each object has three keys.

[
  {
    "Name": "",
    "Action": "",
    "Rule": ""
  }
]

And/OrStatement §

Original
{ "Name": "ruleA", "Statement": { "OrStatement": { "Statements": [ { "foo": {} }, { "bar": {} } ] } } }
Converted
{ "ruleA": "foo OR bar" }

Nested And/OrStatement §

Original
{ "Name": "ruleA", "Statement": { "AndStatement": { "Statements": [ { "OrStatement": { "Statements": [ { "foo": {} }, { "bar": {} } ] } }, { "baz": {} } ] } } }
Converted
{ "ruleA": "(foo OR bar) AND baz" }

NotStatement §

Original
{ "Name": "ruleA", "Statement": { "NotStatement": { "Statement": { "foo": {} } } } }
Converted
{ "ruleA": "NOT foo" }

String match §

Orignal
{ "ByteMatchStatement": { "SearchString": ".conf", "FieldToMatch": { "UriPath": {} }, "PositionalConstraint": "ENDS_WITH" } }
Converted
UriPath=ENDSWITH(.conf)