8th Piece: Preventing uBlock Origin from blocking sentry.io with CloudFront

In my development of StackRef, I’m interested in tracing down React UI errors as they happen to the user. Sentry.io has been a great tool for this, and their free version gets me what I need, at least for the moment.

Recently I had a user test the site out, where they ran into an error that did not show up in Sentry. Why? Because the uBlock Origin plugin is set by default to block that traffic. Once they disabled the plugin, I could see the error, track it down, and fix it. But if this is going to happen to the many people using the plugin, I can’t ask them all to disable it for me!

Thankfully, Sentry has a tunnel option available for this exact case. They even have some handly examples for how to implement it. If you’re using CloudFront, you could direct traffic to a python Lambda function, but there’s a much, much simpler way: CloudFront Functions.

I’ll use React and Terraform for an example of how to set this up.

I’m going to assume that your React app is hosted on CloudFront: app.mydomain.com. You might have something like this setup in React:

Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
  ...other settings...
});

uBlock Origin will block that call out to ingest.sentry.io. So let’s go ahead and follow Sentry’s directions, add a tunnel option and local path:

Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
  tunnel: "/sentry",
  ...other settings...
});

/sentry can be any path you want, so long as you dedicate it to calls to Sentry as a behavior on CloudFront. Do not change the dsn value here to your CloudFront hostname! Keep it as the hostname Sentry provided you.

In Terraform, let’s add a few data objects we’ll use later:

data "aws_cloudfront_cache_policy" "CachingDisabled" {
  name = "Managed-CachingDisabled"
}

data "aws_cloudfront_response_headers_policy" "CORS-with-preflight-and-SecurityHeadersPolicy" {
  name = "Managed-CORS-with-preflight-and-SecurityHeadersPolicy"
}

… and a custom origin request policy for Sentry – we don’t want to send everything to them:

resource "aws_cloudfront_origin_request_policy" "sentry" {
  name    = "sentry"
  comment = "Sentry"
  cookies_config {
    cookie_behavior = "none"
  }
  headers_config {
    header_behavior = "whitelist"
    headers {
      items = [
        "Origin",
        "Access-Control-Request-Method",
        "Access-Control-Request-Headers",
        "DSN", # May not be necessary?
        "Referer",
        "X-Sentry-Token",
        "X-Sentry-Auth"
      ]
    }
  }
  query_strings_config {
    query_string_behavior = "all"
  }
}

In your aws_cloudfront_distribution resource, you’ll add a new origin:

  # Sentry
  origin {
    connection_attempts = 3
    connection_timeout  = 10
    domain_name         = "o0.ingest.sentry.io"
    origin_id           = "o0.ingest.sentry.io"

    custom_origin_config {
      http_port                = 80
      https_port               = 443
      origin_keepalive_timeout = 10
      origin_protocol_policy   = "https-only"
      origin_read_timeout      = 30
      origin_ssl_protocols = [
        "TLSv1.2",
      ]
    }
  }

Next, add a new ordered_cache_behavior block in the distribution resource:

 ordered_cache_behavior {
    allowed_methods = [
      "DELETE",
      "GET",
      "HEAD",
      "OPTIONS",
      "PATCH",
      "POST",
      "PUT"
    ]
    cached_methods = [
      "GET",
      "HEAD"
    ]

    cache_policy_id            = data.aws_cloudfront_cache_policy.CachingDisabled.id
    response_headers_policy_id = data.aws_cloudfront_response_headers_policy.CORS-with-preflight-and-SecurityHeadersPolicy.id
    origin_request_policy_id   = aws_cloudfront_origin_request_policy.sentry.id

    compress               = false
    path_pattern           = "/sentry"
    target_origin_id       = "o0.ingest.sentry.io"
    viewer_protocol_policy = "https-only"

    function_association {
      event_type   = "viewer-request"
      function_arn = aws_cloudfront_function.sentry_viewer_request.arn
    }
  }

Notice we’re using a function_association in that cache behavior – that’s where the magic happens. So, create the function (I’ll assume you’ll place it in cloudfront_functions/sentry_viewer_request.js in the root/module of your Terraform):

function handler(event) {
  var request = event.request;
  var projectId = '12345'; // Your own Project ID goes here

  request.uri = `/api/${projectId}/envelope/`;

  return request;
}

And in Terraform:

resource "aws_cloudfront_function" "sentry_viewer_request" {
  name    = "sentry_viewer_request"
  runtime = "cloudfront-js-1.0"
  comment = "Redirect through tunnel to true Sentry endpoint API"
  publish = true
  code    = file("${path.module}/cloudfront_functions/sentry_viewer_request.js")
}

Now anytime someone goes to your React site, the tunnel: option tells it to direct traffic to yourapp.com/sentry instead of o0.ingest.sentry.io, which uBlock Origin would have seen and instantly blocked. Blocked no-more!

Keith McDuffee avatar
Keith McDuffee
Keith McDuffee is an apologetic, overly-seasoned IT, DevOps and security professional in the New England region of the USA.