State the UUID-is-the-credential rule as a rule (closes #301) #302
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
|
||||
/ 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
|
||||
|
||||
### Prerequisites
|
||||
@@ -1149,14 +1156,38 @@ backups at rest and restrict who can read them.
|
||||
|
||||
## The entrypoint URL is the authentication secret
|
||||
|
||||
The receiver verifies nothing about an inbound request. The UUID in an
|
||||
entrypoint's URL is its credential: anyone who holds that URL can
|
||||
submit events to it, and the receiver checks nothing else about the
|
||||
sender. Treat an entrypoint URL the way you would treat an API token.
|
||||
**The entrypoint UUID is the credential, and it is the only one.**
|
||||
webhooker mints a version 4 UUID per entrypoint and serves it at
|
||||
`/webhook/{uuid}`. Possession of that URL is the authentication:
|
||||
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
|
||||
entrypoint (or deactivate it, which answers `410`) and create a new
|
||||
one, then point the sender at the new URL.
|
||||
There is no shared secret, no HMAC signature, no bearer token and no
|
||||
second factor on the receiver, and none will be added. This was
|
||||
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
|
||||
|
||||
@@ -2867,6 +2898,10 @@ check, see [The login endpoint](#the-login-endpoint).
|
||||
|
||||
### 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
|
||||
encrypted cookies. Sessions are configured with HttpOnly, SameSite
|
||||
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
|
||||
- **The entrypoint URL is the receiver's only credential.** Nothing
|
||||
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))
|
||||
- **SSRF prevention** for HTTP delivery targets: private/reserved IP
|
||||
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
|
||||
// destination that redirects its own path would lose its
|
||||
// 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(
|
||||
t *testing.T,
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user