Skip to content
This repository has been archived by the owner on May 12, 2021. It is now read-only.

Commit

Permalink
Merge pull request #3 from jpalermo/master
Browse files Browse the repository at this point in the history
This adds support for mixing lastpass credentials with credhub
  • Loading branch information
andrewedstrom authored Jun 20, 2018
2 parents 45cf3e8 + 77e206d commit 8ee17cb
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 18 deletions.
62 changes: 45 additions & 17 deletions lastpass/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -37,17 +43,25 @@ 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 fmt.Sprintf("((%s))", credHandle)
}

credential := l.getCredential(pathParts[0], pathParts[1])
err, credential := l.getCredential(pathParts[0], pathParts[1])
if err != nil {
return fmt.Sprintf("((%s))", credHandle)
}

fragment := ""
if len(pathParts) > 2 {
fragment = pathParts[2]
}

var encoded []byte

if fragment != "" {
// Assume YAML contents, return element
fragmentMap := map[string]interface{}{}
Expand All @@ -69,19 +83,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",
Expand All @@ -98,14 +114,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))
}
}
50 changes: 49 additions & 1 deletion lastpass/processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"code.cloudfoundry.org/commandrunner/fake_command_runner"
"github.com/pivotal-cf/reconfigure-pipeline/lastpass"
"errors"
)

var _ = Describe("Processor", func() {
Expand Down Expand Up @@ -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))
})
})

0 comments on commit 8ee17cb

Please sign in to comment.