diff --git a/pkg/detectors/docker/docker_auth_config.go b/pkg/detectors/docker/docker_auth_config.go index 53c86d96a..e22bb4391 100644 --- a/pkg/detectors/docker/docker_auth_config.go +++ b/pkg/detectors/docker/docker_auth_config.go @@ -188,6 +188,10 @@ func verifyMatch(ctx logContext.Context, client *http.Client, registry string, u return false, nil } + if strings.HasPrefix(h, "Basic") { + return false, nil + } + if !strings.HasPrefix(h, "Bearer") { return false, fmt.Errorf("unsupported WWW-Authenticate auth scheme: %s", h) } diff --git a/pkg/detectors/docker/docker_auth_config_test.go b/pkg/detectors/docker/docker_auth_config_test.go index 5ba34504f..6366fb952 100644 --- a/pkg/detectors/docker/docker_auth_config_test.go +++ b/pkg/detectors/docker/docker_auth_config_test.go @@ -1,11 +1,14 @@ package docker import ( + "net/http" + "net/http/httptest" + "testing" + "github.com/google/go-cmp/cmp" "github.com/trufflesecurity/trufflehog/v3/pkg/context" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick" - "testing" ) func TestDocker_Pattern(t *testing.T) { @@ -299,3 +302,102 @@ func Test_ParseAuthenticateHeader(t *testing.T) { } } } + +func Test_VerifyMatch(t *testing.T) { + // base64 of "user:pass" + const basicAuth = "dXNlcjpwYXNz" + + // bearerChallenge points the token realm back at the test server so the follow-up + // request stays in-process. + bearerChallenge := func(host string) string { + return `Bearer realm="http://` + host + `/token",service="registry"` + } + + tests := []struct { + name string + handler http.HandlerFunc + wantVerified bool + wantErr bool + }{ + { + name: "harbor rejects basic auth", + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Www-Authenticate", `Basic realm="harbor"`) + w.WriteHeader(http.StatusUnauthorized) + }, + wantVerified: false, + wantErr: false, + }, + { + name: "unauthorized without a challenge", + handler: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + }, + wantVerified: false, + wantErr: false, + }, + { + name: "unsupported scheme is still indeterminate", + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Www-Authenticate", `Digest realm="registry"`) + w.WriteHeader(http.StatusUnauthorized) + }, + wantVerified: false, + wantErr: true, + }, + { + name: "bearer token exchange rejects credentials", + handler: func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/token" { + w.WriteHeader(http.StatusUnauthorized) + return + } + w.Header().Set("Www-Authenticate", bearerChallenge(r.Host)) + w.WriteHeader(http.StatusUnauthorized) + }, + wantVerified: false, + wantErr: false, + }, + { + name: "bearer token exchange accepts credentials", + handler: func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/token" { + w.WriteHeader(http.StatusOK) + return + } + w.Header().Set("Www-Authenticate", bearerChallenge(r.Host)) + w.WriteHeader(http.StatusUnauthorized) + }, + wantVerified: true, + wantErr: false, + }, + { + name: "registry accepts basic auth", + handler: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{}`)) + }, + wantVerified: true, + wantErr: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + server := httptest.NewServer(tc.handler) + defer server.Close() + + verified, err := verifyMatch(context.Background(), server.Client(), server.URL, "user", basicAuth) + + if tc.wantErr && err == nil { + t.Errorf("expected a verification error, got nil") + } + if !tc.wantErr && err != nil { + t.Errorf("expected no verification error, got %v", err) + } + if verified != tc.wantVerified { + t.Errorf("verified = %v, want %v", verified, tc.wantVerified) + } + }) + } +}