harden production debug wrapper

This commit is contained in:
Chris Amow 2026-08-26 01:06:48 -05:00
parent 0768f6d783
commit d8dcb84edb
3 changed files with 24 additions and 5 deletions

View file

@ -45,6 +45,10 @@ on the diagnostics client at:
- Preserve the `restrict` and forced-command options in `authorized_keys`. - Preserve the `restrict` and forced-command options in `authorized_keys`.
- Verify arbitrary commands and malformed arguments are denied. - Verify arbitrary commands and malformed arguments are denied.
- Do not print container environment variables or application secrets. - Do not print container environment variables or application secrets.
- Ensure `status` passes its token to curl through stdin, not an argv `-H`
argument visible through `/proc`.
- Resolve the container into a variable before Docker calls so selector failure
propagates with the documented return code.
The account may run only: The account may run only:

View file

@ -109,6 +109,11 @@ Security invariants:
fail closed. fail closed.
- Verify denials as well as successful commands. Audit records are available via - Verify denials as well as successful commands. Audit records are available via
`journalctl -t chart-debug`. `journalctl -t chart-debug`.
- Keep secrets off process command lines. The status command supplies
`CHART_AUTH_TOKEN` to curl through stdin (`curl --config -`), never `-H`.
- Assign `resolve_container` before invoking Docker. Calling it inline through
command substitution can trap its exit in a subshell and obscure the intended
fail-closed return code.
## Observability ## Observability

View file

@ -53,7 +53,8 @@ case "${args[0]}" in
[[ "${args[2]}" =~ ^[1-9][0-9]*(s|m|h|d)$ ]] || { [[ "${args[2]}" =~ ^[1-9][0-9]*(s|m|h|d)$ ]] || {
echo "invalid duration" >&2; exit 2; echo "invalid duration" >&2; exit 2;
} }
docker logs --timestamps --tail "$MAX_LOG_LINES" --since "${args[2]}" "$(resolve_container)" 2>&1 | redact container=$(resolve_container)
docker logs --timestamps --tail "$MAX_LOG_LINES" --since "${args[2]}" "$container" 2>&1 | redact
;; ;;
status) status)
[[ ${#args[@]} -eq 1 ]] || { echo "usage: status" >&2; exit 2; } [[ ${#args[@]} -eq 1 ]] || { echo "usage: status" >&2; exit 2; }
@ -63,7 +64,13 @@ case "${args[0]}" in
[[ "$entry" == CHART_AUTH_TOKEN=* ]] && token=${entry#CHART_AUTH_TOKEN=} [[ "$entry" == CHART_AUTH_TOKEN=* ]] && token=${entry#CHART_AUTH_TOKEN=}
done < <(docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$container") done < <(docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$container")
if [[ -n "$token" ]]; then if [[ -n "$token" ]]; then
curl -fsS -H "X-Chart-Token: $token" "$CHART_PUBLIC_URL/api/status" | redact [[ "$token" != *$'\n'* && "$token" != *$'\r'* ]] || {
echo "invalid CHART_AUTH_TOKEN" >&2; exit 4;
}
escaped_token=${token//\\/\\\\}
escaped_token=${escaped_token//\"/\\\"}
printf 'url = "%s/api/status"\nheader = "X-Chart-Token: %s"\nfail\nsilent\nshow-error\n' \
"$CHART_PUBLIC_URL" "$escaped_token" | curl --config - | redact
else else
echo "CHART_AUTH_TOKEN is unavailable" >&2 echo "CHART_AUTH_TOKEN is unavailable" >&2
exit 4 exit 4
@ -71,21 +78,24 @@ case "${args[0]}" in
;; ;;
container-state) container-state)
[[ ${#args[@]} -eq 1 ]] || { echo "usage: container-state" >&2; exit 2; } [[ ${#args[@]} -eq 1 ]] || { echo "usage: container-state" >&2; exit 2; }
container=$(resolve_container)
docker inspect --format \ docker inspect --format \
'name={{.Name}} image={{.Config.Image}} status={{.State.Status}} started={{.State.StartedAt}} restarts={{.RestartCount}} oom={{.State.OOMKilled}} exit={{.State.ExitCode}}' \ 'name={{.Name}} image={{.Config.Image}} status={{.State.Status}} started={{.State.StartedAt}} restarts={{.RestartCount}} oom={{.State.OOMKilled}} exit={{.State.ExitCode}}' \
"$(resolve_container)" "$container"
;; ;;
recent-deploy) recent-deploy)
[[ ${#args[@]} -eq 1 ]] || { echo "usage: recent-deploy" >&2; exit 2; } [[ ${#args[@]} -eq 1 ]] || { echo "usage: recent-deploy" >&2; exit 2; }
curl -fsS "$CHART_PUBLIC_URL/api/version" curl -fsS "$CHART_PUBLIC_URL/api/version"
printf '\n' printf '\n'
docker inspect --format 'container_started={{.State.StartedAt}} image={{.Config.Image}}' "$(resolve_container)" container=$(resolve_container)
docker inspect --format 'container_started={{.State.StartedAt}} image={{.Config.Image}}' "$container"
;; ;;
capture-read) capture-read)
[[ ${#args[@]} -eq 2 ]] && valid_capture_id "${args[1]}" || { [[ ${#args[@]} -eq 2 ]] && valid_capture_id "${args[1]}" || {
echo "usage: capture-read c-XXXXXXXXXXXX" >&2; exit 2; echo "usage: capture-read c-XXXXXXXXXXXX" >&2; exit 2;
} }
docker exec "$(resolve_container)" base64 "/tmp/chart-captures/${args[1]}.png" container=$(resolve_container)
docker exec "$container" base64 "/tmp/chart-captures/${args[1]}.png"
;; ;;
capture-delete) capture-delete)
[[ ${#args[@]} -eq 2 ]] && valid_capture_id "${args[1]}" || { [[ ${#args[@]} -eq 2 ]] && valid_capture_id "${args[1]}" || {