diff --git a/internal/observabilityproxy/server.go b/internal/observabilityproxy/server.go index 0f9e533b7..2e29d3146 100644 --- a/internal/observabilityproxy/server.go +++ b/internal/observabilityproxy/server.go @@ -300,8 +300,8 @@ func (s *Server) readForm(w http.ResponseWriter, r *http.Request) *HTTPError { } func (s *Server) authorize(r *http.Request, route Route) (string, *HTTPError) { - token, ok := strings.CutPrefix(r.Header.Get("Authorization"), "Bearer ") - if !ok { + scheme, token, _ := strings.Cut(r.Header.Get("Authorization"), " ") + if !strings.EqualFold(scheme, "Bearer") { return "", errorf(http.StatusUnauthorized, "missing bearer token") } user, err := s.auth.Authenticate(r.Context(), strings.TrimSpace(token)) diff --git a/internal/observabilityproxy/server_test.go b/internal/observabilityproxy/server_test.go index fe3805961..7f397fcd3 100644 --- a/internal/observabilityproxy/server_test.go +++ b/internal/observabilityproxy/server_test.go @@ -452,3 +452,21 @@ func TestServerTailLongLine(t *testing.T) { t.Fatalf("long line not streamed intact: got %d bytes", len(body)) } } + +// The auth scheme is case-insensitive (RFC 9110, section 11.1), as +// kube-apiserver reads it. +func TestServerBearerSchemeCase(t *testing.T) { + h := newHarness(t, Config{}, nil) + for _, auth := range []string{"Bearer good", "bearer good", "BEARER good"} { + resp := h.do(http.MethodGet, logsRoot+"query", "", nil, map[string]string{"Authorization": auth}) + _ = resp.Body.Close() + if resp.StatusCode != http.StatusOK { + t.Errorf("Authorization %q answered %d, want 200", auth, resp.StatusCode) + } + } + resp := h.do(http.MethodGet, logsRoot+"query", "", nil, map[string]string{"Authorization": "Basic good"}) + _ = resp.Body.Close() + if resp.StatusCode != http.StatusUnauthorized { + t.Errorf("Basic scheme answered %d, want 401", resp.StatusCode) + } +}