Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions charts/nudgebee-agent/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ globalConfig:
# Prometheus URL the runner queries (e.g. "http://prometheus-server.prometheus.svc:80").
# Surfaces as PROMETHEUS_URL on the runner pod. Leave empty to let the agent auto-discover.
prometheus_url: ""
# Optional headers (comma-separated "Header: value" pairs). Surfaces as PROMETHEUS_HEADERS.
# Optional headers (semicolon-separated "Header: value" pairs, e.g. "X-Scope-OrgID: t1; Authorization: Bearer x"). Surfaces as PROMETHEUS_HEADERS.
prometheus_headers: ""
# Optional labels appended to every PromQL query (legacy multi-cluster parity).
# A YAML map, e.g. {k8s_cluster: aws-prod}. Surfaces as PROMETHEUS_ADDITIONAL_LABELS
Expand Down Expand Up @@ -65,7 +65,7 @@ runnerServiceAccount:
runner:
image:
repository: ghcr.io/nudgebee/nudgebee-agent
tag: 2026-08-12T06-01-54_7ec58f7701909a3ce172ad2a9235f8b15255e363
tag: 2026-08-12T08-10-04_e13ce92bc8c238e883d050b1276c6128b2c8fb44
# 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.
Expand Down
2 changes: 1 addition & 1 deletion runner/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ NUDGEBEE_ENDPOINT=https://api.nudgebee.com
# --- Observability (optional but typical) -----------------------------------

# PROMETHEUS_URL=http://prometheus.monitoring.svc:9090
# PROMETHEUS_HEADERS=Authorization: Bearer xxx # static header auth (basic/bearer)
# PROMETHEUS_HEADERS=Authorization: Bearer xxx # static header auth (basic/bearer); multiple headers are ";"-separated
# Managed Prometheus auth (pick one; precedence AWS → Coralogix → Azure):
# AWS SigV4 (Amazon Managed Prometheus): AWS_ACCESS_KEY / AWS_SECRET_ACCESS_KEY / AWS_REGION [/ AWS_SERVICE_NAME=aps]
# Coralogix: CORALOGIX_PROMETHEUS_TOKEN
Expand Down
6 changes: 3 additions & 3 deletions runner/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ If a K8s subsystem is enabled but the agent fails to build a K8s client (no kube
| Variable | Required | Description |
|---|---|---|
| `PROMETHEUS_URL` | recommended | Enables `prometheus_*` actions and `service_map` |
| `PROMETHEUS_HEADERS` | optional | Comma-separated `Header: value` pairs (e.g. `X-Scope-OrgID: tenant-1`); use for static basic/bearer auth |
| `PROMETHEUS_HEADERS` | optional | Semicolon-separated `Header: value` pairs (e.g. `X-Scope-OrgID: tenant-1`); use for static basic/bearer auth |
| `AWS_ACCESS_KEY` / `AWS_SECRET_ACCESS_KEY` / `AWS_REGION` | optional | Managed Prometheus: sign requests with AWS SigV4. `AWS_SERVICE_NAME` defaults to `aps` |
| `CORALOGIX_PROMETHEUS_TOKEN` | optional | Managed Prometheus: sent as `token` header |
| `AZURE_USE_MANAGED_ID` / `AZURE_CLIENT_SECRET` (+ `AZURE_CLIENT_ID` / `AZURE_TENANT_ID`) | optional | Managed Prometheus: Azure AD Bearer token (managed identity or client-secret). Precedence: AWS → Coralogix → Azure |
Expand All @@ -68,9 +68,9 @@ If a K8s subsystem is enabled but the agent fails to build a K8s client (no kube
| Variable | Description |
|---|---|
| `ALERTMANAGER_URL` | Enables `get_silences`, `add_silence`, `delete_silence` |
| `ALERTMANAGER_HEADERS` | Comma-separated headers for AlertManager |
| `ALERTMANAGER_HEADERS` | Semicolon-separated headers for AlertManager |
| `LOKI_RULES_URL` | Loki ruler component URL — enables `create_loki_alert_rule`, etc. |
| `LOKI_RULES_HEADERS` | Comma-separated headers for Loki rules API |
| `LOKI_RULES_HEADERS` | Semicolon-separated headers for Loki rules API |

## Authentication

Expand Down
14 changes: 9 additions & 5 deletions runner/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,16 +386,20 @@ func ParseTargets(s string) map[string]string {
return out
}

// ParseHeaders splits a comma-separated "Header: value" string into an
// http.Header. Returns an empty Header for empty input. Same shape used
// by the GRAFANA_EXTRA_HEADER / LOKI_EXTRA_HEADER pattern (a single
// "Header: value" or comma-separated list).
// ParseHeaders splits a semicolon-separated "Header: value" string into an
// http.Header. Returns an empty Header for empty input. Same shape used by
// the GRAFANA_EXTRA_HEADER / LOKI_EXTRA_HEADER / PROMETHEUS_HEADERS pattern
// (a single "Header: value" or ";"-separated list).
//
// The legacy Python agent split these vars on ";", so a header *value* may
// itself contain a comma (e.g. a multi-value Accept header) without being
// truncated. Multi-header configs must use ";" between pairs.
func ParseHeaders(s string) http.Header {
h := http.Header{}
if s == "" {
return h
}
for _, part := range strings.Split(s, ",") {
for _, part := range strings.Split(s, ";") {
part = strings.TrimSpace(part)
if part == "" {
continue
Expand Down
8 changes: 6 additions & 2 deletions runner/pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,16 +176,20 @@ func TestParseHeaders(t *testing.T) {
{"one", "X-Scope-OrgID: tenant-1", http.Header{"X-Scope-Orgid": []string{"tenant-1"}}},
{
"multi",
"X-Scope-OrgID: tenant-1, Authorization: Bearer abc",
"X-Scope-OrgID: tenant-1; Authorization: Bearer abc",
http.Header{
"X-Scope-Orgid": []string{"tenant-1"},
"Authorization": []string{"Bearer abc"},
},
},
{"trims_whitespace", " X-A : v ", http.Header{"X-A": []string{"v"}}},
{"skips_invalid", "no-colon-here, X-Y: ok", http.Header{"X-Y": []string{"ok"}}},
{"skips_invalid", "no-colon-here; X-Y: ok", http.Header{"X-Y": []string{"ok"}}},
{"value_can_contain_colons", "Authorization: Bearer x:y:z",
http.Header{"Authorization": []string{"Bearer x:y:z"}}},
// A comma inside a value must survive — we split on ";" only, so a
// multi-value header (e.g. Accept) is not truncated at the comma.
{"value_can_contain_comma", "Accept: text/html, application/json",
http.Header{"Accept": []string{"text/html, application/json"}}},
}
Comment on lines +189 to 193

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add a test case to verify that semicolons inside header values (such as parameters in Content-Type or Accept headers) are correctly preserved and not truncated.

		// A comma inside a value must survive — we split on ";" only, so a
		// multi-value header (e.g. Accept) is not truncated at the comma.
		{"value_can_contain_comma", "Accept: text/html, application/json",
			http.Header{"Accept": []string{"text/html, application/json"}}},
		// A semicolon inside a value must survive if it's part of a parameter (no colon)
		{"value_can_contain_semicolon", "Content-Type: application/json; charset=utf-8; X-Scope-OrgID: t1",
			http.Header{
				"Content-Type":  []string{"application/json; charset=utf-8"},
				"X-Scope-Orgid": []string{"t1"},
			},
		},
	}

for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion runner/pkg/grafana/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ type Proxy struct {
PrometheusURL string

// PrometheusHeaders is the parsed PROMETHEUS_HEADERS env (raw
// "Header: value, Header: value" string) — applied to every
// "Header: value; Header: value" string) — applied to every
// Prometheus proxy request so X-Scope-OrgID / tenant headers reach
// the upstream. Same shape ExtraHeaders uses for Grafana.
PrometheusHeaders http.Header
Expand Down
Loading