From 600e6e4404c45681d0b207380755ae2af13cc497 Mon Sep 17 00:00:00 2001 From: Venkat Date: Sat, 5 Sep 2026 16:43:55 +0000 Subject: [PATCH] fix: fill in promtool's server argument, refuse the paths that cannot authenticate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `promtool` was the only query wrapper that did not point itself at the cluster. `logcli` sets `--addr` and `tempo-cli` sets `--path-prefix` from `grafana-ds`, but `bin/promtool` only appended the two auth headers, so the bare form failed: $ toolbox promtool query instant 'sum(up)' promtool.real: error: required argument 'expr' not provided The caller had to hand-build `https://grafana./api/datasources/proxy/uid/`, which the README already said they would not have to. Resolve the prometheus datasource UID and insert the `` positional for `query instant` and `query range`. Those two are also the only subcommands that can work here at all: `query series`, `query labels` and every `debug` subcommand take no `--header`, so the old wrapper appended a flag they reject — $ toolbox promtool debug pprof promtool.real: error: unknown long flag '--header' — and even without the flag their requests would arrive at the edge carrying no credential and be redirected to the login page. Refuse those against this cluster with a message that says why, following the precedent of `tempo-cli` refusing `--use-grpc`. An explicit `http(s)` server is always passed through untouched, so promtool still works against anything else. Verified against nonprod.jupiter.onglueops.rocks: bare `query instant`/`range` return data, the refused paths print the reason, an explicit server still works, and `--help` plus the local subcommands (`check`, `push`, `test`, `tsdb`) are untouched. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01TrpnuCduw2KivngVRQmnG7 --- README.md | 10 +++++++- bin/promtool | 71 ++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index c393253..e250e41 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ container variable below is passed through if set. | `toolbox-login --begin` / `--wait` | The same login in two halves: print the URL and return (idempotent), then wait for approval — about 90 s per call, exit 2 means call again. For callers that can't sit on a blocking command. | | `toolbox-login --force` | Re-authenticate, e.g. to switch accounts. | | `toolbox-token` | Print the raw token, for scripting. | -| `promtool …` | Prometheus CLI, pointed at Thanos. Metrics, plus alert state and rule listings. | +| `promtool query instant\|range …` | Prometheus CLI, pointed at Thanos. Metrics, plus alert state. The server argument is filled in for you. `query series`/`labels` and `debug` take no `--header`, so they cannot reach the cluster. | | `logcli …` | Loki CLI. Log queries and `--tail`. | | `tempo-cli query api …` | Tempo CLI. TraceQL search and trace lookup. | | `grafana-ds ` | Print a datasource UID (`prometheus`/`loki`/`tempo`); used by the wrappers. | @@ -196,6 +196,14 @@ directly, so there is one edge host and one credential for everything. The wrappers resolve the datasource UID at run time (`grafana-ds`), since Grafana generates UIDs per cluster. +`promtool` differs in one way the wrapper hides: only `query instant` and +`query range` accept `--header`. Those two get the `` positional filled in +with the proxy URL. `query series`, `query labels` and every `debug` subcommand +take no `--header`, so a request from them reaches the edge with no credential and +is redirected to the login page; the wrapper refuses those against this cluster +rather than letting them fail as a confusing 302, and passes an explicit +`http(s)` server through untouched. + `tempo-cli` differs in three ways the wrapper hides: headers are `Key=Value` not `Key: Value`; `search` takes a bare host plus `--path-prefix` while `trace-id` takes a full URL; and `--use-grpc` is refused, because headers would then travel diff --git a/bin/promtool b/bin/promtool index 4518699..3666c8c 100755 --- a/bin/promtool +++ b/bin/promtool @@ -5,20 +5,59 @@ # oauth2-proxy at the edge, X-JWT-Assertion by Grafana's [auth.jwt]. Sending only # one gets a 302 to the login page. # -# `query`/`debug` subcommands take a argument; everything else (check, -# push, test) is local and passed straight through. +# Only `query instant` and `query range` accept --header, so they are the only +# subcommands that can carry a credential and therefore the only ones that can +# reach this cluster. For them the wrapper also resolves the datasource UID and +# inserts the positional, so callers never build the proxy URL by hand -- +# the same job logcli's --addr and tempo-cli's --path-prefix do. +# +# `query series`, `query labels` and every `debug` subcommand take no --header: +# their requests would arrive at the edge with no credential and be redirected to +# the login page, so aiming them at this cluster is refused rather than left to +# fail as a confusing 302. An explicit http(s) server is always passed through +# untouched, for promtool against something else. +# +# Everything else (check, push, test, tsdb) is local and passed straight through. set -euo pipefail -case "${1:-}" in - query|debug) - if ! TOKEN="$(toolbox-token --no-login)"; then - echo "toolbox: not authenticated. Run 'toolbox-login' first." >&2; exit 1 - fi - sub="$1"; shift - exec /usr/local/bin/promtool.real "$sub" "$@" \ - --header "Authorization: Bearer ${TOKEN}" \ - --header "X-JWT-Assertion: ${TOKEN}" - ;; - *) - exec /usr/local/bin/promtool.real "$@" - ;; -esac + +# Is any argument already a server URL? An explicit one always wins. +has_server() { + local a + for a in "$@"; do + case "$a" in http://*|https://*) return 0 ;; esac + done + return 1 +} + +if [ "${1:-}" = "query" ]; then + sub="${2:-}" + # No subcommand, or asking for help: there is nothing to point at a server. + case "$sub" in ""|-*) exec /usr/local/bin/promtool.real "$@" ;; esac + shift 2 + case "$sub" in + instant|range) + if ! TOKEN="$(toolbox-token --no-login)"; then + echo "toolbox: not authenticated. Run 'toolbox-login' first." >&2; exit 1 + fi + hdrs=(--header "Authorization: Bearer ${TOKEN}" --header "X-JWT-Assertion: ${TOKEN}") + if has_server "$@"; then + exec /usr/local/bin/promtool.real query "$sub" "$@" "${hdrs[@]}" + fi + server="https://grafana.${TOOLBOX_CAPTAIN_DOMAIN}/api/datasources/proxy/uid/$(grafana-ds prometheus)" + exec /usr/local/bin/promtool.real query "$sub" "$server" "$@" "${hdrs[@]}" + ;; + *) + has_server "$@" && exec /usr/local/bin/promtool.real query "$sub" "$@" + echo "toolbox: 'promtool query $sub' takes no --header, so it cannot authenticate to this cluster. Use 'query instant' or 'query range' (labels are also available as 'logcli labels'), or pass an explicit server." >&2 + exit 1 + ;; + esac +fi + +if [ "${1:-}" = "debug" ]; then + has_server "${@:2}" && exec /usr/local/bin/promtool.real "$@" + echo "toolbox: 'promtool debug' takes no --header, so it cannot authenticate to this cluster. Pass an explicit server to use it against something else." >&2 + exit 1 +fi + +exec /usr/local/bin/promtool.real "$@"