State the UUID-is-the-credential rule as a rule (closes #301)
All checks were successful
check / check (push) Successful in 3m35s
All checks were successful
check / check (push) Successful in 3m35s
The receiver has authenticated on the entrypoint UUID alone since inbound signature verification was removed in #279. The README described that as the current state; it did not say it is the decision. Restate it as the rule, so a proposal to add HMAC, a shared secret or a bearer token to the receiver is contradicted by the docs rather than merely unimplemented. The rule now appears in the intro, in its own section, and in the Authentication and Security lists, and carries the two consequences an operator has to act on: the URL is a capability to be kept out of logs and tickets, and rotation means minting a new entrypoint rather than changing a key. Also corrects one stale comment: a redirect test said the inbound signature was one "the receiver verifies", which in this repo's vocabulary names webhooker's own receiver. The endpoint that verifies it is the delivery target's.
This commit is contained in:
52
README.md
52
README.md
@@ -7,6 +7,13 @@ services, durably stores them, and delivers them to configured targets
|
|||||||
with retry support, logging, and observability. Category: infrastructure
|
with retry support, logging, and observability. Category: infrastructure
|
||||||
/ web service. License: MIT.
|
/ web service. License: MIT.
|
||||||
|
|
||||||
|
Each entrypoint is a version 4 UUID served at `/webhook/{uuid}`, and
|
||||||
|
that UUID is the entrypoint's only credential. webhooker does not use
|
||||||
|
shared secrets, HMAC signatures or token headers on the receiver, and
|
||||||
|
will not add them — read
|
||||||
|
[The entrypoint URL is the authentication secret](#the-entrypoint-url-is-the-authentication-secret)
|
||||||
|
before deploying one.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
@@ -1149,14 +1156,38 @@ backups at rest and restrict who can read them.
|
|||||||
|
|
||||||
## The entrypoint URL is the authentication secret
|
## The entrypoint URL is the authentication secret
|
||||||
|
|
||||||
The receiver verifies nothing about an inbound request. The UUID in an
|
**The entrypoint UUID is the credential, and it is the only one.**
|
||||||
entrypoint's URL is its credential: anyone who holds that URL can
|
webhooker mints a version 4 UUID per entrypoint and serves it at
|
||||||
submit events to it, and the receiver checks nothing else about the
|
`/webhook/{uuid}`. Possession of that URL is the authentication:
|
||||||
sender. Treat an entrypoint URL the way you would treat an API token.
|
anyone who holds it can submit events to the entrypoint, and the
|
||||||
|
receiver verifies nothing else about the sender.
|
||||||
|
|
||||||
There is no way to rotate the UUID in place. To retire one, delete the
|
There is no shared secret, no HMAC signature, no bearer token and no
|
||||||
entrypoint (or deactivate it, which answers `410`) and create a new
|
second factor on the receiver, and none will be added. This was
|
||||||
one, then point the sender at the new URL.
|
considered and rejected; the implementation that existed was removed
|
||||||
|
in [PR #279](https://git.eeqj.de/sneak/webhooker/pulls/279), closing
|
||||||
|
[issue #67](https://git.eeqj.de/sneak/webhooker/issues/67) and
|
||||||
|
[issue #241](https://git.eeqj.de/sneak/webhooker/issues/241). A
|
||||||
|
proposal to reintroduce any of them — including as "defence in depth"
|
||||||
|
alongside the UUID — is answered by this section. Inbound signature
|
||||||
|
headers a sender sends anyway (`X-Hub-Signature` and its
|
||||||
|
per-provider equivalents) are stored and forwarded as ordinary
|
||||||
|
headers; nothing checks them.
|
||||||
|
|
||||||
|
What that means for an operator:
|
||||||
|
|
||||||
|
- **The URL is a capability, so treat it as a secret.** Keep it out of
|
||||||
|
logs, ticket bodies, chat messages and screenshots. Anyone who reads
|
||||||
|
it anywhere can post events as that sender.
|
||||||
|
- **Rotating means minting a new entrypoint, not changing a key.**
|
||||||
|
There is no way to rotate the UUID in place. To retire one, delete
|
||||||
|
the entrypoint (or deactivate it, which answers `410`) and create a
|
||||||
|
new one, then point the sender at the new URL.
|
||||||
|
- **A sender that cannot be given a secret URL is a constraint on that
|
||||||
|
integration, not a reason to change this.** If a service only
|
||||||
|
supports signed payloads to a well-known URL, raise it as its own
|
||||||
|
problem — pick a different integration path, or accept that it
|
||||||
|
cannot be used. It is not grounds to reintroduce shared secrets.
|
||||||
|
|
||||||
## Entrypoints
|
## Entrypoints
|
||||||
|
|
||||||
@@ -2867,6 +2898,10 @@ check, see [The login endpoint](#the-login-endpoint).
|
|||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
|
- **Webhook receiver:** the entrypoint UUID in the URL, and nothing
|
||||||
|
else. No shared secret, no HMAC signature, no token header, and none
|
||||||
|
will be added — see
|
||||||
|
[The entrypoint URL is the authentication secret](#the-entrypoint-url-is-the-authentication-secret).
|
||||||
- **Web UI:** Cookie-based sessions using gorilla/sessions with
|
- **Web UI:** Cookie-based sessions using gorilla/sessions with
|
||||||
encrypted cookies. Sessions are configured with HttpOnly, SameSite
|
encrypted cookies. Sessions are configured with HttpOnly, SameSite
|
||||||
Lax, and Secure whenever the request is on TLS — the flag follows the
|
Lax, and Secure whenever the request is on TLS — the flag follows the
|
||||||
@@ -2906,7 +2941,8 @@ check, see [The login endpoint](#the-login-endpoint).
|
|||||||
mode
|
mode
|
||||||
- **The entrypoint URL is the receiver's only credential.** Nothing
|
- **The entrypoint URL is the receiver's only credential.** Nothing
|
||||||
about an inbound request is verified; possession of the UUID
|
about an inbound request is verified; possession of the UUID
|
||||||
authorises submission (see
|
authorises submission, and no shared secret or signature check will
|
||||||
|
be added alongside it (see
|
||||||
[The entrypoint URL is the authentication secret](#the-entrypoint-url-is-the-authentication-secret))
|
[The entrypoint URL is the authentication secret](#the-entrypoint-url-is-the-authentication-secret))
|
||||||
- **SSRF prevention** for HTTP delivery targets: private/reserved IP
|
- **SSRF prevention** for HTTP delivery targets: private/reserved IP
|
||||||
ranges (RFC 1918, loopback, link-local, cloud metadata) are blocked
|
ranges (RFC 1918, loopback, link-local, cloud metadata) are blocked
|
||||||
|
|||||||
@@ -170,7 +170,8 @@ func TestDelivery_CrossOriginRedirectDropsOriginScopedHeaders(
|
|||||||
// Stripping must not fire within the configured origin, or every
|
// Stripping must not fire within the configured origin, or every
|
||||||
// destination that redirects its own path would lose its
|
// destination that redirects its own path would lose its
|
||||||
// credential and start answering 401 — and would lose the inbound
|
// credential and start answering 401 — and would lose the inbound
|
||||||
// signature the receiver verifies.
|
// signature header the target endpoint verifies. webhooker's own
|
||||||
|
// receiver verifies no signature; it only forwards the header.
|
||||||
func TestDelivery_SameOriginRedirectKeepsOriginScopedHeaders(
|
func TestDelivery_SameOriginRedirectKeepsOriginScopedHeaders(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user