From 123432fdb6095f29d06520f79fae4a74d5f98dee Mon Sep 17 00:00:00 2001 From: Joseph Palermo Date: Mon, 21 May 2018 21:45:43 -0700 Subject: [PATCH 1/2] Ignore credentials when not found in lastpass; This allows mixed mode with credhub or other sources --- lastpass/processor.go | 63 ++++++++++++++++++++++++++++---------- lastpass/processor_test.go | 50 +++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 18 deletions(-) diff --git a/lastpass/processor.go b/lastpass/processor.go index c0ac695..7a39897 100644 --- a/lastpass/processor.go +++ b/lastpass/processor.go @@ -4,28 +4,34 @@ import ( "bytes" "encoding/json" "log" - "os" "os/exec" "regexp" "strings" "code.cloudfoundry.org/commandrunner" "gopkg.in/yaml.v2" + "fmt" ) type Processor struct { commandRunner commandrunner.CommandRunner - credentialCache map[string]string + credentialCache map[string]cacheResult +} + +type cacheResult struct { + Err error + Result string } func NewProcessor(commandRunner commandrunner.CommandRunner) *Processor { return &Processor{ commandRunner: commandRunner, - credentialCache: map[string]string{}, + credentialCache: map[string]cacheResult{}, } } func (l *Processor) Process(config string) string { + l.verifyLoggedIn() re := regexp.MustCompile(`\(\((.*)\)\)`) processedConfig := re.ReplaceAllStringFunc(config, func(match string) string { @@ -37,17 +43,26 @@ func (l *Processor) Process(config string) string { } func (l *Processor) handle(credHandle string) string { + var encoded []byte + var err error + pathParts := strings.Split(credHandle, "/") + if len(pathParts) == 1 { + encoded, _ = json.Marshal(fmt.Sprintf("((%s))", credHandle)) + return string(encoded) + } - credential := l.getCredential(pathParts[0], pathParts[1]) + err, credential := l.getCredential(pathParts[0], pathParts[1]) + if err != nil { + encoded, _ = json.Marshal(fmt.Sprintf("((%s))", credHandle)) + return string(encoded) + } fragment := "" if len(pathParts) > 2 { fragment = pathParts[2] } - var encoded []byte - if fragment != "" { // Assume YAML contents, return element fragmentMap := map[string]interface{}{} @@ -69,19 +84,21 @@ func (l *Processor) handle(credHandle string) string { return string(encoded) } -func (l *Processor) getCredential(credential, field string) string { +func (l *Processor) getCredential(credential, field string) (error, string) { + var err error cacheKey := strings.Join([]string{credential, field}, "/") - credentialValue := l.credentialCache[cacheKey] + credentialValue := l.credentialCache[cacheKey].Result + err = l.credentialCache[cacheKey].Err - if credentialValue == "" { - credentialValue = l.getCredentialFromLastPass(credential, field) - l.credentialCache[cacheKey] = credentialValue + if credentialValue == "" && err == nil { + err, credentialValue = l.getCredentialFromLastPass(credential, field) + l.credentialCache[cacheKey] = cacheResult{err, credentialValue} } - return credentialValue + return err, credentialValue } -func (l *Processor) getCredentialFromLastPass(credential, field string) string { +func (l *Processor) getCredentialFromLastPass(credential, field string) (error, string) { fieldFlagMap := map[string]string{ "Password": "--password", "Username": "--username", @@ -98,14 +115,26 @@ func (l *Processor) getCredentialFromLastPass(credential, field string) string { cmd := exec.Command("lpass", "show", fieldFlag, credential) - cmd.Stderr = os.Stderr - cmd.Stdin = os.Stdin cmd.Stdout = output err := l.commandRunner.Run(cmd) if err != nil { - log.Fatal(err) + return err, "" } - return strings.TrimSpace(output.String()) + return nil, strings.TrimSpace(output.String()) +} + +func (l *Processor) verifyLoggedIn() { + cmd := exec.Command("lpass", "status") + + output := &bytes.Buffer{} + + cmd.Stdout = output + cmd.Stderr = output + + err := l.commandRunner.Run(cmd) + if err != nil { + log.Fatal(fmt.Sprintf("lpass error: %s", output)) + } } diff --git a/lastpass/processor_test.go b/lastpass/processor_test.go index d2bf78e..b194de7 100644 --- a/lastpass/processor_test.go +++ b/lastpass/processor_test.go @@ -9,6 +9,7 @@ import ( "code.cloudfoundry.org/commandrunner/fake_command_runner" "github.com/pivotal-cf/reconfigure-pipeline/lastpass" + "errors" ) var _ = Describe("Processor", func() { @@ -214,9 +215,56 @@ key-2: ((my-credential/Notes/inner-key-2))` output := processor.Process(input) - Expect(commandRunner.ExecutedCommands()).To(HaveLen(1)) + Expect(commandRunner.ExecutedCommands()).To(HaveLen(2)) Expect(output).To(Equal(`key-1: "inner-value-1" key-2: "inner-value-2"`)) }) + + It("leaves top level fields alone", func() { + input := "key: ((top_level_field))" + output := processor.Process(input) + + Expect(output).To(Equal(`key: "((top_level_field))"`)) + }) + + It("leaves unknown fields alone", func() { + commandRunner.WhenRunning(CommandSpec{ + Path: "lpass", + Args: []string{ + "show", + "--field=secret", + "unknown", + }, + }, func(cmd *exec.Cmd) error { + return errors.New("Exit Status 1") + }) + + input := "key: ((unknown/secret))" + output := processor.Process(input) + + Expect(output).To(Equal(`key: "((unknown/secret))"`)) + }) + + It("caches lpass error values", func() { + commandRunner.WhenRunning(CommandSpec{ + Path: "lpass", + Args: []string{ + "show", + "--field=secret", + "unknown", + }, + }, func(cmd *exec.Cmd) error { + return errors.New("Exit Status 1") + }) + + input := `key-1: ((unknown/secret)) +key-2: ((unknown/secret))` + output := processor.Process(input) + + Expect(output).To(Equal(`key-1: "((unknown/secret))" +key-2: "((unknown/secret))"`)) + + Expect(commandRunner.ExecutedCommands()).To(HaveLen(2)) + }) }) From 77e206de775a8f57eb4c9df993480c000b2ea2a5 Mon Sep 17 00:00:00 2001 From: Alvaro Perez-Shirley Date: Tue, 22 May 2018 11:32:58 -0700 Subject: [PATCH 2/2] Avoid double quoting unprocessed fields Signed-off-by: Joseph Palermo --- lastpass/processor.go | 5 ++--- lastpass/processor_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lastpass/processor.go b/lastpass/processor.go index 7a39897..3c63573 100644 --- a/lastpass/processor.go +++ b/lastpass/processor.go @@ -49,13 +49,12 @@ func (l *Processor) handle(credHandle string) string { pathParts := strings.Split(credHandle, "/") if len(pathParts) == 1 { encoded, _ = json.Marshal(fmt.Sprintf("((%s))", credHandle)) - return string(encoded) + return fmt.Sprintf("((%s))", credHandle) } err, credential := l.getCredential(pathParts[0], pathParts[1]) if err != nil { - encoded, _ = json.Marshal(fmt.Sprintf("((%s))", credHandle)) - return string(encoded) + return fmt.Sprintf("((%s))", credHandle) } fragment := "" diff --git a/lastpass/processor_test.go b/lastpass/processor_test.go index b194de7..d5afad5 100644 --- a/lastpass/processor_test.go +++ b/lastpass/processor_test.go @@ -225,7 +225,7 @@ key-2: "inner-value-2"`)) input := "key: ((top_level_field))" output := processor.Process(input) - Expect(output).To(Equal(`key: "((top_level_field))"`)) + Expect(output).To(Equal(`key: ((top_level_field))`)) }) It("leaves unknown fields alone", func() { @@ -243,7 +243,7 @@ key-2: "inner-value-2"`)) input := "key: ((unknown/secret))" output := processor.Process(input) - Expect(output).To(Equal(`key: "((unknown/secret))"`)) + Expect(output).To(Equal(`key: ((unknown/secret))`)) }) It("caches lpass error values", func() { @@ -262,8 +262,8 @@ key-2: "inner-value-2"`)) key-2: ((unknown/secret))` output := processor.Process(input) - Expect(output).To(Equal(`key-1: "((unknown/secret))" -key-2: "((unknown/secret))"`)) + Expect(output).To(Equal(`key-1: ((unknown/secret)) +key-2: ((unknown/secret))`)) Expect(commandRunner.ExecutedCommands()).To(HaveLen(2)) })