{"id":24854417,"url":"https://github.com/juggernaut/webhook-sentry","last_synced_at":"2025-10-15T00:31:40.824Z","repository":{"id":48961100,"uuid":"258363797","full_name":"juggernaut/webhook-sentry","owner":"juggernaut","description":"Egress proxy for webhooks","archived":false,"fork":false,"pushed_at":"2023-05-06T05:26:40.000Z","size":278,"stargazers_count":51,"open_issues_count":1,"forks_count":5,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-06-20T09:18:24.824Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/juggernaut.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-24T00:25:45.000Z","updated_at":"2024-01-30T11:32:03.000Z","dependencies_parsed_at":"2024-06-20T08:20:49.096Z","dependency_job_id":null,"html_url":"https://github.com/juggernaut/webhook-sentry","commit_stats":null,"previous_names":["juggernaut/egress-proxy"],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juggernaut%2Fwebhook-sentry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juggernaut%2Fwebhook-sentry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juggernaut%2Fwebhook-sentry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juggernaut%2Fwebhook-sentry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/juggernaut","download_url":"https://codeload.github.com/juggernaut/webhook-sentry/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":236540008,"owners_count":19165621,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-31T15:37:23.887Z","updated_at":"2025-10-15T00:31:35.482Z","avatar_url":"https://github.com/juggernaut.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Webhook Sentry [![Actions Status](https://github.com/juggernaut/egress-proxy/workflows/Go/badge.svg)](https://github.com/juggernaut/egress-proxy/actions) ![release](https://img.shields.io/github/v/release/juggernaut/webhook-sentry?sort=semver)\nWebhook Sentry is a proxy that helps you send [webhooks](https://en.wikipedia.org/wiki/Webhook) to your customers securely.\n\n## Why?\n### Security\nSending webhooks appears simple on the surface -- they're just HTTP requests after all. But sending them _securely_ is hard. If your application sends webhooks, does your implementation\n1. Prevent [SSRF](https://portswigger.net/web-security/ssrf) attacks?\n2. Protect against [DNS rebinding](https://en.wikipedia.org/wiki/DNS_rebinding) attacks?\n3. Support mutual TLS?\n4. Validate SSL certificate chains correctly?\n5. Use an updated CA certificate bundle?\n6. Specify reasonable idle socket and connection timeouts?\n\nBy proxying webhooks through Webhook Sentry, you get all of these for free.\n\n### Auditability\nSending webhooks involves making connections to untrusted and possibly malicious servers on the public internet. Maintaining an audit trail is essential for forensics and compliance.\nLimiting the set of instances that send such requests to a single proxy layer makes auditing simpler and more manageable.\n\n### Static Egress IPs\nMany customers require webhook requests to be sent from a list or range of static IPs in order to configure their firewalls. In a cloud environment with autoscaling, you\nmay not want to allocate static IPs to your application instances. In other situations, like serverless applications, it may be impossible to assign static IPs. With a centralized\negress proxy layer, you only need to assign static IPs to your proxy instances.\n\n## Getting Started\n\nWebhook Sentry runs on port 9090 by default. You can configure the address and port in the `listeners` section of the [config](#Configuration).\n\nThe simplest way to run Webhook Sentry is to use the latest binary:\n\n1. Download the [latest release](https://github.com/juggernaut/webhook-sentry/releases/latest) for your platform\n2. Run the downloaded binary:\n```\nwhsentry\n```\n\nWe also have a docker image:\n\n```\ndocker run -p 9090:9090 juggernaut/webhook-sentry:latest\n```\n\nYou can also pin a [tagged release](https://github.com/juggernaut/webhook-sentry/releases):\n\n```\ndocker run -p 9090:9090 juggernaut/webhook-sentry:v1.0.8\n```\n\nIf you need to override settings, you can mount a configuration file, pass in command line flags or set environment variables. See [configuration](#Configuration) for details.\n\nIf you need prometheus metrics for the service, allow access on port 2112 with something like `-p 2112:2112`.\n\n\n## Usage\n### HTTP target\n\n```\ncurl -x http://localhost:9090 http://www.google.com\n```\n\n### HTTPS target\nHTTP clients create a `CONNECT` tunnel when a proxy is configured and the target is a `https` URL. This does not give us the benefits of initiating TLS from the proxy. To get around this behavior, Webhook Sentry supports a unique way of proxying to HTTPS targets. Pass a `X-WhSentry-TLS` header and change the protocol to `http`:\n\n```\ncurl -v -x http://localhost:9090 --header 'X-WhSentry-TLS: true' http://www.google.com\n```\n\nAlthough `CONNECT` is supported, I strongly recommend using the header approach to take advantage of the TLS capabilities of Webhook Sentry, like mutual TLS and robust certificate validation.\n\n### Mutual TLS\nSpecify `clientCertFile` and `clientKeyFile` in the configuration to enable mutual TLS:\n```\nclientCertFile: /path/to/client.pem\nclientKeyFile: /path/to/key.pem\n```\n\n### Prometheus Metrics\n\nPoint your collector to \u003cproxy-address\u003e:2112 for metrics.\n\nE.g if the proxy is running on localhost, to verify metrics are correctly exposed:\n\n```\ncurl http://localhost:2112/metrics\n```\n\n## AWS EKS Configuration for Static IP egress\nTo deploy Webhook Sentry with a static egress IP addresses in AWS EKS, you'll need a node group with an Elastic IP address:\n\n - Create a new NAT Gateway.\n - Create an Elastic IP address and assign it to your NAT Gateway. This will be your egress IP.\n - Create a subnet dividing your network space.\n - Create a route table linking the subnet to the NAT Gateway.\n - Create a custom node group for the Webhook Sentry pods and assign it to the new subnet.\n - Finally, [assign](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/) your Webhook Sentry pods to that node group, and ensure your existing pods do not get assigned to it.\n\nFor redundancy, you may want to create multiple NAT Gatways, subnets, and egress IP addressses.\n\n\n## Protections\n### SSRF attack protection\nWebhook Sentry blocks access to private/internal IPs to prevent SSRF attacks:\n```\n$ curl -i -x http://localhost:9090 http://127.0.0.1:3000\n\nHTTP/1.1 403 Forbidden\nContent-Type: text/plain; charset=utf-8\nX-Content-Type-Options: nosniff\nX-Whsentry-Reason: IP 127.0.0.1 is blocked\nX-Whsentry-Reasoncode: 1000\nDate: Fri, 18 Sep 2020 07:15:20 GMT\nContent-Length: 24\n\nIP 127.0.0.1 is blocked\n```\n\nUnlike naive implementations, it also correctly checks the IP after DNS resolution. This example makes use of the [1u.ms](http://1u.ms/) service which can serve up DNS records using any IP we want:\n```\n$ curl -i -x http://localhost:9090 http://make-127-0-0-1-rr.1u.ms\n\nHTTP/1.1 403 Forbidden\nContent-Type: text/plain; charset=utf-8\nX-Content-Type-Options: nosniff\nX-Whsentry-Reason: IP 127.0.0.1 is blocked\nX-Whsentry-Reasoncode: 1000\nDate: Fri, 18 Sep 2020 07:21:58 GMT\nContent-Length: 24\n\nIP 127.0.0.1 is blocked\n```\n\n### DNS rebinding attack prevention\nA malicious attacker can set up their DNS such that it first resolves to a valid public IP adddress, but subsequent resolutions point to private/internal IP addresses. This can be used to exploit webhook implementations that validate the resolved IP using `getaddrinfo()` or equivalent, then pass the original URL to a HTTP client library which resolves the host a second time. Again, let's use 1u.ms to first return a valid public IP and then the loopback IP:\n\n```\n$ curl -i -x http://localhost:9090 http://make-3-221-81-55-rebind-127-0-0-1-rr.1u.ms/get\n\nHTTP/1.1 200 OK\nAccess-Control-Allow-Credentials: true\nAccess-Control-Allow-Origin: *\nContent-Length: 324\nContent-Type: application/json\nDate: Wed, 30 Sep 2020 07:38:47 GMT\nServer: gunicorn/19.9.0\n\n{\n  \"args\": {},\n  \"headers\": {\n    \"Accept\": \"*/*\",\n    \"Host\": \"make-3-221-81-55-rebind-127-0-0-1-rr.1u.ms\",\n    \"User-Agent\": \"Webhook Sentry/0.1\",\n    \"X-Amzn-Trace-Id\": \"Root=1-5f743607-afdf257ca619f90a14fc92b8\"\n  },\n  \"origin\": \"73.189.176.226\",\n  \"url\": \"http://make-3-221-81-55-rebind-127-0-0-1-rr.1u.ms/get\"\n}\n```\n\n### Mozilla CA certificate bundle\nWebhook Sentry uses the latest [Mozilla CA certificate bundle](https://www.mozilla.org/en-US/about/governance/policies/security-group/certs/) instead of relying on CA certificates bundled with the OS. This avoids the problem of out-of-date root CA certificates on older OS versions. See [this blog post](https://www.agwa.name/blog/post/fixing_the_addtrust_root_expiration) for why this is important. Notably, Stripe's webhooks were affected by this issue and took hours to fix.\n\nOn startup, Webhook Sentry checks if there is a newer version of the Mozilla CA certificate bundle than on disk, and if so, downloads it.\n\nAdditionally, by virtue of being written in Go, Webhook Sentry does not rely on OpenSSL or GnuTLS for certificate validation.\n\n## Configuration\nwebhook-sentry uses [viper](https://github.com/spf13/viper) for configuration. You can use a yaml file, environment variables or command line flags to provide configuration parameters.\nBy default, webhook-sentry looks for a file named `config.yaml` in the current working directory. You can specify a different file using the `--config \u003cfilename` flag.\n\nThe parameters documented below are in YAML, but most can also be provided as environment variables or command line flags:\n\n* `listener`: An HTTP/HTTPS endpoint the proxy listens on. For HTTPS endpoints, also specify `certFile` and `keyFile`.\n\n**Example**:\n```\nlistener:\n  type: https\n  address: 127.0.0.1:9091\n  certFile: /path/to/cert\n  keyFile: /path/to/key\n```\n\n* `connectTimeout`: Timeout for the TCP connection to the destination host.\n\n**Default**: 10s\n\n* `connectionLifetime`: Maximum time a connection to the destination can be alive.\n\n**Default**: 60s\n\n* `readTimeout`: Maximum time a connection to the destination can remain idle.\n\n**Default**: 10s\n\n* `maxResponseBodySize`: Maximum size of the HTTP response body in bytes. If `Content-Length` is specified in the response and it is greater than this value, the connection is torn down and the response is discarded. The client receives a 502.\n\n**Default**: 1048576\n\n* `clientCertFile`: Path to the client certificate to present to the destination (if enabling mutual TLS)\n\n* `clientKeyFile`: Path to the private key of the client certificate (if enabling mutual TLS)\n\n* `accessLog`: Specifies `type` and `file` of the proxy access log. `type` can be either `text` or `json`. By default, `text` is output to stdout.\n\n**Example**\n```\naccessLog:\n  type: json\n  file: /path/to/access.log\n```\n\n* `proxyLog`: Specifies `type` and `file` of the proxy application log. This log includes warnings and info messages related to handling proxy requests. By default, `text` is output to stdout.\n\n* `metrics.address`: Listening address of the Prometheus metrics endpoint.\n\n**Default**: :2112\n\n## Limitations\n* No IPv6 support\n* No TLSv1.3 support\n* No Proxy authentication\n* Proxy does not check client certificates (not to be confused with proxy presenting client certificate to the remote host)\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuggernaut%2Fwebhook-sentry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuggernaut%2Fwebhook-sentry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuggernaut%2Fwebhook-sentry/lists"}