Skip to content

feat: support Bitbucket webhook secrets via X-Hub-Signature validation (#360)#386

Open
cdelmonte-zg wants to merge 1 commit into
masterfrom
feature/issue-360-webhook-secret
Open

feat: support Bitbucket webhook secrets via X-Hub-Signature validation (#360)#386
cdelmonte-zg wants to merge 1 commit into
masterfrom
feature/issue-360-webhook-secret

Conversation

@cdelmonte-zg

Copy link
Copy Markdown
Contributor

Implements webhook secret support requested in #360.

What

When a secret is configured on a Bitbucket webhook, Bitbucket Cloud and Bitbucket Server / Data Center sign every delivery with an X-Hub-Signature header of the form sha256=<hex>, where <hex> is the HMAC-SHA256 of the request body computed with the secret as key. Until now the plugin ignored that header: the endpoint is an UnprotectedRootAction, so anyone able to reach Jenkins could POST a forged payload and trigger jobs.

This PR adds an optional Webhook secret to the plugin's global configuration, selecting a Secret text credential. When set:

  • every request to the webhook endpoint must carry a valid X-Hub-Signature header;
  • a missing, malformed or non-matching signature is rejected with HTTP 403, before any payload parsing or acknowledgement;
  • a configured but unresolvable secret credential fails closed (403), with a SEVERE log;
  • the hex comparison is constant-time (MessageDigest.isEqual).

When no secret is configured, behaviour is unchanged: unsigned requests are accepted as before, and a stray signature header is ignored.

Design notes

Tests

  • SignatureUtilsTest (unit): known-answer vector verified independently with openssl dgst -sha256 -hmac, case-insensitive hex/prefix, empty body, tampered body, wrong secret, missing/blank header, sha1= prefix, malformed hex.
  • BitBucketPPRHookReceiverSignatureTest (@WithJenkins, e2e over HTTP): signed request accepted (200), unsigned/wrong-signature rejected (403), gzip-compressed delivery validated against the uncompressed bytes (200), unresolvable credential fails closed (403), no configured secret ignores the header (200), signed but malformed payload still rejected by Webhook receiver swallows undeserializable payloads: null payload triggers NPE and silent HTTP 200 instead of a clear 4xx #384 validation (400).

Full suite green locally (311 run, 0 failures, 12 pre-existing skips) on JDK 17.

Fixes #360.

When a webhook secret is configured on Bitbucket (Cloud or Server /
Data Center), every delivery is signed with an X-Hub-Signature header
carrying the hex-encoded HMAC-SHA256 of the request body. The plugin
can now validate that signature: a new 'Webhook secret' option in the
global configuration selects a Secret text credential holding the
secret, and when set, requests with a missing or invalid signature are
rejected with HTTP 403 before any payload processing. An unresolvable
secret credential fails closed.

The signature is computed by Bitbucket before any transport encoding,
so it is validated on the decompressed, not-yet-URL-decoded body; the
comparison is constant-time. Without a configured secret the behaviour
is unchanged and unsigned requests are accepted as before.

The receiver no longer caches the global configuration in a static
field: the static pinned the GlobalConfiguration instance of whichever
Jenkins was alive when the class loaded, going stale across Jenkins
instances (e.g. JenkinsRule tests in the same JVM).

Fixes #360
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bitbucket Webhook Secret

1 participant