diff --git a/charts/nudgebee-agent/values.yaml b/charts/nudgebee-agent/values.yaml index 2491e5f..b5c1693 100644 --- a/charts/nudgebee-agent/values.yaml +++ b/charts/nudgebee-agent/values.yaml @@ -65,7 +65,7 @@ runnerServiceAccount: runner: image: repository: ghcr.io/nudgebee/nudgebee-agent - tag: 2026-07-14T03-38-04_fbc9436d684526a0b45488ac510a33e5168b5ae7 + tag: 2026-07-14T09-20-14_17a3cbad58bd80c14dee85a158cc4ec2de19e09f # Image template the pod_profiler action launches debugger pods from. # The agent substitutes `{}` for the variant (bpf, jvm, python, perf, ruby). # Surfaces as PROFILER_IMAGE; leave empty to fall back to the binary default. diff --git a/runner/pkg/enrichers/loki.go b/runner/pkg/enrichers/loki.go index a3af769..cbab51c 100644 --- a/runner/pkg/enrichers/loki.go +++ b/runner/pkg/enrichers/loki.go @@ -117,6 +117,12 @@ func (l *LokiCompat) rawGet(ctx context.Context, path string) ([]byte, error) { if err != nil { return nil, err } + // LOKI_USERNAME/LOKI_PASSWORD → Basic-Auth, mirroring the primitive + // loki.Client.get (client.go). The compat path was dropping these, + // causing 401s on any basic-auth Loki. + if l.c.Username != "" && l.c.Password != "" { + req.SetBasicAuth(l.c.Username, l.c.Password) + } for k, vv := range l.c.ExtraHeaders { for _, v := range vv { req.Header.Add(k, v) diff --git a/runner/pkg/enrichers/loki_test.go b/runner/pkg/enrichers/loki_test.go new file mode 100644 index 0000000..7137706 --- /dev/null +++ b/runner/pkg/enrichers/loki_test.go @@ -0,0 +1,63 @@ +package enrichers + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/nudgebee/nudgebee-agent/pkg/observability/loki" +) + +// TestLokiCompat_SendsBasicAuth verifies the compat handlers apply +// LOKI_USERNAME/LOKI_PASSWORD basic auth, matching the primitive +// loki.Client path. Regression guard for the 401-on-basic-auth-Loki bug. +func TestLokiCompat_SendsBasicAuth(t *testing.T) { + var gotUser, gotPass string + var gotOK bool + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotUser, gotPass, gotOK = r.BasicAuth() + _, _ = w.Write([]byte(`{"status":"success"}`)) + })) + defer srv.Close() + + lc := loki.New(srv.URL, &http.Client{Timeout: 5 * time.Second}) + lc.Username = "loki-user" + lc.Password = "loki-pass" + compat := NewLokiCompat(lc) + + h := compat.Handlers()["query_loki"] + if _, err := h(context.Background(), map[string]any{"query": "query={foo=\"bar\"}"}); err != nil { + t.Fatal(err) + } + if !gotOK { + t.Fatal("no basic auth header reached upstream") + } + if gotUser != "loki-user" || gotPass != "loki-pass" { + t.Errorf("basic auth = %q/%q; want loki-user/loki-pass", gotUser, gotPass) + } +} + +// TestLokiCompat_NoAuthWhenUnset confirms we don't send basic auth when +// username/password aren't configured (only one set counts as unset). +func TestLokiCompat_NoAuthWhenUnset(t *testing.T) { + var sawAuth bool + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _, sawAuth = r.BasicAuth() + _, _ = w.Write([]byte(`{}`)) + })) + defer srv.Close() + + lc := loki.New(srv.URL, &http.Client{Timeout: 5 * time.Second}) + lc.Username = "only-user" // password empty → must not send basic auth + compat := NewLokiCompat(lc) + + h := compat.Handlers()["query_loki"] + if _, err := h(context.Background(), map[string]any{"query": "query=x"}); err != nil { + t.Fatal(err) + } + if sawAuth { + t.Error("basic auth sent even though password is empty") + } +}