vctx: exec auth/ writes uncoloured; stop forwarding SIGINT to vault
This commit is contained in:
+4
-2
@@ -57,7 +57,8 @@ When the effective format is `json` or `yaml` (from the table above,
|
||||
`--format`, a `-format` vault arg, or `VAULT_FORMAT`), `vctx` colourises vault's
|
||||
output like `jq`/`yq`. To do so it runs `vault` as a child instead of exec'ing
|
||||
it: stdin and stderr pass straight through, stdout is buffered and re-emitted
|
||||
coloured, SIGINT/SIGTERM are forwarded, and vault's exit code is returned
|
||||
coloured, SIGTERM/SIGHUP are forwarded (SIGINT already reaches vault from the
|
||||
terminal), and vault's exit code is returned
|
||||
(`128+n` if killed by signal `n`).
|
||||
|
||||
`--color` controls this:
|
||||
@@ -71,7 +72,8 @@ coloured, SIGINT/SIGTERM are forwarded, 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`) are always exec'd and never coloured. `JQ_COLORS`
|
||||
`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`
|
||||
overrides the JSON palette as in `jq`.
|
||||
|
||||
## Flags
|
||||
|
||||
+16
-12
@@ -17,6 +17,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
@@ -239,7 +240,8 @@ func autoFormat(args, baseEnv []string) string {
|
||||
// when it is one we colourise, else "". A -format arg beats VAULT_FORMAT, as in
|
||||
// vault itself.
|
||||
func colourFormat(args, env []string) string {
|
||||
f, cmd := "", ""
|
||||
f := ""
|
||||
var pos []string
|
||||
for _, e := range env {
|
||||
if v, ok := strings.CutPrefix(e, "VAULT_FORMAT="); ok {
|
||||
f = v
|
||||
@@ -248,15 +250,11 @@ func colourFormat(args, env []string) string {
|
||||
for i := 0; i < len(args); i++ {
|
||||
a := args[i]
|
||||
if a == "--" {
|
||||
if cmd == "" && i+1 < len(args) {
|
||||
cmd = args[i+1]
|
||||
}
|
||||
pos = append(pos, args[i+1:]...)
|
||||
break
|
||||
}
|
||||
if !strings.HasPrefix(a, "-") {
|
||||
if cmd == "" {
|
||||
cmd = a
|
||||
}
|
||||
pos = append(pos, a)
|
||||
continue
|
||||
}
|
||||
name, val, hasVal := strings.Cut(strings.TrimLeft(a, "-"), "=")
|
||||
@@ -270,14 +268,17 @@ func colourFormat(args, env []string) string {
|
||||
f = val
|
||||
}
|
||||
f = strings.ToLower(f)
|
||||
if uncaptured[cmd] || (f != "json" && f != "yaml") {
|
||||
if len(pos) == 0 || uncaptured[pos[0]] || (pos[0] == "write" && slices.ContainsFunc(pos[1:], isAuthPath)) || (f != "json" && f != "yaml") {
|
||||
return ""
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// runChild runs vault with stdin/stderr passed through, forwards SIGINT and
|
||||
// SIGTERM, then writes its captured stdout colourised to w. It returns vault's
|
||||
// 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.
|
||||
func runChild(bin string, argv, env []string, format string, w io.Writer) (int, error) {
|
||||
var out bytes.Buffer
|
||||
@@ -285,7 +286,8 @@ func runChild(bin string, argv, env []string, format string, w io.Writer) (int,
|
||||
cmd.Env, cmd.Stdin, cmd.Stdout, cmd.Stderr = env, os.Stdin, &out, os.Stderr
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
// SIGINT is caught but not forwarded: vault is in our process group and already gets ^C.
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
defer signal.Stop(sigs)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return 0, fmt.Errorf("run vault: %w", err)
|
||||
@@ -296,7 +298,9 @@ func runChild(bin string, argv, env []string, format string, w io.Writer) (int,
|
||||
for {
|
||||
select {
|
||||
case s := <-sigs:
|
||||
_ = cmd.Process.Signal(s)
|
||||
if s != syscall.SIGINT {
|
||||
_ = cmd.Process.Signal(s)
|
||||
}
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
|
||||
+12
-2
@@ -284,6 +284,11 @@ func TestColourFormat(t *testing.T) {
|
||||
{[]string{"operator", "unseal"}, []string{"VAULT_FORMAT=json"}, ""},
|
||||
{[]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"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := colourFormat(c.args, c.env); got != c.want {
|
||||
@@ -331,7 +336,8 @@ func TestRunChild(t *testing.T) {
|
||||
|
||||
func TestRunChildForwardsSignal(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
bin := fakeVault(t, `trap 'echo TERM > "$D/got"; trap - TERM; kill -TERM $$' TERM
|
||||
bin := fakeVault(t, `trap 'echo INT >> "$D/got"' INT
|
||||
trap 'echo TERM >> "$D/got"; trap - TERM; kill -TERM $$' TERM
|
||||
touch "$D/ready"
|
||||
i=0; while [ $i -lt 100 ]; do sleep 0.05; i=$((i+1)); done`)
|
||||
type res struct {
|
||||
@@ -349,6 +355,10 @@ i=0; while [ $i -lt 100 ]; do sleep 0.05; i=$((i+1)); done`)
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if err := syscall.Kill(os.Getpid(), syscall.SIGINT); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
if err := syscall.Kill(os.Getpid(), syscall.SIGTERM); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -357,7 +367,7 @@ i=0; while [ $i -lt 100 ]; do sleep 0.05; i=$((i+1)); done`)
|
||||
t.Fatal(r.err)
|
||||
}
|
||||
if got, _ := os.ReadFile(filepath.Join(dir, "got")); string(got) != "TERM\n" {
|
||||
t.Errorf("child did not receive SIGTERM (got %q)", got)
|
||||
t.Errorf("child signals = %q, want only SIGTERM", got)
|
||||
}
|
||||
if r.code != 128+15 {
|
||||
t.Errorf("code = %d, want %d", r.code, 128+15)
|
||||
|
||||
Reference in New Issue
Block a user