Remove inbound request signature verification (closes #279)
All checks were successful
check / check (push) Successful in 3m14s

This commit was merged in pull request #281.
This commit is contained in:
2026-08-24 03:25:09 +02:00
parent ee2276a912
commit 37b59f8822
16 changed files with 59 additions and 2227 deletions

View File

@@ -1,142 +0,0 @@
package delivery_test
import (
"context"
"encoding/json"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sneak.berlin/go/webhooker/internal/database"
"sneak.berlin/go/webhooker/internal/delivery"
"sneak.berlin/go/webhooker/internal/signature"
)
// gitlabDeliverySecret is the shared secret the entrypoint in these
// tests is configured with. No outbound request may contain it.
const gitlabDeliverySecret = "QQDELIVERYSECRETQQ"
// receivedEventHeaders builds the Event.Headers value the receiver
// stores for an inbound request, by running the request's headers
// through the same sanitizer the receive path uses. Going through
// signature.SanitizeHeaders rather than a literal is the point of
// the test: it joins the two egresses at the field they share, so a
// regression at either end shows up here.
func receivedEventHeaders(
t *testing.T,
scheme database.SignatureScheme,
inbound http.Header,
) string {
t.Helper()
ep := &database.Entrypoint{
SignatureScheme: scheme,
SignatureSecret: gitlabDeliverySecret,
}
encoded, err := json.Marshal(
signature.SanitizeHeaders(ep, inbound),
)
require.NoError(t, err)
return string(encoded)
}
// TestApplyRequestHeadersDropsInboundCredential proves a delivery to
// an HTTP target does not carry the GitLab shared secret.
//
// isForwardableHeader is a blocklist of hop-by-hop names, so it
// forwards X-Gitlab-Token like any other header; what keeps the
// secret out of the outbound request is that the receiver never
// stored it. Handing a target operator the token would hand them the
// ability to forge requests to the entrypoint it authenticates,
// which is the one control the receiver has.
func TestApplyRequestHeadersDropsInboundCredential(t *testing.T) {
t.Parallel()
inbound := http.Header{}
inbound.Set(signature.HeaderGitLab, gitlabDeliverySecret)
inbound.Set("X-Gitlab-Event", "Push Hook")
event := &database.Event{
Headers: receivedEventHeaders(
t, database.SignatureSchemeGitLab, inbound,
),
ContentType: "application/json",
}
req, err := http.NewRequestWithContext(
context.Background(),
http.MethodPost,
"https://target.example.com/hook",
http.NoBody,
)
require.NoError(t, err)
delivery.ExportApplyRequestHeaders(
req, event, &delivery.HTTPTargetConfig{},
)
assert.Empty(
t,
req.Header.Values(signature.HeaderGitLab),
"the shared secret header must not reach a target",
)
// Header.Values canonicalises, so a differently-cased spelling
// would be caught above; this catches the value arriving under
// some other name.
for name, values := range req.Header {
for _, v := range values {
assert.NotContains(
t, v, gitlabDeliverySecret,
"secret present in outbound header %s", name,
)
}
}
// The rest of the sender's headers still arrive. A fix that
// dropped everything would pass the assertions above while
// breaking delivery.
assert.Equal(
t,
"Push Hook",
req.Header.Get("X-Gitlab-Event"),
)
}
// TestApplyRequestHeadersKeepsGitHubDigest proves the stripping is
// scoped to headers that carry the secret itself. GitHub's
// X-Hub-Signature-256 is an HMAC over the body, so a target can be
// shown it without being handed the key.
func TestApplyRequestHeadersKeepsGitHubDigest(t *testing.T) {
t.Parallel()
const digest = "sha256=deadbeef"
inbound := http.Header{}
inbound.Set(signature.HeaderGitHub, digest)
event := &database.Event{
Headers: receivedEventHeaders(
t, database.SignatureSchemeGitHub, inbound,
),
}
req, err := http.NewRequestWithContext(
context.Background(),
http.MethodPost,
"https://target.example.com/hook",
http.NoBody,
)
require.NoError(t, err)
delivery.ExportApplyRequestHeaders(
req, event, &delivery.HTTPTargetConfig{},
)
assert.Equal(
t, digest, req.Header.Get(signature.HeaderGitHub),
)
}