Skip to content

fix(postgres): keep the Patroni switchover credential out of curl's argv - #1179

Open
paulocsanz wants to merge 1 commit into
masterfrom
pcs/patroni-switchover-credential-stdin
Open

fix(postgres): keep the Patroni switchover credential out of curl's argv#1179
paulocsanz wants to merge 1 commit into
masterfrom
pcs/patroni-switchover-credential-stdin

Conversation

@paulocsanz

Copy link
Copy Markdown
Collaborator

What

railway postgres ha switchover (src/controllers/patroni.rs) authenticates against Patroni's REST API inside the member's container. Since #1171 the credential reached curl as -u "$PATRONI_REST_USER:$PATRONI_REST_PW". It now reaches curl as a one-line config document on stdin, user = "user:pass" piped into curl -K -, escaped for curl's config parser by a pure-POSIX shell function (curl_cfg_quote: \ and " backslashed, newline to \n).

Unchanged: the credential is resolved inside the container from the member's own environment (PATRONI_RESTAPI_PASSWORD > PATRONI_SUPERUSER_PASSWORD > PGPASSWORD > POSTGRES_PASSWORD; username PATRONI_RESTAPI_USERNAME > PATRONI_SUPERUSER_USERNAME > PGUSER > POSTGRES_USER > postgres); a member with no password gets a bare POST (no -K, no document); Patroni's status code and response body surface as the error.

Why

HA internal-credential audit (2026-09-08), finding LOW-7 / REPORT-mono-cli Finding 7: src/controllers/patroni.rs:110 on master emits set -- -u "$PATRONI_REST_USER:$PATRONI_REST_PW", so the password sits in curl's argv for the duration of the request. /proc/<pid>/cmdline is world-readable, so any process in the container's PID namespace (ps) can read the Patroni REST password while a switchover runs, and under the ONE PASSWORD design that is the superuser password. A pipe's stdin is private to its two ends, and printf is a builtin of the shells the data images ship (dash on the Debian-based postgres image, bash on Oracle Linux), so no process argv carries the secret at any point.

How verified

  • cargo fmt --all --check; cargo test controllers::patroni: 11 passed, on macOS with /bin/sh and again with sh resolving to dash (CI's ubuntu runner).
  • The sh-shim tests now capture curl's argv and the stdin config document. They assert the password appears in no argv element, -u is gone, the document is exactly user = "user:pass", the precedence chain and the bare case are unchanged. A new test feeds a password containing ", \, $, ', space, #, newline and tab and checks the escaped document decodes back to the original under curl's own unslashquote rules (replicated in the test).
  • Negative control: the new tests run against the previous -u prelude fail four of five credential tests (document absent, password in argv); only the bare-request test passes.
  • Outside the suite: the emitted shell text was run under dash, bash and zsh against a local HTTP server with real curl 8.7.1. The server received the exact Authorization: Basic header for the same password in all three shells; the unescaped variant did not.
  • clippy: no findings in src/controllers/patroni.rs; the repo baseline is untouched.

Rollout notes

CLI-only; no image, template or backboard change. Behaviour against enforcing and non-enforcing members is identical apart from where curl reads the credential. release/patch.

The switchover authenticates against Patroni's REST API inside the
member's container, and since #1171 the credential reached curl as
`-u "$PATRONI_REST_USER:$PATRONI_REST_PW"`. Argv is public inside the
container: /proc/<pid>/cmdline (ps) shows every process's arguments to
every other process in the PID namespace for as long as the request
runs, and under the ONE PASSWORD design that argument is the superuser
password.

Hand curl the credential as a one-line config document on stdin instead
(`user = "user:pass"` piped into `curl -K -`), escaped for curl's config
parser by a pure-POSIX shell function (`\` and `"` backslashed, newline
to `\n`). printf is a builtin of the shells the data images ship (dash,
bash), so no process argv carries the secret at any point.

Resolution precedence, the bare request for a member with no password,
and the error surfacing of Patroni's status and body are unchanged. The
sh-shim tests now capture curl's stdin as well as its argv and assert
the password is in the document and in no argument; a new test round-
trips a password with every character curl's parser treats specially.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release/patch Author patch release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant