favoritest
kmerkuri  

Customizing CloudFront with Terraform: Redirecting by Country

In this blog post, we’ll explore how to use Terraform to create a custom CloudFront function that redirects traffic based on the viewer’s country. We’ll also set up a CloudFront distribution with the custom function and configure it to work with our desired origin.

The CloudFront Function

Our custom function, myredirect_redirect_function, is written in CloudFront’s JavaScript runtime (cloudfront-js-1.0). It takes in the event object, which contains information about the request, including the viewer’s IP address and headers. The function checks the cloudfront-viewer-country header to determine the viewer’s country and then redirects the traffic accordingly.

The function also includes logic to check if the IP address is whitelisted, and if so, allows the request to proceed. Additionally, it checks if the request is for a specific type of content (e.g., HTML) and blocks it if not.

The CloudFront Distribution

Our CloudFront distribution, mysite, is configured to work with our origin (origin.kmerkuri.de) and has the following settings:

  • Aliases: www.kmerkuri.de
  • Comment: cloudfront for mypage
  • Enabled: true
  • Is IPv6 enabled: true
  • Wait for deployment: true

The default cache behavior is configured to cache GET and HEAD requests for 1 hour, and compress responses. We’ve also associated our custom function with the viewer-request event, which will trigger the function to run on every request.

Configuring Forwarded Values

We’re forwarding several headers from the origin to the viewer, including Accept-Language, Host, User-Agent, CloudFront-Viewer-Country, Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, and Referer. We’re also forwarding cookies with the forward setting set to "none".

Terraform Configuration

Here’s the complete Terraform configuration file:

resource "aws_cloudfront_function" "myredirect_redirect_function" {
  name    = "redirectByCountry"
  runtime = "cloudfront-js-1.0"
  publish = true

  code = <<EOF
 function handler(event) {
  var request = event.request;
  var headers = request.headers;
    var clientIP = event.viewer.ip;
var country = headers['cloudfront-viewer-country'].value;
var userAgent = headers['user-agent'] ? headers['user-agent'].value : '';
    request.headers['x-forwarded-for'] = {value: clientIP};
var accept = headers['accept'] ? headers['accept'].value : '';
var xAppPlatform = headers['x-app-platform'] ? headers['x-app-platform'].value : '';
  // Define your predefined IP block
  var isWhitelisted = checkIP(clientIP, whitelistedIPRanges);
  if (!accept.includes('text/html')) {
     return request;
  }
  if (botUserAgents.some(function(bot) { return userAgent.includes(bot); })) {
        return request;
 }
  if (xAppPlatform.includes('native')) { // 'native ios' or 'native android'
     return request;
  }
  if (isWhitelisted) {
    // Code to execute if the IP address is whitelisted
    return request;
  }


  else if (country === 'AT') {
    return {
      statusCode: 302,
      statusDescription: 'Found',
      headers: {
        'location': {
          value: 'https://www.klevimerkuri.com'
        }
      },
    };}
  else if (country === 'BE') {
    return {
      statusCode: 302,
      statusDescription: 'Found',
      headers: {
        'location': {
          value: 'https://www.kmerkuri.be'
        }
      },
    };}
  else if (country === 'DK') {
    return {
      statusCode: 302,
      statusDescription: 'Found',
      headers: {
        'location': {
          value: 'https://www.kmerkuri.dk'
        }
      },
    };
  }
  else if (country === 'RS') {
    return {
      statusCode: 302,
      statusDescription: 'Found',
      headers: {
        'location': {
          value: 'https://www.kmerkuri.rs'
        }
      },
    };
  }
  else if (country === 'CZ') {
    return {
      statusCode: 302,
      statusDescription: 'Found',
      headers: {
        'location': {
          value: 'https://www.kmerkuri.cz'
        }
      },
    };
  }else {
     return request;
  }

}

function checkIP(clientIP, whitelistedIPRanges) {
  for (var i = 0; i < whitelistedIPRanges.length; i++) {
    var ipRange = whitelistedIPRanges[i];
    if (ipRange.includes('/')) {
      var ipAndSubnet = ipRange.split('/');
      var ip = ipAndSubnet[0];
      var subnet = ipAndSubnet[1];
      var subnetMask = 32 - parseInt(subnet, 10);
      var ipInt = ipToInt(clientIP);
      var subnetInt = ipToInt(ip);
      var maskInt = 0xffffffff << subnetMask;
      if ((ipInt & maskInt) === (subnetInt & maskInt)) {
        return true;
      }
    } else {
      if (clientIP === ipRange) {
        return true;
      }
    }
  }
  return false;
}

function ipToInt(ip) {
  return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0);
}

var whitelistedIPRanges = [
  '192.168.1.0/24',
  '192.168.2.124/32',
];
    var botUserAgents = [
        'Googlebot',
        'Bingbot',
        'Slurp',
        'DuckDuckBot',
        'Baiduspider',
        'YandexBot',
        'Sogou',
        'Exabot',
        'Facebot',
        'facebookexternalhit',
        'ia_archiver',
        'pingdom',
        'twitterbot',
        'crawler',
        'spider',
        'robot'
    ];
EOF
}

resource "aws_cloudfront_distribution" "mysite" {
  aliases = [
    "www.kmerkuri.de",
  ]
  comment             = "cloudfront for mypage"
  enabled             = true
  is_ipv6_enabled     = true
  wait_for_deployment = true

  default_cache_behavior {
    allowed_methods = [
      "GET",
      "HEAD",
    ]
    cached_methods = [
      "GET",
      "HEAD",
    ]
    compress         = true
    target_origin_id = "origin.kmerkuri.de"
    function_association {
      event_type   = "viewer-request"
      function_arn = aws_cloudfront_function.myredirect_redirect_function.arn
    }
    viewer_protocol_policy = "redirect-to-https"

    forwarded_values {
      headers = [
        "Accept-Language",
        "Host",
        "User-Agent",
        "CloudFront-Viewer-Country",
        "Origin",
        "Accept",
        "Access-Control-Request-Method",
        "Access-Control-Request-Headers",
        "Referer",
        "X-App-Platform",
      ]
      query_string            = false
      query_string_cache_keys = []

      cookies {
        forward           = "none"
        whitelisted_names = []
      }
    }
  }
}

Conclusion

In this blog post, we’ve seen how to use Terraform to create a custom CloudFront function that redirects traffic based on the viewer’s country. We’ve also set up a CloudFront distribution with the custom function and configured it to work with our desired origin. This configuration allows us to customize the behavior of our CloudFront distribution and provide a better experience for our users.