feat(cli): read secret-bearing option values from a file or stdin (#137)

Every CLI option that can carry a secret gets a `--<name>-file <path|->`
sibling ('-' reads stdin), so the value stays out of the process list and
shell history: --password-file (global), --primary-config-file /
--backup-config-file (data-connection create|update), --auth-config-file
(external-system create|update), --connection-string-file (db-connection
create|update), --credentials-file (notification smtp create|update) and
--auth-token-file (notification sms create|update). Follows the existing
--config-file convention; `@path` is avoided because System.CommandLine
expands @-tokens as response files.

The value is read at parse time; a missing/unreadable/empty source, or '-'
on a terminal stdin, is a parse error naming option + path only, never the
content. Inline and file forms are mutually exclusive, at most one option
may read stdin, and one trailing line break is stripped. Inline forms keep
working.

docker/seed-sites.sh and docker-env2/seed-sites.sh now write the MxGateway
config (windev gateway key) to a mode-600 temp file removed by an EXIT trap
and pass --primary-config-file; seed-flow-outbound-demo.sh pipes its rig
connection string via --connection-string-file -. CLI README (new "Secrets
from a file or stdin" section + per-command tables), Component-CLI.md and
both docker READMEs updated.

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
This commit is contained in:
Joseph Doherty
2026-11-13 00:46:17 -05:00
parent 1e98997ce1
commit e2f433e849
19 changed files with 724 additions and 52 deletions
+3
View File
@@ -86,6 +86,9 @@ bash docker-env2/seed-data-path.sh
> The MxGateway data connections need the windev gateway API key, which is not in the repo. Put it in
> `~/.config/scadabridge/windev-mxgw-apikey` (mode 600), point `MXGW_APIKEY_FILE` at another file, or export
> `MXGW_APIKEY`. Without a key the script skips the MxGateway connection step and seeds everything else.
> The script never puts the key on a command line: it writes the gateway config to a mode-600 temp file
> (removed on exit by a trap) and passes it with `data-connection create --primary-config-file`
> (ScadaBridge#137; see *Secrets from a file or stdin* in `src/ZB.MOM.WW.ScadaBridge.CLI/README.md`).
Site-x's LocalDb is **replicated** (node-a dials node-b on 8083, same dev key on both),
like the rig's site-a. Without it a site failover brings up an active node with no
+12 -2
View File
@@ -55,7 +55,17 @@ if [ -z "${MXGW_APIKEY:-}" ] && [ -r "$MXGW_APIKEY_FILE" ]; then
MXGW_APIKEY=$(tr -d '\r\n' < "$MXGW_APIKEY_FILE")
fi
MXGW_APIKEY="${MXGW_APIKEY:-}"
MXGW_CONFIG="{\"endpoint\":\"${MXGW_ENDPOINT}\",\"apiKey\":\"${MXGW_APIKEY}\",\"clientName\":\"\",\"writeUserId\":0,\"useTls\":false,\"caFile\":\"\",\"serverName\":\"\",\"readTimeoutMs\":5000}"
# The config (which carries the key) goes to the CLI through a mode-600 temp file
# (--primary-config-file), never on the command line where `ps` and shell history
# would show it (ScadaBridge#137). printf is a bash builtin, so the key is not in
# any process's argv while the file is written either.
MXGW_CONFIG_FILE=""
if [ -n "$MXGW_APIKEY" ]; then
MXGW_CONFIG_FILE=$(umask 077 && mktemp "${TMPDIR:-/tmp}/sb-mxgw-config.XXXXXX")
trap 'rm -f "$MXGW_CONFIG_FILE"' EXIT
printf '{"endpoint":"%s","apiKey":"%s","clientName":"","writeUserId":0,"useTls":false,"caFile":"","serverName":"","readTimeoutMs":5000}' \
"$MXGW_ENDPOINT" "$MXGW_APIKEY" > "$MXGW_CONFIG_FILE"
fi
SITE_X_ID=$($CLI $URL $AUTH --format json site list \
| python3 -c "import sys,json; print(next((s['id'] for s in json.load(sys.stdin) if s.get('siteIdentifier')=='site-x'), ''))" 2>/dev/null)
@@ -68,7 +78,7 @@ elif [ -n "$SITE_X_ID" ]; then
--site-id "$SITE_X_ID" \
--name "$CONN_NAME" \
--protocol "MxGateway" \
--primary-config "$MXGW_CONFIG" \
--primary-config-file "$MXGW_CONFIG_FILE" \
|| echo " ('$CONN_NAME' may already exist on site-x)"
else
echo " (site-x not found — skipping MxGateway connection)"