blog/source/_posts/aws-waf.md

3.0 KiB

title excerpt date updated tags
Convert AWS WAF ACLs to human-readable format Run the attached script to download and convert ACLs 2021-06-27 2021-09-01
aws
security

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

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

Nested And/OrStatement

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

NotStatement

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

String match

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