Technical

Enhance API Security With Geo-Blocking

As the web has matured, it has had to reconcile the promise of becoming a borderless global medium with the almost brick-and-mortar practicalities of intellectual property rights, local customs and regulations, and region-based marketing, as well as the universal problems of abusive actors and sheer scale. These considerations apply to companies of all sizes and across all industries, whether that’s streaming services, booksellers, record labels, grocery delivery companies, online gambling, or services by and for governments.

There are many reasons why a service provider would restrict access to their content or services by location. Perhaps a broadcaster doesn’t have the intellectual property rights to distribute content to certain territories; maybe a streaming service has the rights, but applies different pricing that requires customers to access their services a different way or access localized versions of the content. Certainly, there are country-specific reasons for access restrictions such as compliance with local laws or censorship requirements. Maybe there are security reasons to block malicious or fraudulent actors by location.

It is often left to companies themselves to determine how best to approach this patchwork of global commerce, bearing in mind that there are often non-negotiable legal requirements that they must meet and harsh consequences to getting it wrong. The exact solutions often depend on the size and scale of the organization, the industry they operate in (as an indicator of the regulation and type of risks they might face), and the tolerance of their actual and prospective customer base.

One Approach: Block Traffic by Location

Where it’s not practical to provide regional variations of content or services tailored to the particular location that a customer is in, it can be more effective to block traffic from that location altogether.

With this approach, there are a few different enforcement points where geo-blocking can be applied as a web request makes its way to the service provider. Organizations that operate at a large scale may use a content delivery network (CDN) like Akamai, Cloudflare, or Fastly, where API requests can be inspected, manipulated, and rejected before they ever reach your own services. In between the CDN and your services, you would expect to find an API Gateway where requests are further inspected before being sent on to their ultimate destination. The apps that deliver these services are also capable of making decisions about what they should do to fulfill or reject a request.

Geo-Blocking With a Web Application Firewall

A Web Application Firewall (WAF) is a common way of guarding the front door to a set of applications, especially those that face the public internet. Many of these are based on ModSecurity, an open source WAF that can be used with the OWASP core ruleset and fully customized using ModSecurity directives called SecRules.

In a gateway that uses ModSecurity, you could write the following SecLang directives to use a GeoIP2 database to lookup the country that a source IP address comes from and reject it if it matches a country you wish to block (we’ll use the United Kingdom in this example):

SecGeoLookupDb GeoLite2-Country.mmdb
SecRule REMOTE_ADDR "@geoLookup" "chain,id:22,deny,status:403,msg:'GB traffic blocked'"
SecRule GEO:COUNTRY_CODE "@streq GB"

Solo.io’s flagship Gloo Gateway uses ModSecurity for its WAF implementation, so such directives can be wrapped into a WAF policy to make sure that requests to Gloo Gateway are rejected if they come from a particular country:

apiVersion: security.policy.gloo.solo.io/v2
kind: WAFPolicy
metadata:
  name: geoblock
  namespace: app-frontend
spec:
  applyToRoutes:
  - route:
      labels:
        waf: "true"
  config:
    disableCoreRuleSet: true
    customInterventionMessage: 'Not available in your country'
    customRuleSets:
    - ruleStr: |
        SecRuleEngine On
        SecDebugLog /dev/stdout
        SecDebugLogLevel 9
        SecGeoLookupDb /etc/geoip/database/GeoLite2-Country.mmdb
        SecRule REQUEST_HEADERS:X-Envoy-External-Address "@geoLookup" "chain,id:22,deny,status:403,msg:'GB request blocked'"
        SecRule GEO:COUNTRY_CODE "@streq GB"
    auditLogging:
      action: ALWAYS
      location: DYNAMIC_METADATA

Of course, this only works if we have a way to convert the IP address of the requestor into a country code. This is typically done with a GeoIP2 database. There are a few ways to supply a GeoIP2 country database to the gateway that enforces this policy, and the simplest is probably to use the geoipupdate agent from MaxMind, which brings the additional benefit of automatically updating the database as changes are published.

You could use this in a Kubernetes API Gateway by adding an additional container to your gateway with the configuration needed by the geoipupdate program. For example, the following container descriptor can be added to a Kubernetes pod or deployment spec to run geoipupdate once or continuously, and maintain the GeoIP2 country database on a volume that’s accessible to the main gateway container:

- name: geoipupdate
  image: ghcr.io/maxmind/geoipupdate:v6.0
  env:
  # Register for a MaxMind account and provide the details in a secret
  - name: GEOIPUPDATE_ACCOUNT_ID
    valueFrom:
      secretKeyRef:
        name: maxmind
        key: AccountID
  - name: GEOIPUPDATE_LICENSE_KEY
    valueFrom:
      secretKeyRef:
        name: maxmind
        key: LicenseKey
  - name: GEOIPUPDATE_EDITION_IDS
    value: GeoLite2-Country
  - name: GEOIPUPDATE_FREQUENCY  # in hours, 0 to run once then quit
    value: "72"
  - name: GEOIPUPDATE_DB_DIR
    value: /etc/geoip/database/
  - name: GEOIPUPDATE_CONF_FILE
    value: /etc/geoip/GeoIP.conf

You can find more information about running the MaxMind geoipupdate agent here.

This approach is excellent when protecting an entire application or estate of applications from unauthorised access from a relatively static set of countries, but what if your applications’ needs are more nuanced and you want to give more control to application teams to govern the countries that should be able to access their applications?

Dynamic Geo-Blocking With API Keys

If you need to apply more fine-grained filtering of requests by country, the Web Application Firewall approach may be too blunt a tool.

Another approach is to store the set of prohibited (or permitted) countries on an API key, which in Gloo Gateway you might store as a Kubernetes secret like this:

apiVersion: v1
kind: Secret
metadata:
  name: user1
  namespace: app-frontend
  labels:
    auth: api-key
type: extauth.solo.io/apikey
stringData:
  api-key: fc23f505-893c-4fec-addf-a9dfdbda9a51
  user-id: user1
  user-email: user1@solo.io
  usage-plan: gold
  prohibited-countries: GB

Note that you can also store API keys in Redis, where the data about the key is stored in a similar construct.

For enforcement, you can take advantage of Gloo Gateway’s support for OPA, with rules written in OPA’s language, Rego. This gives you huge flexibility in how you handle and pre-process web requests before they reach the back-end services. The example below takes two inputs: the source country of the request (geo-CountryCode), and a comma-separated list of countries from which requests should be blocked (X-AppConfig-Prohibited-Countries), and it will deny the request if they are both non-blank and the source country is in the list of prohibited countries:

apiVersion: v1
kind: ConfigMap
metadata:
  name: deny-prohibited-countries
  namespace: app-frontend
data:
  policy.rego: |-
    package test

    import future.keywords.if
    import future.keywords.in

    default allow = false

    allow if not input.state["X-AppConfig-Prohibited-Countries"]

    allow if not input.state["geo-CountryCode"]

    allow if input.state["geo-CountryCode"] == ""

    allow if {
      not input.state["geo-CountryCode"] in split(input.state["X-AppConfig-Prohibited-Countries"], ",")
    }

We can bring this together with a Gloo Gateway external authorization policy which configures our bundled Ext Auth server to apply complex authorization policies to requests before they are forwarded on to their destinations. This policy needs to do three things:

  1. Authenticate the API key presented by the requestor, and extract the set of prohibited countries that is stored internally alongside that API key.
  2. Call a passthrough external authorization service to lookup the requestor’s country from the source IP address and add that as a new header.
  3. Apply the OPA rule declared above to decide if the request is authorized.

An example of this external authorization policy is below, with all three of these actions shown under spec.config.glooAuth. The gRPC service that implements the passthrough auth is completely customizable and can use any mechanism you can write in code to look up a country from an IP address, like interrogating a GeoIP database directly or using a third party service like ip-api.com (you can see an example passthrough auth implementation here).

apiVersion: security.policy.gloo.solo.io/v2
kind: ExtAuthPolicy
metadata:
  name: apiauth
  namespace: app-frontend
spec:
  applyToRoutes:
  - route:
      labels:
        apikeys: "true"
  config:
    server:
      name: ext-auth-server
      namespace: gloo-mesh-addons
      cluster: cluster1
    glooAuth:
      configs:
      - apiKeyAuth:
          headerName: api-key
          headersFromMetadataEntry:
            X-AppConfig-Prohibited-Countries:
              name: prohibited-countries
          k8sSecretApikeyStorage:
            labelSelector:
              auth: api-key
      - passThroughAuth:
          grpc:
            address: geolookup-auth-service.gloo-mesh-addons.svc.cluster.local:9001
      - opaAuth:
          modules:
          - name: deny-prohibited-countries
            namespace: app-frontend
          query: "data.test.allow == true"

Take a Multi-Layered Approach

These approaches let gateway and application teams put flexible restrictions in place to stop their apps from serving requests from locations that they do not want – or are not permitted – to do business with. However, they have a key dependency on the translation of a source IP address to a country or location, which in the days of VPNs and anonymizer services is hard to rely on for accuracy.

The solutions described here will work best as part of a multi-layered approach, especially where one of those layers is a content delivery network that has a clear view of where requests are coming from, for example by estimating the latency of a connection to determine whether the request actually correlates with the location of the source IP address. Techniques like this can be applied to enrich API requests with more accurate indicators of whether traffic should be blocked, and in many cases those edge proxies can also block requests themselves. How and where the enforcement should happen is best determined by the risk that your applications are exposed to as well as the flexibility you need in deciding and changing the specific policies you wish to enforce.

Outright blocking by location is not the only approach you can employ to meet your regional market and regulatory requirements. Using the location information available from the source IP address, your gateway can redirect traffic to different versions of services and content or enrich requests with additional information to allow your services determine how to serve the request based on its origin country. I don’t cover these approaches in this post, but consider how the OPA example used above can be adapted to apply bespoke logic to requests using their geo-location.

Solo.io’s API Gateway is a building block in your zero trust API architecture. To learn more about Gloo Gateway’s web application firewall capabilities please check out our documentation here, and to find out about the additional policy capability of authorization solutions based on our external authorization, please see our guides here.