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!