diff --git a/content/developers/webhooks-and-events/securing-your-webhooks.md b/content/developers/webhooks-and-events/securing-your-webhooks.md index 4c079ea329a2..4cbe3ab69618 100644 --- a/content/developers/webhooks-and-events/securing-your-webhooks.md +++ b/content/developers/webhooks-and-events/securing-your-webhooks.md @@ -34,9 +34,17 @@ $ export SECRET_TOKEN=your_token ### Validating payloads from GitHub -When your secret token is set, GitHub uses it to create a hash signature with each payload. +When your secret token is set, {% data variables.product.product_name %} uses it to create a hash signature with each payload. This hash signature is included with the headers of each request as {% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "private-instances@latest" %}`X-Hub-Signature-256`{% else if currentVersion ver_lt "enterprise-server@2.23" %}`X-Hub-Signature`{% endif %}. -This hash signature is passed along with each request in the headers as `X-Hub-Signature`. Suppose you have a basic server listening to webhooks that looks like this: +{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "private-instances@latest" %} +{% note %} + +**Note:** For backward-compatibility, we also include the `X-Hub-Signature` header that is generated using the SHA-1 hash function. If possible, we recommend that you use the `X-Hub-Signature-256` header for improved security. The example below demonstrate using the `X-Hub-Signature-256` header. + +{% endnote %} +{% endif %} + +For example, if you have a basic server that listens for webhooks, it might be configured similar to this: ``` ruby require 'sinatra' @@ -48,7 +56,7 @@ post '/payload' do end ``` -The goal is to compute a hash using your `SECRET_TOKEN`, and ensure that the hash from GitHub matches. GitHub uses an HMAC hexdigest to compute the hash, so you could change your server to look a little like this: +The intention is to calculate a hash using your `SECRET_TOKEN`, and ensure that the result matches the hash from {% data variables.product.product_name %}. {% data variables.product.product_name %} uses an HMAC hex digest to compute the hash, so you could reconfigure your server to look a little like this: ``` ruby post '/payload' do @@ -59,16 +67,21 @@ post '/payload' do "I got some JSON: #{push.inspect}" end +{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "private-instances@latest" %} +def verify_signature(payload_body) + signature = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body) + return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_HUB_SIGNATURE_2']) +end{% else if currentVersion ver_lt "enterprise-server@2.23" %} def verify_signature(payload_body) signature = 'sha1=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), ENV['SECRET_TOKEN'], payload_body) return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_HUB_SIGNATURE']) -end +end{% endif %} ``` -Obviously, your language and server implementations may differ than this code. There are a couple of very important things to point out, however: +Your language and server implementations may differ from this example code. However, there are a number of very important things to point out: -* No matter which implementation you use, the hash signature starts with `sha1=`, using the key of your secret token and your payload body. +* No matter which implementation you use, the hash signature starts with {% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or "private-instances@latest" %}`sha256=`{% else if currentVersion ver_lt "enterprise-server@2.23" %}`sha1=`{% endif %}, using the key of your secret token and your payload body. -* Using a plain `==` operator is **not advised**. A method like [`secure_compare`][secure_compare] performs a "constant time" string comparison, which renders it safe from certain timing attacks against regular equality operators. +* Using a plain `==` operator is **not advised**. A method like [`secure_compare`][secure_compare] performs a "constant time" string comparison, which helps mitigate certain timing attacks against regular equality operators. [secure_compare]: http://rubydoc.info/github/rack/rack/master/Rack/Utils.secure_compare diff --git a/content/developers/webhooks-and-events/webhook-events-and-payloads.md b/content/developers/webhooks-and-events/webhook-events-and-payloads.md index bb2310d0cfeb..41f03e44d1f1 100644 --- a/content/developers/webhooks-and-events/webhook-events-and-payloads.md +++ b/content/developers/webhooks-and-events/webhook-events-and-payloads.md @@ -49,8 +49,9 @@ Header | Description `X-GitHub-Event`| Name of the event that triggered the delivery. `X-GitHub-Delivery`| A [GUID](http://en.wikipedia.org/wiki/Globally_unique_identifier) to identify the delivery.{% if currentVersion != "free-pro-team@latest" %} `X-GitHub-Enterprise-Version` | The version of the {% data variables.product.prodname_ghe_server %} instance that sent the HTTP POST payload. -`X-GitHub-Enterprise-Host` | The hostname of the {% data variables.product.prodname_ghe_server %} instance that sent the HTTP POST payload.{% endif %} -`X-Hub-Signature`| The HMAC hex digest of the response body. This header will be sent if the webhook is configured with a [`secret`](/v3/repos/hooks/#create-hook-config-params). The HMAC hex digest is generated using the `sha1` hash function and the `secret` as the HMAC `key`. +`X-GitHub-Enterprise-Host` | The hostname of the {% data variables.product.prodname_ghe_server %} instance that sent the HTTP POST payload.{% endif %}{% if currentVersion != "private-instances@latest" %} +`X-Hub-Signature`| This header is sent if the webhook is configured with a [`secret`](/v3/repos/hooks/#create-hook-config-params). This is the HMAC hex digest of the request body, and is generated using the SHA-1 hash function and the `secret` as the HMAC `key`.{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" %} `X-Hub-Signature` is provided for compatibility with existing integrations, and we recommend that you use the more secure `X-Hub-Signature-256` instead.{% endif %}{% endif %}{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "private-instances@latest" %} +`X-Hub-Signature-256`| This header is sent if the webhook is configured with a [`secret`](/v3/repos/hooks/#create-hook-config-params). This is the HMAC hex digest of the request body, and is generated using the SHA-256 hash function and the `secret` as the HMAC `key`.{% endif %} Also, the `User-Agent` for the requests will have the prefix `GitHub-Hookshot/`. @@ -62,8 +63,9 @@ Also, the `User-Agent` for the requests will have the prefix `GitHub-Hookshot/`. > Host: localhost:4567 > X-GitHub-Delivery: 72d3162e-cc78-11e3-81ab-4c9367dc0958{% if currentVersion != "free-pro-team@latest" %} > X-GitHub-Enterprise-Version: 2.15.0 -> X-GitHub-Enterprise-Host: example.com{% endif %} -> X-Hub-Signature: sha1=7d38cdd689735b008b3c702edd92eea23791c5f6 +> X-GitHub-Enterprise-Host: example.com{% endif %}{% if currentVersion != "private-instances@latest" %} +> X-Hub-Signature: sha1=7d38cdd689735b008b3c702edd92eea23791c5f6{% endif %}{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" or currentVersion == "private-instances@latest" %} +> X-Hub-Signature-256: sha256=d57c68ca6f92289e6987922ff26938930f6e66a2d161ef06abdf1859230aa23c{% endif %} > User-Agent: GitHub-Hookshot/044aadd > Content-Type: application/json > Content-Length: 6615 diff --git a/data/reusables/webhooks/secret.md b/data/reusables/webhooks/secret.md index ee5f2f79baef..d4d128984aec 100644 --- a/data/reusables/webhooks/secret.md +++ b/data/reusables/webhooks/secret.md @@ -1 +1 @@ -Setting a webhook secret allows you to ensure that `POST` requests sent to the payload URL are from GitHub. When you set a secret, you'll receive the `X-Hub-Signature` header in the webhook `POST` request. For more details on how to use the secret and the `X-Hub-Signature` header to secure your webhook payloads, see "[Securing your webhooks](/webhooks/securing/)." +Setting a webhook secret allows you to ensure that `POST` requests sent to the payload URL are from {% data variables.product.product_name %}. When you set a secret, you'll receive the {% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" %}`X-Hub-Signature` and `X-Hub-Signature-256` headers{% else if currentVersion ver_lt "enterprise-server@2.23" %}`X-Hub-Signature` header{% else if currentVersion == "private-instances@latest" %}`X-Hub-Signature-256` header{% endif %} in the webhook `POST` request. For more information on how to use a secret with a signature header to secure your webhook payloads, see "[Securing your webhooks](/webhooks/securing/)."