Remove inbound request signature verification (closes #279)
All checks were successful
check / check (push) Successful in 3m14s
All checks were successful
check / check (push) Successful in 3m14s
This commit was merged in pull request #281.
This commit is contained in:
@@ -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),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user