Skip to content

Instantly share code, notes, and snippets.

@razor-x
Last active May 3, 2024 12:39
Show Gist options
  • Save razor-x/44c1c572a8c9e4cb723b1945a4d75bdb to your computer and use it in GitHub Desktop.
Save razor-x/44c1c572a8c9e4cb723b1945a4d75bdb to your computer and use it in GitHub Desktop.
Sentry.io CloudFront Lambda@Edge tunnel
# TODO: Setup https://github.com/silvermine/serverless-plugin-cloudfront-lambda-edge
sentryOriginReq:
handler: handlers/handler.default
lambdaAtEdge:
distribution: AppDistribution
eventType: origin-request
includeBody: true
pathPattern: /_tunnel # TODO: Choose tunnel path.
const options = {
sentryDsn: 'https://public_key@public_host.ingest.sentry.io/project_id' // TODO: Set DSN.
}
module.exports.default = (event, context, callback) => {
const request = event.Records[0].cf.request
if (!willHandleSentryRequest(request, options)) {
return callback(null, {
status: 404,
statusDescription: 'Not Found',
})
}
const { body } = request
if (body.inputTruncated) {
return callback(null, {
status: 413,
statusDescription: 'Payload Too Large and truncated by CloudFront',
})
}
const envelope =
body.encoding === 'base64'
? Buffer.from(body.data, 'base64').toString('utf-8')
: body.data
const { host, projectId } = parseEnvelope(envelope)
const { host: allowedHost, projectId: allowedProjectId } = parseDsn(
options.sentryDsn
)
if (projectId !== allowedProjectId) {
return callback(null, {
status: 403,
statusDescription: `Forbidden Sentry project id: ${projectId}`,
})
}
if (host !== allowedHost) {
return callback(null, {
status: 403,
statusDescription: `Forbidden Sentry host: ${host}`,
})
}
request.uri = `/api/${projectId}/envelope/`
callback(null, request)
}
const willHandleSentryRequest = (request, options) => {
if (!options.sentryDsn) return false
if (request.method === 'POST') return false
return true
}
const parseDsn = (dsn) => {
const url = new URL(dsn)
const path = url.pathname
const host = url.hostname
const projectId = path.split('/')[1]
return { host, projectId }
}
const parseEnvelope = (envelope) => {
const [headerJson] = envelope.split('\n')
const header = JSON.parse(headerJson)
const { host, projectId } = parseDsn(header.dsn)
return { host, projectId }
}
{
"Records": [
{
"cf": {
"config": {
"distributionDomainName": "d111111abcdef8.cloudfront.net",
"distributionId": "EDFDVBD6EXAMPLE",
"eventType": "origin-request",
"requestId": "4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ=="
},
"request": {
"clientIp": "203.0.113.178",
"headers": {
"x-forwarded-for": [
{
"key": "X-Forwarded-For",
"value": "203.0.113.178"
}
],
"user-agent": [
{
"key": "User-Agent",
"value": "Amazon CloudFront"
}
],
"via": [
{
"key": "Via",
"value": "2.0 2afae0d44e2540f472c0635ab62c232b.cloudfront.net (CloudFront)"
}
],
"host": [
{
"key": "Host",
"value": "example.org"
}
],
"cache-control": [
{
"key": "Cache-Control",
"value": "no-cache, cf-no-cache"
}
]
},
"method": "GET",
"body": {
"data": "eyJkc24iOiJodHRwczovL3B1YmxpY19rZXlAcHVibGljX2hvc3QuaW5nZXN0LnNlbnRyeS5pby9wcm9qZWN0X2lkIn0Ke30Ke30=",
"inputTruncated": false,
"encoding": "base64"
},
"origin": {
"custom": {
"customHeaders": {},
"domainName": "example.org",
"keepaliveTimeout": 5,
"path": "",
"port": 443,
"protocol": "https",
"readTimeout": 30,
"sslProtocols": ["TLSv1", "TLSv1.1", "TLSv1.2"]
}
},
"querystring": "",
"uri": "/_tunnel"
}
}
}
]
}
AppDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
# TODO: Put in the rest of your CloudFront config.
CacheBehaviors:
- TargetOriginId: SentryOrigin
PathPattern: /_tunnel # TODO: Choose tunnel path.
ViewerProtocolPolicy: redirect-to-https
AllowedMethods:
- DELETE
- GET
- HEAD
- OPTIONS
- PATCH
- POST
- PUT
CachedMethods:
- GET
- HEAD
ForwardedValues:
QueryString: false
Cookies:
Forward: 'none'
Headers:
- Origin
Origins:
- Id: SentryOrigin
DomainName: project_id.sentry.io # TODO: Set project id.
CustomOriginConfig:
OriginProtocolPolicy: https-only
OriginSSLProtocols:
- TLSv1.2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment