diff --git a/command/token/helper.go b/command/token/helper.go new file mode 100644 index 000000000..f1c529004 --- /dev/null +++ b/command/token/helper.go @@ -0,0 +1,53 @@ +package token + +import ( + "bytes" + "os/exec" +) + +// Helper is the struct that has all the logic for storing and retrieving +// tokens from the token helper. The API for the helpers is simple: the +// Path is executed within a shell. The last argument appended will be the +// operation, which is: +// +// * "get" - Read the value of the token and write it to stdout. +// * "store" - Store the value of the token which is on stdin. Output +// nothing. +// * "erase" - Erase the contents stored. Output nothing. +// +// Any errors can be written on stdout. If the helper exits with a non-zero +// exit code then the stderr will be made part of the error value. +type Helper struct { + Path string +} + +// Erase deletes the contents from the helper. +func (h *Helper) Erase() error { + cmd := h.cmd("erase") + return cmd.Run() +} + +// Get gets the token value from the helper. +func (h *Helper) Get() (string, error) { + var buf bytes.Buffer + cmd := h.cmd("get") + cmd.Stdout = &buf + if err := cmd.Run(); err != nil { + return "", err + } + + return buf.String(), nil +} + +// Store stores the token value into the helper. +func (h *Helper) Store(v string) error { + buf := bytes.NewBufferString(v) + cmd := h.cmd("store") + cmd.Stdin = buf + return cmd.Run() +} + +func (h *Helper) cmd(op string) *exec.Cmd { + cmd := exec.Command("sh", "-c", h.Path+" "+op) + return cmd +} diff --git a/command/token/helper_test.go b/command/token/helper_test.go new file mode 100644 index 000000000..9ae4b4905 --- /dev/null +++ b/command/token/helper_test.go @@ -0,0 +1,116 @@ +package token + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "strings" + "testing" +) + +func TestHelper(t *testing.T) { + h := testHelper(t) + if err := h.Store("foo"); err != nil { + t.Fatalf("err: %s", err) + } + + v, err := h.Get() + if err != nil { + t.Fatalf("err: %s", err) + } + + if v != "foo" { + t.Fatalf("bad: %#v", v) + } + + if err := h.Erase(); err != nil { + t.Fatalf("err: %s", err) + } + + v, err = h.Get() + if err != nil { + t.Fatalf("err: %s", err) + } + + if v != "" { + t.Fatalf("bad: %#v", v) + } +} + +func testHelper(t *testing.T) *Helper { + return &Helper{Path: helperPath("helper")} +} + +func helperPath(s ...string) string { + tf, err := ioutil.TempFile("", "vault") + if err != nil { + panic(err) + } + tf.Close() + + cs := []string{"-test.run=TestHelperProcess", "--"} + cs = append(cs, s...) + return fmt.Sprintf( + "GO_HELPER_PATH=%s GO_WANT_HELPER_PROCESS=1 %s %s", + tf.Name(), + os.Args[0], + strings.Join(cs, " ")) +} + +// This is not a real test. This is just a helper process kicked off by tests. +func TestHelperProcess(*testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { + return + } + + defer os.Exit(0) + + args := os.Args + for len(args) > 0 { + if args[0] == "--" { + args = args[1:] + break + } + + args = args[1:] + } + + if len(args) == 0 { + fmt.Fprintf(os.Stderr, "No command\n") + os.Exit(2) + } + + cmd, args := args[0], args[1:] + switch cmd { + case "helper": + path := os.Getenv("GO_HELPER_PATH") + + switch args[0] { + case "erase": + os.Remove(path) + case "get": + f, err := os.Open(path) + if os.IsNotExist(err) { + return + } + if err != nil { + fmt.Fprintf(os.Stderr, "Err: %s\n", err) + os.Exit(1) + } + defer f.Close() + io.Copy(os.Stdout, f) + case "store": + f, err := os.Create(path) + if err != nil { + fmt.Fprintf(os.Stderr, "Err: %s\n", err) + os.Exit(1) + } + defer f.Close() + io.Copy(f, os.Stdin) + } + default: + fmt.Fprintf(os.Stderr, "Unknown command: %q\n", cmd) + os.Exit(2) + } +}