github-webhook-proxy is a request forwarder
for GitHub webhooks from github.com
to internal enterprise destinations, designed for use in Github Enterprise Cloud.
Usage
To use this Terraform module, you will need an S3 bucket containing a proxy-lambda.zip asset attached to each release of this repository.
This zip file can be uploaded using the following script:
# Upload Lambda to s3_destination file="proxy-lambda.zip" curl -o "${file}" -fL https://github.com/ExpediaGroup/github-webhook-proxy/releases/download/"${version}"/"${file}" aws s3 cp "${file}" "${s3_destination}/${file}"
Optionally, you may create a Lambda layer which optionally contains the following files:
allowed-destination-hosts.json: An array of destination hosts that the proxy can forward to. If omitted, all destinations will be allowed. Wildcard matching is supported via micromatchca.pem: A root CA certificate for forwarding to internal destinations with self-signed certscert.pem: A chain certificate for forwarding to internal destinations with self-signed certs
These files must be in a zipped layer directory, and this can be uploaded using the following script:
# Zip and Upload Lambda Layer to s3_destination file="proxy-lambda-layer.zip" zip -r -qq "${file}" layer aws s3 cp "${file}" "${s3_destination}/${file}"
If the layer is used, its ARN must be passed to the lambda_layer_arn Terraform variable.
Example Terraform Module Usage
module "github-webhook-proxy" { source = "git::https://github.com/ExpediaGroup/github-webhook-proxy.git?ref=v2" aws_region = var.aws_region vpc_id = data.aws_vpc.vpc.id subnet_ids = data.aws_subnets.subnets.ids lambda_bucket_name = local.lambda_bucket_name lambda_layer_arn = aws_lambda_layer_version.proxy_layer.arn enterprise_slug = 'my_enterprise' custom_tags = { "Application" = "github-webhook-proxy" } } data "aws_s3_object" "proxy_lambda_layer" { bucket = local.lambda_bucket_name key = "path/to/proxy-lambda-layer.zip" } resource "aws_lambda_layer_version" "proxy_layer" { layer_name = "github-webhook-proxy-layer" s3_bucket = data.aws_s3_object.proxy_lambda_layer.bucket s3_key = data.aws_s3_object.proxy_lambda_layer.key s3_object_version = data.aws_s3_object.proxy_lambda_layer.version_id } locals { lambda_bucket_name = "proxy-lambda-bucket" }
Adding a New Webhook
- Create the webhook proxy URL
- Obtain your desired destination URL, i.e. the internal endpoint where you want to send webhooks.
- Encode your destination URL! An easy way to do this is to use
jqin your terminal (install it here if you don't have it already):jq -rn --arg x 'YOUR_DESTINATION_URL_HERE' '$x|@uri' - Paste the encoded URL at the end of the webhook proxy base URL (
https://YOUR_API_URL/webhook).
- Add the webhook to your repository
- As an administrator, navigate to your repository settings -> Webhooks -> Add webhook
- Paste your webhook proxy URL in the "Payload URL" box. You do not need to worry about "Content type".
- By default, GitHub will only send requests on the "push" event, but you may configure it to send on other events as well.
- Click "Add webhook"
Example Webhook Proxy URL Creation
Destination URL: https://my-destination.url/endpoint
⬇️
Encoded destination URL: https%3A%2F%2Fmy-destination.url%2Fendpoint%2F
⬇️
Webhook URL: https://YOUR_API_URL/webhook/https%3A%2F%2Fmy-destination.url%2Fendpoint%2F
Technical Overview
Incoming Request
When a webhook from github.com is sent to https://YOUR_API_URL/webhook, it is routed to the API Gateway resource via DNS mapping. The API Gateway has an IP allowlist which only accepts requests originating from GitHub Hooks IP ranges. This ensures that the proxy endpoint can only be accessed by webhook requests from github.com.
Request Parsing
The API Gateway then invokes the Lambda function, which parses the request body from the
supported content types
.
Each request to the webhook proxy must adhere to the following format:
https://YOUR_API_URL/webhook/${endpointId}
where endpointId is an encoded destination URL. The Lambda decodes
the endpointId to make it a valid URL.
Request Validation
The Lambda then performs the following validations:
- The request must have an enterprise slug which matches the
enterprise_slugenvironment variable, OR the request must come from a personal repository where the username ends in the enterprise managed user suffix (if provided). The user suffix is passed via theenterprise_managed_user_suffixTerraform variable. - The request host must have an approved destination URL host, which is the decoded
endpointIdspecified in the request URL. The list of allowed destination hosts is read fromallowed-destination-hosts.jsonin the Lambda layer.
TLS Support
If a root and chain certificate are not provided in the Lambda layer, the runtime environment will supply certificates for requests.
If these certificates are provided, however, the proxy will forward each request with ca.pem and cert.pem as the
root and chain, respectively, with the root certificate appended to the Mozilla CA certificate store.
Webhook Proxy Responses
If any of these validations fail, the webhook proxy will return a 403 unauthorized error. If all validations pass, the request payload and headers are forwarded to the specified destination URL, and the proxy will return the response it receives from the destination. If an unexpected error occurs, the webhook proxy will return a 500 internal server error.
Repository Overview
Terraform Module
This repository contains Terraform (*.tf) files which are intended to be consumed as a Terraform module.
The files are generally organized by resource type. See the "Resources" section in USAGE.md for more infrastructure details.
Lambda Function
The Lambda function is a Node.js Lambda compiled from Typescript, and lives in the "lambda" directory.
GitHub IP Range Allowlist
This repo has a GitHub Actions workflow which checks that the GitHub Hooks IP Ranges file is up to date. It runs a script once a day which calls https://api.github.com/meta and ensures the IP ranges in "hooks" match our current IP allowlist in the API Gateway. If the list is out of date, it will create a PR to update it.