diff --git a/src/edit.go b/src/edit.go index 91e99c0..8fc8aa8 100644 --- a/src/edit.go +++ b/src/edit.go @@ -1,15 +1,15 @@ package main import ( + "bufio" "fmt" "io/ioutil" "os" "os/exec" - "regexp" + "sort" "strings" "github.com/mitchellh/cli" - "gopkg.in/yaml.v2" ) type EditCommand struct { @@ -28,7 +28,6 @@ func (c *EditCommand) Run(args []string) int { } path := args[0] - secret, err := vc.Logical().Read(path) if err != nil { return 1 @@ -45,7 +44,7 @@ func (c *EditCommand) Run(args []string) int { if answer := strings.ToLower(answer); answer == "n" { return 0 } - data["key"] = "value" + } else { data = secret.Data } @@ -85,46 +84,59 @@ func (c *EditCommand) Synopsis() string { return "Edit a secret at specified path" } -// Processes a secret by unmarshaling and writting it into a tempfile. -// After the file was edit it will reread the tempfile marhsal the data and clean up. +// Processes a secret by writting all k/v pairs into a tempfile. After the file was edited through a +// text editor it will be parsed. func ProcessSecret(data map[string]interface{}) (map[string]interface{}, error) { - f, err := ioutil.TempFile("", "vaultsecret") + file, err := ioutil.TempFile("", "vaultsecret") if err != nil { return nil, err } - defer os.Remove(f.Name()) + defer os.Remove(file.Name()) - ymldata, err := yaml.Marshal(&data) - _, err = f.Write(ymldata) - if err != nil { - return nil, err + // Sort secrets lexicographically + var keys []string + for k := range data { + keys = append(keys, k) + } + sort.Strings(keys) + + // Write secrets to tempfile in sorted order + for _, k := range keys { + file.WriteString(k + ": " + data[k].(string) + "\n") } + file.Close() - editedData, err := EditFile(f.Name()) + err = EditFile(file.Name()) if err != nil { return nil, err } + // Parse secret parsedData := make(map[string]interface{}) - - err = yaml.Unmarshal(editedData, parsedData) + editedFile, err := os.Open(file.Name()) if err != nil { - return nil, fmt.Errorf("Unable to parse yaml tempfile: %q", err) + return nil, err } - err = ValidateData(parsedData) - if err != nil { - return nil, err + scanner := bufio.NewScanner(editedFile) + + for scanner.Scan() { + line := scanner.Text() + kv_pair := strings.Split(line, ": ") + if len(kv_pair) == 2 { + parsedData[kv_pair[0]] = kv_pair[1] + } else { + return nil, fmt.Errorf("Unable to parse key/value pair: %q", line) + } } return parsedData, nil - } // Edit a file with the editor specified in $EDITOR or vi as fallback -func EditFile(path string) ([]byte, error) { +func EditFile(path string) error { var cmdstring []string @@ -144,32 +156,8 @@ func EditFile(path string) ([]byte, error) { err := cmd.Run() if err != nil { - return nil, err - } - - content, err := ioutil.ReadFile(path) - if err != nil { - return nil, err + return err } - return content, nil -} - -// Check if data keys of a secrets contain only valid characters -func ValidateData(data map[string]interface{}) error { - - allowedCharacters := "^[A-Za-z0-9-_]*$" - - for k := range data { - matched, err := regexp.MatchString(allowedCharacters, k) - if err != nil { - return fmt.Errorf("Unable to validate secret keys: %q", err) - } - - if !matched { - return fmt.Errorf("Invalid characters in key %q", k) - } - - } return nil } diff --git a/src/show.go b/src/show.go index 9beba4b..ed49452 100644 --- a/src/show.go +++ b/src/show.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "sort" "github.com/mitchellh/cli" ) @@ -36,18 +37,27 @@ func (c *ShowCommand) Run(args []string) int { // Get length of the largest key in order to calculate the // "whitespace padded" representation of `show` - max_key_len := 0 + MaxKeyLen := 0 for k, _ := range secret.Data { - if key_len := len(k); key_len > max_key_len { - max_key_len = key_len + if KeyLen := len(k); KeyLen > MaxKeyLen { + MaxKeyLen = KeyLen } } // Add an additional X whitespaces between "key:" and "value" - max_key_len += 4 + MaxKeyLen += 4 - for k, v := range secret.Data { - c.Ui.Output(fmt.Sprintf("%-"+fmt.Sprint(max_key_len)+"v %v", fmt.Sprint(k, ":"), v)) + // Sort secrets lexicographically + var keys []string + for k := range secret.Data { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + c.Ui.Output(fmt.Sprintf("%-"+fmt.Sprint(MaxKeyLen)+"v %v", + k+":", // Secret identifier + secret.Data[k])) // Secret value } return 0 diff --git a/src/show_test.go b/src/show_test.go index b4a73a9..2a5a2e3 100644 --- a/src/show_test.go +++ b/src/show_test.go @@ -71,6 +71,38 @@ func TestShow(t *testing.T) { } }) + t.Run("ShowSortedSecrets", func(t *testing.T) { + + // Create test secret + data := make(map[string]interface{}) + data["a_key"] = "value" + data["c_key"] = "value" + data["b_key"] = "value" + + _, err = vc.Logical().Write("secret/secret1", data) + if err != nil { + t.Fatalf("Unable to write test secret: %q", err) + } + + args := []string{"secret/secret1"} + + if rc := c.Run(args); rc != 0 { + t.Fatalf("Wrong exit code. errors: \n%s", ui.ErrorWriter.String()) + } + + expectedErr := "" + if actual := ui.ErrorWriter.String(); !strings.Contains(actual, expectedErr) { + t.Fatalf("expected error:\n%s\n\nto include: %q", actual, expectedErr) + } + + expectedOutput := `a_key: value +b_key: value +c_key: value` + if actual := ui.OutputWriter.String(); !strings.Contains(actual, expectedOutput) { + t.Fatalf("expected output:\n%s\n\nto include: %q", actual, expectedOutput) + } + }) + _, err = vc.Logical().Delete("secret/secret1") if err != nil { t.Fatalf("Unable to write test secret: %q", err)