Steyn Huizinga

CTO AWS | AWS APN Ambassador | AWS Premier Consulting Partner | Xebia

Limiting access using geographic restrictions

2022-03-03 9 min read AWS

The world is on fire. We’re heading towards - or exactly it already is - a humanitarian disaster in Ukraine. We’ve all seen the heartbreaking footage from the war. Thousands of homeless people fighting and fearing for their lives. I’ve written this blog to help. I do know that a large number of government websites are hosted on AWS.

The conflict between Ukraine and Russia is expanding with cyber warfare. There is fighting on the ground, but also online. Government websites are taken down, broadcasting companies are being hacked, etc. Everything is done to manipulate and disrupt communication technologies. Although most attacks will be sophisticated, there are some simple measurements in AWS you can take to make it more difficult for attackers. It will not be 100% waterproof. Hackers often use Tor-networks and Command and Control-machines. But every bit helps.

I can’t really support the people in Ukraine, but I can try to help with bringing in AWS knowledge and hope that the people will keep access to the information they need.

Geographic restrictions

Communication between clients-and-servers is done based on IP addresses. Clients are connecting to a server using a TCP-session. The server can see the client’s IP address and uses that as the source address. The IP address is just a number and not really useful if you want to restrict access to your resources by excluding countries or continents. The IP address itself will not provide you sufficient context to filter traffic.

IP addresses are part of a bigger network, divided into blocks. Blocks are owned by providers (ISPs) and registered at a Regional Internet Registry (RIR). World-wide five RIRs are managing and allocating IP addresses:

  • The African Network Information Center (AFRINIC) serves Africa.
  • The American Registry for Internet Numbers (ARIN) serves Antarctica, Canada, parts of the Caribbean, and the United States.
  • The Asia-Pacific Network Information Centre (APNIC) serves East Asia, Oceania, South Asia, and Southeast Asia.
  • The Latin America and Caribbean Network Information Centre (LACNIC) serves most of the Caribbean and all of Latin America.
  • The Réseaux IP Européens Network Coordination Centre (RIPE NCC) serves Europe, Central Asia, Russia, and West Asia.

Netblocks owned by ISPs (and other parties) are registered at the local RIR. The RIRs provide services to link netblocks to their owners, via whois services. This information holds the company data of the ISP, including the country. With this information you can more or less determine from which country a connection is initiated. This information is not always 100% accurate, for various reasons. Common reason is that ISPs can operate in multiple countries and are running a global infrastructure. For geo services this data is enriched with data based on feedback (deducting information from mobile devices, data mining and routing information for example), to provide a reliable source of geo information.

This information is made available as a database and can be used for geolocation technology, such as websites and CDNs.

Amazon CloudFront

Amazon CloudFront is a content delivery network (CDN) operated by AWS. It is used to serve data from globally-distributed networks, locally for end-users. This provides access speed (low proximity), but also scalability. In 2013 AWS added the Geo Restriction feature. This feature can be used to restrict access to content based on the geographical location of the viewer. It can be used in three modes: no restrictions, allow list (implicit deny, explicit allows) and deny list (implicit allow, explicit denies). For the two list modes, country codes (NL for the Netherlands, US for the United States, etc.) can be provided to control traffic from the countries. CloudFront then evaluates each request against the list. Denied countries will have an HTTP status code 403 (Forbidden) response. Traffic from allowed countries will be passed to the origin (and ultimately serve the content (if no other deny rules have been hit).

For the use-case in the introduction the block list type will give the best result. In this case we want the website to be accessible for everyone, except from a number of countries. We use Russia and their best known allies as entries in the deny list:

  • Country code RU: Russian Federation
  • Country code BY: Belarus
  • Country code CN: China

If you are using AWS CDK and TypeScript, add the following line to your CloudFront distribution object:

geoRestriction: cloudfront.GeoRestriction.denylist('RU', 'BY', 'CN'),

If you use native CloudFormation add the following to your AWS::CloudFront::Distribution resource:

    Restrictions: 
      GeoRestriction:
        Locations:
         - RU
         - BY
         - CN
        RestrictionType: blacklist

AWS WAF

AWS Web Application Firewall (WAF) is a web application firewall. It can inspect content, but it can also be used to apply rules based on session context. Such as source IP address. AWS WAF integrates with CloudFront or with regional services such as Application Load Balancer (ALB), an Amazon API Gateway REST API, or an AWS AppSync GraphQL API.

For this use-case we use the geographic match rule statement feature. With this feature you can write geo-based rules to be included in a WebACL. With this type of rule statement you configure a list of country codes which you want to deny or allow (depending on the selected default action). For now we want to allow all countries, except three specific countries. The best choice is default action allow and include a geoMatchStatement to block access from the three countries.

For WAF it is recommended to, in addition to blocking countries, block anonymous networks (networks that are known to be part of) and sources with a bad reputation. AWS provides manage rule lists that are useful for our case:

  • AWSManagedIPReputationList: Inspects for a list of IP addresses that have been identified as bots by Amazon threat intelligence.
  • AnonymousIPList: Inspects for a list of IP addresses of sources known to anonymize client information, like TOR nodes, temporary proxies, and other masking services.

Now put the custom rules, two managed rule lists together in a WebACL using AWS CDK:

Import WAFv2 module:

import * as wafv2 from 'aws-cdk-lib/aws-wafv2';

Create WebACL, combined with our own custom country block rule and two AWS managed rule groups:

const webacl = new wafv2.CfnWebACL(this, 'GeoRestriction', {
    description: 'Block suspicious traffic',
    defaultAction: {
        allow: {},
    },
    scope: 'CLOUDFRONT',
    visibilityConfig: {
        sampledRequestsEnabled: true,
        cloudWatchMetricsEnabled: true,
        metricName: 'GeoRestrictionWebACL',
    },
    rules: [{
        priority: 0,
        action: {
            block: {},
        },
        visibilityConfig: {
            sampledRequestsEnabled: true,
            cloudWatchMetricsEnabled: true,
            metricName: 'GeoRestrictionCountryRule',
        },
        name: 'GeoRestrictionCountry',
        statement: {
            geoMatchStatement: {
                countryCodes: [
                    'RU',
                    'BY',
                    'CN',
                ],
            },
        },
    }, {
        priority: 1,
        overrideAction: { 
            none: {},
        },
        visibilityConfig: {
            sampledRequestsEnabled: true,
            cloudWatchMetricsEnabled: true,
            metricName: 'AWS-AWSManagedRulesAmazonIpReputationList',
        },
        name: 'AWS-AWSManagedRulesAmazonIpReputationList',
        statement: {
            managedRuleGroupStatement: {
                vendorName: 'AWS',
                name: 'AWSManagedRulesAmazonIpReputationList',
            },
        },
    }, {
        priority: 2,
        overrideAction: { 
            none: {},
        },
        visibilityConfig: {
            sampledRequestsEnabled: true,
            cloudWatchMetricsEnabled: true,
            metricName: 'AWS-AWSManagedRulesAnonymousIpList',
        },
        name: 'AWS-AWSManagedRulesAnonymousIpList',
        statement: {
            managedRuleGroupStatement: {
                vendorName: 'AWS',
                name: 'AWSManagedRulesAnonymousIpList',
            },
        },
    },],
});

Or in plain CloudFormation:

GeoRestriction: 
  Properties: 
    DefaultAction: 
      Allow: {}
    Description: "Block suspicious traffic"
    Rules: 
      - Action: 
          Block: {}
        Name: GeoRestrictionCountry
        Priority: 0
        Statement: 
          GeoMatchStatement: 
            CountryCodes: 
              - RU
              - BY
              - CN
        VisibilityConfig: 
          CloudWatchMetricsEnabled: true
          MetricName: GeoRestrictionCountryRule
          SampledRequestsEnabled: true
      - Name: AWS-AWSManagedRulesAmazonIpReputationList
        OverrideAction: 
          None: {}
        Priority: 1
        Statement: 
          ManagedRuleGroupStatement: 
            Name: AWSManagedRulesAmazonIpReputationList
            VendorName: AWS
        VisibilityConfig: 
          CloudWatchMetricsEnabled: true
          MetricName: AWS-AWSManagedRulesAmazonIpReputationList
          SampledRequestsEnabled: true
      - Name: AWS-AWSManagedRulesAnonymousIpList
        OverrideAction: 
          None: {}
        Priority: 2
        Statement: 
          ManagedRuleGroupStatement: 
            Name: AWSManagedRulesAnonymousIpList
            VendorName: AWS
        VisibilityConfig: 
          CloudWatchMetricsEnabled: true
          MetricName: AWS-AWSManagedRulesAnonymousIpList
          SampledRequestsEnabled: true
    Scope: CLOUDFRONT
    VisibilityConfig: 
      CloudWatchMetricsEnabled: true
      MetricName: GeoRestrictionWebACL
      SampledRequestsEnabled: true
  Type: "AWS::WAFv2::WebACL"

Finally attach WAF to CloudFront by adding the following to your CloudFront distribution object. Please note that the webAclId expects the arn of the WebACL and not the physical or logical ID.

webAclId: webacl.attrArn,
To attach the WebACL to regional resources, please use the construct CfnWebACLAssociation.

You’ve now configured your CloudFront distribution to use the WAF ACL, blocking traffic from the offending countries, from anonymous nodes and from addresses with a bad reputation.

In the example above the country filtering is redundant. Both CloudFront Geo Restriction feature and AWS WAF GeoMatchStatement have been used. To simplify management and reduce costs you can remove the rule with geoMatchStatement from the WebACL. It is included in this example if you want to use AWF for regional services and thus cannot rely on CloudFront’s Geo Restriction feature. Don’t forget the scope-property when using this WAF for regional services.

Note: Global WAF WebACLs are managed in us-east-1 only. Trying to deploy them in different regions will throw an error. In CDK the target region for a stack can be enforced by specifying the region in your app’s environment:

env: { region: 'us-east-1' },

Amazon Route 53

Amazon Route 53 supports geolocation-based routing. Based on the country where the DNS request originated from, the answer will/can be different. We can use this in order to respond with a bogus value for the offending countries, by pointing them to 127.0.0.1 (localhost). This way resolving your resources is made a bit more complex. However, if this DNS request has been made via a global DNS-provider the results may vary. This is certainly not waterproof.

const banned_country_codes = ['RU', 'BY', 'CN'];

for (let banned_country_code of banned_country_codes) {
    new route53.CfnRecordSet(this, 'CountryCodeRecordSet' + banned_country_code, {
        name: 'www.domain.tld',
        type: 'A',
        geoLocation: {
            countryCode: banned_country_code,
        },
        hostedZoneId: domain.hostedZone.hostedZoneId,
        resourceRecords: [
            '127.0.0.1',
        ],
        ttl: '600',
        setIdentifier: 'banned_country_' + banned_country_code,
    });
};

Finally add a recordset with countryCode '*' which is pointing to your actual resource. This way all countries not listed in banned_country_codes will get the response of your Internet-facing resource.

AWS Shield

AWS Shield is a managed Distributed Denial of Service (DDoS) protection service that safeguards applications running on AWS. AWS Shield comes in two tiers: Standard and Advanced. AWS Shield Standard is automatically enabled on CloudFront, load balancers and Amazon Route 53, without additional costs. AWS Shield Advanced is a paid service and provides tailored detection, advanced mitigation, proactive event response and DDoS cost protection (almost like an insurance), 24x7 response team etc. This service, both levels, can be used at global resources such as CloudFront and with regional resources such as load balancers.

Putting it all together

Like any other security challenge, the best results can be achieved by applying security-in-depth. Bulk of the malicious traffic can be dropped at the global infrastructure of AWS. At this level you can benefit from the scale of AWS. Amazon CloudFront and AWS WAF both provide options to deny traffic based on Geo Restriction. This is low maintenance and quite effective. But not waterproof. Consider using the WAF managed rules to deny traffic from anonymous networks. And finally make your application robust by applying Auto Scaling groups or using serverless solutions.

Some tips:

  • By blocking traffic early in the process, you reduce the risk for your regional - and perhaps less scalable or less cost-effective - resources.
  • Don’t forget to monitor and apply alarms based on metrics
  • In your VPC only allow ingress traffic from CloudFront, reducing your attack vector.
  • Consider AWS Shield Advanced. It might look expensive, but once activated the service can be used (this is included in the pricing) across all your AWS accounts.
  • If you also want to apply geo statements on your regional WAF, please make sure that you are using the X-Forwarded-For header to determine the source IP address.
  • Deploy/manage your global resources from us-east-1-region. This simplifies your IaC (less cross-region resources).

This diagram should give an high-level overview of how an (classic tiered) application could be protected by Geo Restrictions. High-level overview of restricting traffic based on geographical rules


Note: Geo restriction of course can also be used for less political reasons such as DRM or licensing cases.