package delivery_test import ( "net/url" "testing" "github.com/stretchr/testify/assert" "sneak.berlin/go/webhooker/internal/database" "sneak.berlin/go/webhooker/internal/delivery" ) // The secret path segments of a Slack incoming webhook URL. const ( redactSecretPath = "/services/T11111111/B11111111/" + "YYYYYYYYYYYYYYYYYYYYYYYY" redactWebhookURL = "https://hooks.slack.com" + redactSecretPath ) func TestRedactor_RemovesSlackWebhookURL(t *testing.T) { t.Parallel() r := delivery.NewRedactor(&database.Target{ Type: database.TargetTypeSlack, Config: `{"webhookUrl":"` + redactWebhookURL + `"}`, }) got := r.Redact("no_service for " + redactWebhookURL) assert.NotContains(t, got, redactSecretPath) assert.NotContains(t, got, "T11111111") assert.Contains(t, got, delivery.RedactionMarker) assert.Contains(t, got, "no_service for ") } // TestRedactor_RemovesBarePath covers a remote that echoes // only the request path rather than the whole URL. The path // segments are the credential on their own. func TestRedactor_RemovesBarePath(t *testing.T) { t.Parallel() r := delivery.NewRedactor(&database.Target{ Type: database.TargetTypeSlack, Config: `{"webhookUrl":"` + redactWebhookURL + `"}`, }) got := r.Redact("POST " + redactSecretPath + " 404") assert.NotContains(t, got, redactSecretPath) assert.Equal( t, "POST "+delivery.RedactionMarker+" 404", got, ) } // TestRedactor_RemovesHTTPURLQueryAndUserinfo covers the HTTP // target, whose destination is an arbitrary URL: the query // string and the userinfo carry credentials as readily as the // path does. func TestRedactor_RemovesHTTPURLQueryAndUserinfo(t *testing.T) { t.Parallel() // Assembled rather than written out, so the literal is // not itself a credential-shaped string. dest := url.URL{ Scheme: "https", User: url.UserPassword("user", "hunter2"), Host: "example.com", Path: "/in", RawQuery: "token=s3cr3t", } raw := dest.String() r := delivery.NewRedactor(&database.Target{ Type: database.TargetTypeHTTP, Config: `{"url":"` + raw + `"}`, }) for _, echoed := range []string{ raw, "/in?token=s3cr3t", "hunter2", } { got := r.Redact("rejected: " + echoed) assert.NotContains(t, got, "s3cr3t", echoed) assert.NotContains(t, got, "hunter2", echoed) assert.Contains( t, got, delivery.RedactionMarker, echoed, ) } } // TestRedactor_LeavesUnrelatedTextAlone pins that the // redactor matches literally: it does not guess at what a // secret looks like, so ordinary response content survives. func TestRedactor_LeavesUnrelatedTextAlone(t *testing.T) { t.Parallel() const response = "ok=false error=channel_not_found" r := delivery.NewRedactor(&database.Target{ Type: database.TargetTypeSlack, Config: `{"webhookUrl":"` + redactWebhookURL + `"}`, }) assert.Equal(t, response, r.Redact(response)) } // TestRedactor_ZeroValueAndConfiglessTargets pins that a // caller with no target, an unparseable config, or a target // type with no destination URL gets a redactor that changes // nothing rather than one that panics. func TestRedactor_ZeroValueAndConfiglessTargets(t *testing.T) { t.Parallel() const text = "some response body" var zero delivery.Redactor assert.Equal(t, text, zero.Redact(text)) assert.Equal(t, text, delivery.NewRedactor(nil).Redact(text)) for _, tgt := range []database.Target{ {Type: database.TargetTypeLog}, {Type: database.TargetTypeDatabase}, {Type: database.TargetTypeSlack, Config: "not json"}, {Type: database.TargetTypeHTTP, Config: ""}, } { assert.Equal( t, text, delivery.NewRedactor(&tgt).Redact(text), tgt.Type, ) } }