vctx: always exec write and patch (MFA prompt on stdout)
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful

This commit is contained in:
2026-10-24 16:16:20 +11:00
parent cb1e048e0f
commit 7540cbc9a4
3 changed files with 10 additions and 14 deletions
+2 -2
View File
@@ -72,8 +72,8 @@ terminal), and vault's exit code is returned
Without colour (piped output, `NO_COLOR`, `table`/`raw`/`pretty` formats)
`vctx` execs `vault` exactly as before, so scripts see unchanged output.
Streaming or interactive commands (`monitor`, `debug`, `events`, `ssh`,
`agent`, `proxy`, `server`, `login`, `operator`, and `write` to an `auth/`
path, which may prompt for MFA) are always exec'd and never coloured. `JQ_COLORS`
`agent`, `proxy`, `server`, `login`, `operator`, and `write`/`patch`, which
may prompt for MFA) are always exec'd and never coloured. `JQ_COLORS`
overrides the JSON palette as in `jq`.
## Flags
+4 -8
View File
@@ -17,7 +17,6 @@ import (
"os"
"os/exec"
"os/signal"
"slices"
"strings"
"syscall"
@@ -37,9 +36,9 @@ var execVault = syscall.Exec
var osExit = os.Exit
// uncaptured commands are always exec'd: buffering their stdout would hide
// streamed output or prompts (unseal keys, MFA passcodes) until exit, or break
// the TTY, for ssh.
var uncaptured = map[string]bool{"monitor": true, "debug": true, "events": true, "ssh": true, "agent": true, "proxy": true, "server": true, "login": true, "operator": true}
// streamed output or prompts (unseal keys, MFA passcodes; write and patch can
// prompt for MFA) until exit, or break the TTY, for ssh.
var uncaptured = map[string]bool{"monitor": true, "debug": true, "events": true, "ssh": true, "agent": true, "proxy": true, "server": true, "login": true, "operator": true, "write": true, "patch": true}
func main() {
if err := newRootCmd().Execute(); err != nil {
@@ -268,15 +267,12 @@ func colourFormat(args, env []string) string {
f = val
}
f = strings.ToLower(f)
if len(pos) == 0 || uncaptured[pos[0]] || (pos[0] == "write" && slices.ContainsFunc(pos[1:], isAuthPath)) || (f != "json" && f != "yaml") {
if len(pos) == 0 || uncaptured[pos[0]] || (f != "json" && f != "yaml") {
return ""
}
return f
}
// isAuthPath matches auth/ paths; a write there may be a login prompting for MFA on stdout.
func isAuthPath(p string) bool { return strings.HasPrefix(strings.TrimPrefix(p, "/"), "auth/") }
// runChild runs vault with stdin/stderr passed through, forwards SIGTERM and
// SIGHUP, then writes its captured stdout colourised to w. It returns vault's
// exit code, or 128+n if it died from signal n.
+4 -4
View File
@@ -285,10 +285,10 @@ func TestColourFormat(t *testing.T) {
{[]string{"-format=yaml", "operator", "generate-root"}, nil, ""},
{[]string{"operator", "rekey", "-format=json"}, nil, ""},
{[]string{"write", "auth/userpass/login/bob"}, []string{"VAULT_FORMAT=json"}, ""},
{[]string{"write", "-format=json", "auth/x/login/y"}, nil, ""},
{[]string{"write", "-format", "yaml", "auth/x/login/y", "password=p"}, nil, ""},
{[]string{"write", "secret/foo", "a=b"}, []string{"VAULT_FORMAT=json"}, "json"},
{[]string{"write", "-format=json", "secret/foo", "a=b"}, nil, "json"},
{[]string{"write", "-format=json", "ns1/auth/x/login/y"}, nil, ""},
{[]string{"write", "-format=json", "secret/foo", "a=b"}, nil, ""},
{[]string{"patch", "auth/x/config"}, []string{"VAULT_FORMAT=json"}, ""},
{[]string{"patch", "-format=yaml", "secret/foo", "a=b"}, nil, ""},
}
for _, c := range cases {
if got := colourFormat(c.args, c.env); got != c.want {