Lambda URL CloudFront WAF

You can associate an existing WAF ACL with the CloudFront distribution.

Here’s an example of how to configure an existing WAF.

config/jets/deploy.rb

Jets.deploy.configure do
  config.lambda.url.cloudfront.enable = true
  config.lambda.url.cloudfront.cert.arn = acm_cert_arn(domain: "example.com", region: "us-east-1")

  config.lambda.url.cloudfront.web_acl_id = web_acl_arn("prod")
end

Note that web_acl_id expects the arn, so the code above is correct. The setting is called web_acl_id because it matches with the CloudFormation property. Here’s a useful cheatsheet command to list your WAFs that CloudFront can use.

aws wafv2 list-web-acls --scope=CLOUDFRONT --region=us-east-1 | jq

Assigning an existing WAF ALC allows you to reuse and associate its Rules with multiple CloudFront Distributions. It can help keep your WAF rules DRY. IE: Change the rules once, and they protect all the CloudFront traffic across all apps.

WAF Web ACL must be a global WAF. Regional WAFs do not work with CloudFront. The web_acl_arn helper can take in an options hash at the end. It defaults {scope: "CLOUDFRONT"}.

Also note that CloudFront provides built-in baseline protection called AWS Sheild Standard. You get it just by using it.

AWS Shield Standard provides protection for all AWS customers against common and most frequently occurring infrastructure (layer 3 and 4) attacks like SYN/UDP floods, reflection attacks, and others to support high availability of your applications on AWS.

A WAF provides additional protection measures.

Standalone Jets WAF

Jets provides a waf command to create a standalone WAF ACL that is decoupled from the Jets app.

jets waf:deploy

You may want to consider creating a “shared” or “infra” project with the standalone WAF config since the WAF can to be shared by multiple projects.

Some advantages for this:

  • It allows you to use the standalone WAF as a shared resource to among all your CloudFront distributions. So if you add a WAF rule, it applies everywhere.
  • Each WAF costs money. The WAF ACL has a $5 baseline cost and $1 per rule per ACL. A CloudFront WAF with 3 rules costs $8. Sharing a WAF can save you money.

That being said, you can configure different WAFs for each Jets app if needed.

Configure Jets WAF

You can adjust the default rules or add additional custom-user rules. Example:

config/jets/waf.rb

Jets.deploy.configure do
  # change the default rules
  config.waf.default_rules = %w[
    AWSManagedRulesAmazonIpReputationList
    AWSManagedRulesCommonRuleSet
    AWSManagedRulesKnownBadInputsRuleSet
  ]
  # add additional custom-user rules
  config.waf.rules = []
  config.waf.monitoring = false # When true, adjust rules to use Count action to all rules
  # config.waf.name = Jets.env # IE: dev
end

Note: The config.waf can be in config/waf.rb or config/deploy.rb. It’s recommended to use waf.rb to make it clear that it’s for WAF settings. Similarly, you can also have config/waf/dev.rb and config/waf/prod.rb.

Jets WAF us-east-1

The Jets WAF is always deployed to us-east-1 because that’s a CloudFront requirement. CloudFront operates out of us-east-1, so the WAF must also reside there. Regardless of what you set your AWS_REGION to, Jets will switch and use us-east-1 for the WAF standalone stack. The CloudFormation stack can be found in us-east-1.

Reference

The table below covers each setting. Each option is configured with config.OPTION. The config. portion is not shown for conciseness. IE: logger.level vs config.logger.level.

Name Default Description
waf.block_ips.enable false Enable blocking of IP addresses.
waf.block_ips.list [] List of IP addresses to block.
waf.custom_rules.blanket_rate_limiter.action Block Action to take when rate limit is reached.
waf.custom_rules.blanket_rate_limiter.aggregate_key_type IP Type of IP address to use. Can be IP or FORWARDED_IP. See: WAF Rule Rate Based Statement
waf.custom_rules.blanket_rate_limiter.enable true Enable blanker rate limiter. Fundamental defense against DDOS.
waf.custom_rules.blanket_rate_limiter.evaluation_window_sec 300 How far back in seconds the WAF should look when it checks how many times the IP address sent a request.
waf.custom_rules.blanket_rate_limiter.limit 1000 Number of requests by the same IP before being rate limited.
waf.custom_rules.uri_rate_limiter.action Block Action to take when rate limit is reached.
waf.custom_rules.uri_rate_limiter.aggregate_key_type IP Type of IP address to use. Can be IP or FORWARDED_IP. See: WAF Rule Rate Based Statement
waf.custom_rules.uri_rate_limiter.enable false Enable url rate limiter. Can be useful to restrict specific URLs to a lower rate limit. IE: /login
waf.custom_rules.uri_rate_limiter.evaluation_window_sec 300 How far back in seconds the WAF should look when it checks how many times the IP address sent a request.
waf.custom_rules.uri_rate_limiter.limit 100 Number of requests by the same IP before being rate limited.
waf.custom_rules.uri_rate_limiter.logical_statement “Or” Logical statement to join the paths logical together.
waf.custom_rules.uri_rate_limiter.paths [”/”] The paths to apply the rate limit rule. IE: ["/logins", "/signgup"]
waf.custom_rules.uri_rate_limiter.string_match_condition “STARTS_WITH” Match condition expression. Examples: CONTAINS EXACTLY STARTS_WITH. See: CloudFormation PositionalConstraint Docs for more
waf.default_rules see desc The default rules curated AWS managed rules and Jets Blanket Rate Limiter rule. See: WAF Default Rules
waf.logging.enable false Turn on WAF logging to CloudWatch. Useful to determine whether or not WAF is blocking a request.
waf.monitoring false When true, adjust rules to use Count action to all rules. This is “Monitoring” mode.
waf.name Jets.env Name of the waf. Defaults to Jets.env. IE: dev or prod
waf.properties {} Properties to override WebACL
waf.rules [] Additional Custom User WAF rules to add.

See Full Config Reference