From cddc3ae1c7278ac004dfe64c1b1c661f3110fb9e Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Thu, 7 May 2020 11:19:02 +0200 Subject: [PATCH 01/11] Add MVP of tfstate backend to secrethub run --- go.mod | 3 + go.sum | 6 + internals/integrations/tfstate/backend.go | 162 ++++++++++++++++++ internals/integrations/tfstate/pid.go | 48 ++++++ internals/integrations/tfstate/socks.go | 18 ++ .../integrations/tfstate/socks_darwin.go | 29 ++++ internals/integrations/tfstate/socks_other.go | 33 ++++ internals/integrations/tfstate/state.go | 96 +++++++++++ internals/secrethub/run.go | 21 ++- 9 files changed, 415 insertions(+), 1 deletion(-) create mode 100644 internals/integrations/tfstate/backend.go create mode 100644 internals/integrations/tfstate/pid.go create mode 100644 internals/integrations/tfstate/socks.go create mode 100644 internals/integrations/tfstate/socks_darwin.go create mode 100644 internals/integrations/tfstate/socks_other.go create mode 100644 internals/integrations/tfstate/state.go diff --git a/go.mod b/go.mod index fb42264c..a515c538 100644 --- a/go.mod +++ b/go.mod @@ -7,16 +7,19 @@ require ( github.com/alecthomas/kingpin v1.3.8-0.20200323085623-b6657d9477a6 github.com/atotto/clipboard v0.1.2 github.com/aws/aws-sdk-go v1.25.49 + github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 github.com/docker/go-units v0.3.3 github.com/fatih/color v1.7.0 github.com/masterzen/winrm v0.0.0-20190308153735-1d17eaf15943 github.com/mattn/go-colorable v0.1.1 github.com/mattn/go-isatty v0.0.7 github.com/mitchellh/go-homedir v1.1.0 + github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/mapstructure v1.1.2 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/secrethub/demo-app v0.1.0 github.com/secrethub/secrethub-go v0.28.0 + github.com/weaveworks/procspy v0.0.0-20150706124340-cb970aa190c3 github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07 golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 diff --git a/go.sum b/go.sum index 977a9e18..e58b0ccf 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/aws/aws-sdk-go v1.19.38 h1:WKjobgPO4Ua1ww2NJJl2/zQNreUZxvqmEzwMlRjjm9 github.com/aws/aws-sdk-go v1.19.38/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.49 h1:j5R2Ey+g8qaiy2NJ9iH+KWzDWS4SjXRCjhc22EeQVE4= github.com/aws/aws-sdk-go v1.25.49/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 h1:BjkPE3785EwPhhyuFkbINB+2a1xATwk8SNDWnJiD41g= +github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5/go.mod h1:jtAfVaU/2cu1+wdSRPWE2c1N2qeAA3K4RH9pYgqwets= github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU= github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= @@ -62,6 +64,8 @@ github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3Zk github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= @@ -84,6 +88,8 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/weaveworks/procspy v0.0.0-20150706124340-cb970aa190c3 h1:UC4iN/yCDCObTBhKzo34/R2U6qptTPmqbzG6UiQVMUQ= +github.com/weaveworks/procspy v0.0.0-20150706124340-cb970aa190c3/go.mod h1:cJTfuBcxkdbj8Mabk4PPdaf0AXv9TYEJmkFxKcWxYY4= github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07 h1:U5I57s4ISLpeeLYl8b3MsainSSh9F+mRXauln37b50I= github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07/go.mod h1:XlXBIfkGawHNVOHlenOaBW7zlfCh8LovwjOgjamYnkQ= golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= diff --git a/internals/integrations/tfstate/backend.go b/internals/integrations/tfstate/backend.go new file mode 100644 index 00000000..39df0470 --- /dev/null +++ b/internals/integrations/tfstate/backend.go @@ -0,0 +1,162 @@ +package tfstate + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + + "github.com/secrethub/secrethub-go/internals/api" + "github.com/secrethub/secrethub-go/pkg/randchar" + "github.com/secrethub/secrethub-go/pkg/secrethub" + "github.com/secrethub/secrethub-go/pkg/secretpath" +) + +type backend struct { + client secrethub.ClientInterface + port uint16 + logger io.Writer +} + +func New(client secrethub.ClientInterface, port uint16, logger io.Writer) *backend { + return &backend{ + client: client, + port: port, + logger: prefixWriter{ + Writer: logger, + prefix: "[SecretHub]: ", + }, + } +} + +func (b *backend) Serve() error { + server := &http.Server{ + Addr: fmt.Sprintf("127.0.0.1:%d", b.port), + Handler: http.HandlerFunc(b.Handle), + } + err := server.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + return err + } + + return nil +} + +func (b *backend) Handle(w http.ResponseWriter, r *http.Request) { + isChild, err := connectionFromChildProcess(os.Getpid(), r) + if err != nil { + w.WriteHeader(http.StatusForbidden) + return + } + if !isChild { + fmt.Println("can only be reached from a process spawned with secrethub run") + w.WriteHeader(http.StatusForbidden) + return + } + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + fmt.Printf("Errors: %v\n", err) + return + } + + path, password, ok := r.BasicAuth() + if !ok { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintln(b.logger, "set the SecretHub path to the state as the username") + return + } + + if secretpath.Count(path) < 2 { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(b.logger, "set user to a valid repository or directory. Got: %s\n", path) + return + } + + statePath := secretpath.Join(path, "state") + lockPath := secretpath.Join(path, "lock") + passwordPath := secretpath.Join(path, "password") + + secret, err := b.client.Secrets().ReadString(passwordPath) + if err != nil && !api.IsErrNotFound(err) { + w.WriteHeader(http.StatusInternalServerError) + return + } else if err == nil { + if password == "" { + w.WriteHeader(http.StatusUnauthorized) + fmt.Fprintf(w, "password stored at %s should be set as auth password", passwordPath) + return + } + if password != secret { + w.WriteHeader(http.StatusForbidden) + fmt.Fprintf(w, "provided password does not password stored at %s", passwordPath) + return + } + } + + switch r.Method { + case http.MethodGet: + secret, err := b.client.Secrets().Read(statePath) + if api.IsErrNotFound(err) { + w.WriteHeader(http.StatusNotFound) + return + } else if err != nil { + fmt.Fprintf(b.logger, "%v\n", err) + + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Write(secret.Data) + case http.MethodPost: + _, err = b.client.Secrets().Write(statePath, body) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, err.Error()) + return + } + case "LOCK": + _, err := b.client.Secrets().Get(lockPath) + if !api.IsErrNotFound(err) { + w.WriteHeader(http.StatusLocked) + return + } + + data, err := randchar.Generate(32) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, err.Error()) + return + } + + res, err := b.client.Secrets().Write(lockPath, data) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, err.Error()) + return + } + if res.Version != 1 { + w.WriteHeader(http.StatusLocked) + } + + case "UNLOCK": + err := b.client.Secrets().Delete(lockPath) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + default: + w.WriteHeader(http.StatusNotImplemented) + } +} + +type prefixWriter struct { + io.Writer + prefix string +} + +func (l prefixWriter) Write(p []byte) (int, error) { + _, err := fmt.Fprintf(l.Writer, "%s%s", l.prefix, p) + return len(p), err +} diff --git a/internals/integrations/tfstate/pid.go b/internals/integrations/tfstate/pid.go new file mode 100644 index 00000000..760c6bd0 --- /dev/null +++ b/internals/integrations/tfstate/pid.go @@ -0,0 +1,48 @@ +package tfstate + +import ( + "net" + "net/http" + "strconv" + "strings" + + "github.com/mitchellh/go-ps" +) + +func connectionFromChildProcess(pid int, r *http.Request) (bool, error) { + split := strings.Split(r.RemoteAddr, ":") + host := net.ParseIP(split[0]) + port64, err := strconv.ParseUint(split[1], 10, 16) + if err != nil { + return false, err + } + port := uint16(port64) + + socks, err := tcpSocks() + if err != nil { + return false, err + } + for _, c := range socks { + if c.LocalAddress.Equal(host) && c.LocalPort == port { + nextProcess := c.Process.PID + for { + if nextProcess == pid { + return true, nil + } + if nextProcess == 1 { + break + } + parent, err := ps.FindProcess(nextProcess) + if err != nil { + return false, err + } + if parent == nil { + break + } + + nextProcess = parent.PPid() + } + } + } + return false, nil +} diff --git a/internals/integrations/tfstate/socks.go b/internals/integrations/tfstate/socks.go new file mode 100644 index 00000000..f6656cce --- /dev/null +++ b/internals/integrations/tfstate/socks.go @@ -0,0 +1,18 @@ +package tfstate + +import ( + "net" +) + +type Connection struct { + LocalAddress net.IP + LocalPort uint16 + RemoteAddress net.IP + RemotePort uint16 + Process +} + +type Process struct { + PID int + Name string +} diff --git a/internals/integrations/tfstate/socks_darwin.go b/internals/integrations/tfstate/socks_darwin.go new file mode 100644 index 00000000..c1515c22 --- /dev/null +++ b/internals/integrations/tfstate/socks_darwin.go @@ -0,0 +1,29 @@ +// +build darwin + +package tfstate + +import ( + "github.com/weaveworks/procspy" +) + +func tcpSocks() ([]Connection, error) { + cs, err := procspy.Connections(true) + if err != nil { + return nil, err + } + var res []Connection + + for sock := cs.Next(); sock != nil; sock = cs.Next() { + res = append(res, Connection{ + LocalAddress: sock.LocalAddress, + LocalPort: sock.LocalPort, + RemoteAddress: sock.RemoteAddress, + RemotePort: sock.RemotePort, + Process: Process{ + PID: int(sock.Proc.PID), + Name: sock.Proc.Name, + }, + }) + } + return res +} diff --git a/internals/integrations/tfstate/socks_other.go b/internals/integrations/tfstate/socks_other.go new file mode 100644 index 00000000..4ab337a3 --- /dev/null +++ b/internals/integrations/tfstate/socks_other.go @@ -0,0 +1,33 @@ +// +build !darwin + +package tfstate + +import ( + "github.com/cakturk/go-netstat/netstat" +) + +func tcpSocks() ([]Connection, error) { + socks, err := netstat.TCPSocks(netstat.NoopFilter) + if err != nil { + return nil, err + } + + res := make([]Connection, len(socks)) + + for i, sock := range socks { + if sock.Process == nil { + continue + } + res[i] = Connection{ + LocalAddress: sock.LocalAddr.IP, + LocalPort: sock.LocalAddr.Port, + RemoteAddress: sock.RemoteAddr.IP, + RemotePort: sock.RemoteAddr.Port, + Process: Process{ + PID: sock.Process.Pid, + Name: sock.Process.Name, + }, + } + } + return res, nil +} diff --git a/internals/integrations/tfstate/state.go b/internals/integrations/tfstate/state.go new file mode 100644 index 00000000..b4de0618 --- /dev/null +++ b/internals/integrations/tfstate/state.go @@ -0,0 +1,96 @@ +package tfstate + +import ( + "bytes" + "compress/gzip" + "encoding/json" + "errors" + "fmt" + "io/ioutil" +) + +type compressionType string + +const ( + NoCompression compressionType = "none" + GzipCompression = "gzip" +) + +var errUnknownCompressionType = errors.New("unknown compression type") + +func (t compressionType) Decompress(in []byte) ([]byte, error) { + switch t { + case NoCompression: + return in, nil + case GzipCompression: + r, err := gzip.NewReader(bytes.NewBuffer(in)) + if err != nil { + return nil, err + } + return ioutil.ReadAll(r) + default: + return nil, errUnknownCompressionType + } +} + +func (t compressionType) Compress(in []byte) ([]byte, error) { + switch t { + case NoCompression: + return in, nil + case GzipCompression: + buf := &bytes.Buffer{} + w := gzip.NewWriter(buf) + _, err := w.Write(in) + if err != nil { + return nil, err + } + err = w.Close() + return buf.Bytes(), err + default: + return nil, errUnknownCompressionType + } +} + +func UnpackState(in []byte) ([]byte, error) { + var compressedState compressedState + err := json.Unmarshal(in, &compressedState) + if err != nil { + return nil, fmt.Errorf("json: %v", err) + } + + state, err := compressedState.Decompress() + if err != nil { + return nil, fmt.Errorf("decompress: %v", err) + } + return state, nil +} + +func PackState(in []byte) ([]byte, error) { + compressedState, err := compressState(in, GzipCompression) + if err != nil { + return nil, err + } + + return json.MarshalIndent(compressedState, "", "\t") +} + +func compressState(in []byte, compression compressionType) (compressedState, error) { + compressed, err := compression.Compress(in) + if err != nil { + return compressedState{}, fmt.Errorf("compressing state: %v", err) + } + return compressedState{ + Compression: compression, + Content: compressed, + }, nil + +} + +type compressedState struct { + Compression compressionType `json:"compression"` + Content []byte `json:"content"` +} + +func (s compressedState) Decompress() ([]byte, error) { + return s.Compression.Decompress(s.Content) +} diff --git a/internals/secrethub/run.go b/internals/secrethub/run.go index 939726f4..38651219 100644 --- a/internals/secrethub/run.go +++ b/internals/secrethub/run.go @@ -8,13 +8,14 @@ import ( "strings" "syscall" - "github.com/secrethub/secrethub-cli/internals/cli/masker" + "github.com/secrethub/secrethub-cli/internals/integrations/tfstate" "github.com/secrethub/secrethub-cli/internals/secrethub/tpl" "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-go/internals/errio" + "github.com/secrethub/secrethub-cli/internals/cli/masker" "github.com/secrethub/secrethub-cli/internals/cli/validation" "github.com/secrethub/secrethub-cli/internals/secrethub/command" ) @@ -56,6 +57,8 @@ type RunCommand struct { maskerOptions masker.Options newClient newClientFunc ignoreMissingSecrets bool + tfStateEnabled bool + tfStatePort uint16 } // NewRunCommand creates a new RunCommand. @@ -83,6 +86,8 @@ func (cmd *RunCommand) Register(r command.Registerer) { clause.Flag("no-output-buffering", "Disable output buffering. This increases output responsiveness, but decreases the probability that secrets get masked.").BoolVar(&cmd.maskerOptions.DisableBuffer) clause.Flag("masking-buffer-period", "The time period for which output is buffered. A higher value increases the probability that secrets get masked but decreases output responsiveness.").Default("50ms").DurationVar(&cmd.maskerOptions.BufferDelay) clause.Flag("ignore-missing-secrets", "Do not return an error when a secret does not exist and use an empty value instead.").BoolVar(&cmd.ignoreMissingSecrets) + clause.Flag("tfstate", "Serve HTTP endpoint as a Terraform State Backend.").Hidden().BoolVar(&cmd.tfStateEnabled) + clause.Flag("tfstate-port", "Port used for Terraform State HTTP endpoint.").Default("8118").Hidden().Uint16Var(&cmd.tfStatePort) cmd.environment.register(clause) command.BindAction(clause, cmd.Run) } @@ -106,6 +111,20 @@ func (cmd *RunCommand) Run() error { sequences = append(sequences, []byte(val)) } } + if cmd.tfStateEnabled { + client, err := cmd.newClient() + if err != nil { + return err + } + + go func() { + err := tfstate.New(client, cmd.tfStatePort, cmd.io.Stdout()).Serve() + if err != nil { + fmt.Printf("State backend error: %s\n", err) + } + }() + } + m := masker.New(sequences, &cmd.maskerOptions) command := exec.Command(cmd.command[0], cmd.command[1:]...) From c44cb03eedb92960c541db06701736467765ebb1 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Thu, 7 May 2020 11:29:40 +0200 Subject: [PATCH 02/11] Fix build on darwin --- internals/integrations/tfstate/socks_darwin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/integrations/tfstate/socks_darwin.go b/internals/integrations/tfstate/socks_darwin.go index c1515c22..a1290c9b 100644 --- a/internals/integrations/tfstate/socks_darwin.go +++ b/internals/integrations/tfstate/socks_darwin.go @@ -25,5 +25,5 @@ func tcpSocks() ([]Connection, error) { }, }) } - return res + return res, nil } From 8d0f107f4441eb390c4f69d3dec834bc3ac1e2f9 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Fri, 8 May 2020 16:31:57 +0200 Subject: [PATCH 03/11] Use provided lock instead of generating one --- internals/integrations/tfstate/backend.go | 30 +++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/internals/integrations/tfstate/backend.go b/internals/integrations/tfstate/backend.go index 39df0470..6aa80cb3 100644 --- a/internals/integrations/tfstate/backend.go +++ b/internals/integrations/tfstate/backend.go @@ -1,6 +1,7 @@ package tfstate import ( + "bytes" "fmt" "io" "io/ioutil" @@ -8,7 +9,6 @@ import ( "os" "github.com/secrethub/secrethub-go/internals/api" - "github.com/secrethub/secrethub-go/pkg/randchar" "github.com/secrethub/secrethub-go/pkg/secrethub" "github.com/secrethub/secrethub-go/pkg/secretpath" ) @@ -117,20 +117,17 @@ func (b *backend) Handle(w http.ResponseWriter, r *http.Request) { return } case "LOCK": - _, err := b.client.Secrets().Get(lockPath) - if !api.IsErrNotFound(err) { + currentLock, err := b.client.Secrets().Versions().GetWithData(lockPath + ":1") + if err == nil { w.WriteHeader(http.StatusLocked) + fmt.Fprint(w, string(currentLock.Data)) return - } - - data, err := randchar.Generate(32) - if err != nil { + } else if !api.IsErrNotFound(err) { w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, err.Error()) return } - res, err := b.client.Secrets().Write(lockPath, data) + res, err := b.client.Secrets().Write(lockPath, body) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, err.Error()) @@ -141,7 +138,20 @@ func (b *backend) Handle(w http.ResponseWriter, r *http.Request) { } case "UNLOCK": - err := b.client.Secrets().Delete(lockPath) + secret, err := b.client.Secrets().Read(lockPath) + if api.IsErrNotFound(err) { + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "not locked") + return + } + + if len(body) > 0 && !bytes.Equal(body, secret.Data) { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "incorrect lock") + return + } + + err = b.client.Secrets().Delete(lockPath) if err != nil { w.WriteHeader(http.StatusInternalServerError) return From c212ff9133ca1743c1763ace6c4d3be94c5e1ed9 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 13 May 2020 17:24:43 +0200 Subject: [PATCH 04/11] Update procspy Previous version had compatibility issues with the latest version of lsof on MacOS. --- go.mod | 2 +- go.sum | 10 ++++++++++ internals/integrations/tfstate/socks_darwin.go | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index a515c538..c653bb42 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 github.com/docker/go-units v0.3.3 github.com/fatih/color v1.7.0 + github.com/john-pierce/procspy v0.0.0-20191229154840-1689d734233b github.com/masterzen/winrm v0.0.0-20190308153735-1d17eaf15943 github.com/mattn/go-colorable v0.1.1 github.com/mattn/go-isatty v0.0.7 @@ -19,7 +20,6 @@ require ( github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/secrethub/demo-app v0.1.0 github.com/secrethub/secrethub-go v0.28.0 - github.com/weaveworks/procspy v0.0.0-20150706124340-cb970aa190c3 github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07 golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 diff --git a/go.sum b/go.sum index e58b0ccf..2e0b78f6 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5Vpd github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alvaroloes/enumer v1.1.2 h1:5khqHB33TZy1GWCO/lZwcroBFh7u+0j40T83VUbfAMY= +github.com/alvaroloes/enumer v1.1.2/go.mod h1:FxrjvuXoDAx9isTJrv4c+T410zFi0DtXIT0m65DJ+Wo= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY= @@ -44,6 +46,8 @@ github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASu github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/john-pierce/procspy v0.0.0-20191229154840-1689d734233b h1:7aPSSKrDkCDih93KBe2CYX6jYOg+DGwR9RT+KPFOMzo= +github.com/john-pierce/procspy v0.0.0-20191229154840-1689d734233b/go.mod h1:KYfx4NvMycLeRE8w0OmgF+QNX/gSPTE9m/e5PcUXchw= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -70,6 +74,8 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1 h1:/I3lTljEEDNYLho3/FUB7iD/oc2cEFgVmbHzV+O0PtU= +github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1/go.mod h1:eD5JxqMiuNYyFNmyY9rkJ/slN8y59oEu4Ei7F8OoKWQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= @@ -98,14 +104,18 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exq golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190524210228-3d17549cdc6b h1:iEAPfYPbYbxG/2lNN4cMOHkmgKNsCuUwkxlDCK46UlU= +golang.org/x/tools v0.0.0-20190524210228-3d17549cdc6b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internals/integrations/tfstate/socks_darwin.go b/internals/integrations/tfstate/socks_darwin.go index a1290c9b..77359a15 100644 --- a/internals/integrations/tfstate/socks_darwin.go +++ b/internals/integrations/tfstate/socks_darwin.go @@ -3,7 +3,7 @@ package tfstate import ( - "github.com/weaveworks/procspy" + "github.com/john-pierce/procspy" ) func tcpSocks() ([]Connection, error) { From faf8d56d57c64e6a40b23501072de3dd8ac627c2 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 13 May 2020 17:45:08 +0200 Subject: [PATCH 05/11] Give user feedback when repo/dir cannot be found --- internals/integrations/tfstate/backend.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/internals/integrations/tfstate/backend.go b/internals/integrations/tfstate/backend.go index 6aa80cb3..782cf4fa 100644 --- a/internals/integrations/tfstate/backend.go +++ b/internals/integrations/tfstate/backend.go @@ -111,7 +111,12 @@ func (b *backend) Handle(w http.ResponseWriter, r *http.Request) { w.Write(secret.Data) case http.MethodPost: _, err = b.client.Secrets().Write(statePath, body) - if err != nil { + if api.IsErrNotFound(err) { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintf(w, err.Error()) + fmt.Fprintf(b.logger, err.Error()) + return + } else if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, err.Error()) return @@ -128,7 +133,12 @@ func (b *backend) Handle(w http.ResponseWriter, r *http.Request) { } res, err := b.client.Secrets().Write(lockPath, body) - if err != nil { + if api.IsErrNotFound(err) { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintf(w, err.Error()) + fmt.Fprintf(b.logger, err.Error()) + return + } else if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, err.Error()) return From 72631ec4c43659fe89b538f59cd2423e5c3bb227 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 13 May 2020 18:22:19 +0200 Subject: [PATCH 06/11] Improve error outputting to user --- internals/integrations/tfstate/backend.go | 125 ++++++++++++---------- 1 file changed, 68 insertions(+), 57 deletions(-) diff --git a/internals/integrations/tfstate/backend.go b/internals/integrations/tfstate/backend.go index 782cf4fa..64d79854 100644 --- a/internals/integrations/tfstate/backend.go +++ b/internals/integrations/tfstate/backend.go @@ -2,6 +2,7 @@ package tfstate import ( "bytes" + "errors" "fmt" "io" "io/ioutil" @@ -44,34 +45,51 @@ func (b *backend) Serve() error { } func (b *backend) Handle(w http.ResponseWriter, r *http.Request) { - isChild, err := connectionFromChildProcess(os.Getpid(), r) + resp, err := b.handle(r) if err != nil { - w.WriteHeader(http.StatusForbidden) + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(b.logger, "Encountered an unexpected error: %s\n", err) return } + w.WriteHeader(resp.code) + fmt.Fprintf(w, resp.body) +} + +type statusResponse struct { + code int + body string +} + +func (b *backend) respondError(statusCode int, format string, a ...interface{}) *statusResponse { + msg := fmt.Sprintf(format, a...) + fmt.Fprintf(b.logger, "%s\n", msg) + return &statusResponse{ + code: statusCode, + body: msg, + } +} + +func (b *backend) handle(r *http.Request) (*statusResponse, error) { + isChild, err := connectionFromChildProcess(os.Getpid(), r) + if err != nil { + return nil, err + } if !isChild { - fmt.Println("can only be reached from a process spawned with secrethub run") - w.WriteHeader(http.StatusForbidden) - return + return b.respondError(http.StatusForbidden, "can only be reached from a process spawned with secrethub run"), nil } body, err := ioutil.ReadAll(r.Body) if err != nil { - fmt.Printf("Errors: %v\n", err) - return + return nil, fmt.Errorf("reading request body: %s", err) } path, password, ok := r.BasicAuth() if !ok { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintln(b.logger, "set the SecretHub path to the state as the username") - return + return b.respondError(http.StatusBadRequest, "set the SecretHub path to the state as the username"), nil } if secretpath.Count(path) < 2 { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(b.logger, "set user to a valid repository or directory. Got: %s\n", path) - return + return b.respondError(http.StatusBadRequest, "set user to a valid repository or directory, got: %s", path), nil } statePath := secretpath.Join(path, "state") @@ -80,18 +98,13 @@ func (b *backend) Handle(w http.ResponseWriter, r *http.Request) { secret, err := b.client.Secrets().ReadString(passwordPath) if err != nil && !api.IsErrNotFound(err) { - w.WriteHeader(http.StatusInternalServerError) - return + return nil, err } else if err == nil { if password == "" { - w.WriteHeader(http.StatusUnauthorized) - fmt.Fprintf(w, "password stored at %s should be set as auth password", passwordPath) - return + return b.respondError(http.StatusUnauthorized, "password stored at %s should be set as auth password", passwordPath), nil } if password != secret { - w.WriteHeader(http.StatusForbidden) - fmt.Fprintf(w, "provided password does not password stored at %s", passwordPath) - return + return b.respondError(http.StatusForbidden, "provided password does not password stored at %s", passwordPath), nil } } @@ -99,75 +112,73 @@ func (b *backend) Handle(w http.ResponseWriter, r *http.Request) { case http.MethodGet: secret, err := b.client.Secrets().Read(statePath) if api.IsErrNotFound(err) { - w.WriteHeader(http.StatusNotFound) - return + return &statusResponse{code: http.StatusNotFound}, nil } else if err != nil { - fmt.Fprintf(b.logger, "%v\n", err) - - w.WriteHeader(http.StatusInternalServerError) - return + return nil, err } - w.Write(secret.Data) + return &statusResponse{ + code: http.StatusOK, + body: string(secret.Data), + }, nil case http.MethodPost: _, err = b.client.Secrets().Write(statePath, body) if api.IsErrNotFound(err) { - w.WriteHeader(http.StatusNotFound) - fmt.Fprintf(w, err.Error()) - fmt.Fprintf(b.logger, err.Error()) - return + return b.respondError(http.StatusNotFound, err.Error()), nil } else if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, err.Error()) - return + return nil, err } + return &statusResponse{ + code: http.StatusOK, + }, nil case "LOCK": currentLock, err := b.client.Secrets().Versions().GetWithData(lockPath + ":1") if err == nil { - w.WriteHeader(http.StatusLocked) - fmt.Fprint(w, string(currentLock.Data)) - return + return &statusResponse{ + code: http.StatusLocked, + body: string(currentLock.Data), + }, nil } else if !api.IsErrNotFound(err) { - w.WriteHeader(http.StatusInternalServerError) - return + return nil, err } res, err := b.client.Secrets().Write(lockPath, body) if api.IsErrNotFound(err) { - w.WriteHeader(http.StatusNotFound) - fmt.Fprintf(w, err.Error()) - fmt.Fprintf(b.logger, err.Error()) - return + return b.respondError(http.StatusNotFound, err.Error()), nil } else if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, err.Error()) - return + return nil, err } if res.Version != 1 { - w.WriteHeader(http.StatusLocked) + return &statusResponse{ + code: http.StatusLocked, + }, nil } + return &statusResponse{ + code: http.StatusOK, + }, nil case "UNLOCK": secret, err := b.client.Secrets().Read(lockPath) if api.IsErrNotFound(err) { - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "not locked") - return + return &statusResponse{ + code: http.StatusOK, + body: "not locked", + }, nil } if len(body) > 0 && !bytes.Equal(body, secret.Data) { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, "incorrect lock") - return + return b.respondError(http.StatusBadRequest, "incorrect lock"), nil } err = b.client.Secrets().Delete(lockPath) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return + return nil, err } + return &statusResponse{ + code: http.StatusOK, + }, nil default: - w.WriteHeader(http.StatusNotImplemented) + return nil, errors.New("received an unexpected request") } } From ed9d05cc7327ef7a7083f62c0d984b35728a6b6a Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Thu, 14 May 2020 09:40:22 +0200 Subject: [PATCH 07/11] Remove unused state compression This should be handled in secrethub-go instead. --- internals/integrations/tfstate/state.go | 96 ------------------------- 1 file changed, 96 deletions(-) delete mode 100644 internals/integrations/tfstate/state.go diff --git a/internals/integrations/tfstate/state.go b/internals/integrations/tfstate/state.go deleted file mode 100644 index b4de0618..00000000 --- a/internals/integrations/tfstate/state.go +++ /dev/null @@ -1,96 +0,0 @@ -package tfstate - -import ( - "bytes" - "compress/gzip" - "encoding/json" - "errors" - "fmt" - "io/ioutil" -) - -type compressionType string - -const ( - NoCompression compressionType = "none" - GzipCompression = "gzip" -) - -var errUnknownCompressionType = errors.New("unknown compression type") - -func (t compressionType) Decompress(in []byte) ([]byte, error) { - switch t { - case NoCompression: - return in, nil - case GzipCompression: - r, err := gzip.NewReader(bytes.NewBuffer(in)) - if err != nil { - return nil, err - } - return ioutil.ReadAll(r) - default: - return nil, errUnknownCompressionType - } -} - -func (t compressionType) Compress(in []byte) ([]byte, error) { - switch t { - case NoCompression: - return in, nil - case GzipCompression: - buf := &bytes.Buffer{} - w := gzip.NewWriter(buf) - _, err := w.Write(in) - if err != nil { - return nil, err - } - err = w.Close() - return buf.Bytes(), err - default: - return nil, errUnknownCompressionType - } -} - -func UnpackState(in []byte) ([]byte, error) { - var compressedState compressedState - err := json.Unmarshal(in, &compressedState) - if err != nil { - return nil, fmt.Errorf("json: %v", err) - } - - state, err := compressedState.Decompress() - if err != nil { - return nil, fmt.Errorf("decompress: %v", err) - } - return state, nil -} - -func PackState(in []byte) ([]byte, error) { - compressedState, err := compressState(in, GzipCompression) - if err != nil { - return nil, err - } - - return json.MarshalIndent(compressedState, "", "\t") -} - -func compressState(in []byte, compression compressionType) (compressedState, error) { - compressed, err := compression.Compress(in) - if err != nil { - return compressedState{}, fmt.Errorf("compressing state: %v", err) - } - return compressedState{ - Compression: compression, - Content: compressed, - }, nil - -} - -type compressedState struct { - Compression compressionType `json:"compression"` - Content []byte `json:"content"` -} - -func (s compressedState) Decompress() ([]byte, error) { - return s.Compression.Decompress(s.Content) -} From ab96e7746954cc9f152577eddc6d003bdbf46993 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Thu, 14 May 2020 09:40:38 +0200 Subject: [PATCH 08/11] Fix linting issue --- internals/integrations/tfstate/backend.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internals/integrations/tfstate/backend.go b/internals/integrations/tfstate/backend.go index 64d79854..dbed6b2a 100644 --- a/internals/integrations/tfstate/backend.go +++ b/internals/integrations/tfstate/backend.go @@ -14,14 +14,14 @@ import ( "github.com/secrethub/secrethub-go/pkg/secretpath" ) -type backend struct { +type Backend struct { client secrethub.ClientInterface port uint16 logger io.Writer } -func New(client secrethub.ClientInterface, port uint16, logger io.Writer) *backend { - return &backend{ +func New(client secrethub.ClientInterface, port uint16, logger io.Writer) *Backend { + return &Backend{ client: client, port: port, logger: prefixWriter{ @@ -31,7 +31,7 @@ func New(client secrethub.ClientInterface, port uint16, logger io.Writer) *backe } } -func (b *backend) Serve() error { +func (b *Backend) Serve() error { server := &http.Server{ Addr: fmt.Sprintf("127.0.0.1:%d", b.port), Handler: http.HandlerFunc(b.Handle), @@ -44,7 +44,7 @@ func (b *backend) Serve() error { return nil } -func (b *backend) Handle(w http.ResponseWriter, r *http.Request) { +func (b *Backend) Handle(w http.ResponseWriter, r *http.Request) { resp, err := b.handle(r) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -60,7 +60,7 @@ type statusResponse struct { body string } -func (b *backend) respondError(statusCode int, format string, a ...interface{}) *statusResponse { +func (b *Backend) respondError(statusCode int, format string, a ...interface{}) *statusResponse { msg := fmt.Sprintf(format, a...) fmt.Fprintf(b.logger, "%s\n", msg) return &statusResponse{ @@ -69,7 +69,7 @@ func (b *backend) respondError(statusCode int, format string, a ...interface{}) } } -func (b *backend) handle(r *http.Request) (*statusResponse, error) { +func (b *Backend) handle(r *http.Request) (*statusResponse, error) { isChild, err := connectionFromChildProcess(os.Getpid(), r) if err != nil { return nil, err From 4adba31c12599cf8dff18baf7fc64ec7f5781afb Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Thu, 14 May 2020 09:53:37 +0200 Subject: [PATCH 09/11] Make compilable on windows-386 Backend now returns an error when invoked on Windows 386. Support can be added later. --- internals/integrations/tfstate/backend.go | 177 +---------------- .../integrations/tfstate/backend_other.go | 185 ++++++++++++++++++ .../integrations/tfstate/backend_windows.go | 21 ++ internals/integrations/tfstate/pid.go | 2 + internals/integrations/tfstate/socks_other.go | 1 + 5 files changed, 211 insertions(+), 175 deletions(-) create mode 100644 internals/integrations/tfstate/backend_other.go create mode 100644 internals/integrations/tfstate/backend_windows.go diff --git a/internals/integrations/tfstate/backend.go b/internals/integrations/tfstate/backend.go index dbed6b2a..c6f143d1 100644 --- a/internals/integrations/tfstate/backend.go +++ b/internals/integrations/tfstate/backend.go @@ -1,185 +1,12 @@ package tfstate import ( - "bytes" - "errors" "fmt" "io" - "io/ioutil" - "net/http" - "os" - - "github.com/secrethub/secrethub-go/internals/api" - "github.com/secrethub/secrethub-go/pkg/secrethub" - "github.com/secrethub/secrethub-go/pkg/secretpath" ) -type Backend struct { - client secrethub.ClientInterface - port uint16 - logger io.Writer -} - -func New(client secrethub.ClientInterface, port uint16, logger io.Writer) *Backend { - return &Backend{ - client: client, - port: port, - logger: prefixWriter{ - Writer: logger, - prefix: "[SecretHub]: ", - }, - } -} - -func (b *Backend) Serve() error { - server := &http.Server{ - Addr: fmt.Sprintf("127.0.0.1:%d", b.port), - Handler: http.HandlerFunc(b.Handle), - } - err := server.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - return err - } - - return nil -} - -func (b *Backend) Handle(w http.ResponseWriter, r *http.Request) { - resp, err := b.handle(r) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(b.logger, "Encountered an unexpected error: %s\n", err) - return - } - w.WriteHeader(resp.code) - fmt.Fprintf(w, resp.body) -} - -type statusResponse struct { - code int - body string -} - -func (b *Backend) respondError(statusCode int, format string, a ...interface{}) *statusResponse { - msg := fmt.Sprintf(format, a...) - fmt.Fprintf(b.logger, "%s\n", msg) - return &statusResponse{ - code: statusCode, - body: msg, - } -} - -func (b *Backend) handle(r *http.Request) (*statusResponse, error) { - isChild, err := connectionFromChildProcess(os.Getpid(), r) - if err != nil { - return nil, err - } - if !isChild { - return b.respondError(http.StatusForbidden, "can only be reached from a process spawned with secrethub run"), nil - } - - body, err := ioutil.ReadAll(r.Body) - if err != nil { - return nil, fmt.Errorf("reading request body: %s", err) - } - - path, password, ok := r.BasicAuth() - if !ok { - return b.respondError(http.StatusBadRequest, "set the SecretHub path to the state as the username"), nil - } - - if secretpath.Count(path) < 2 { - return b.respondError(http.StatusBadRequest, "set user to a valid repository or directory, got: %s", path), nil - } - - statePath := secretpath.Join(path, "state") - lockPath := secretpath.Join(path, "lock") - passwordPath := secretpath.Join(path, "password") - - secret, err := b.client.Secrets().ReadString(passwordPath) - if err != nil && !api.IsErrNotFound(err) { - return nil, err - } else if err == nil { - if password == "" { - return b.respondError(http.StatusUnauthorized, "password stored at %s should be set as auth password", passwordPath), nil - } - if password != secret { - return b.respondError(http.StatusForbidden, "provided password does not password stored at %s", passwordPath), nil - } - } - - switch r.Method { - case http.MethodGet: - secret, err := b.client.Secrets().Read(statePath) - if api.IsErrNotFound(err) { - return &statusResponse{code: http.StatusNotFound}, nil - } else if err != nil { - return nil, err - } - - return &statusResponse{ - code: http.StatusOK, - body: string(secret.Data), - }, nil - case http.MethodPost: - _, err = b.client.Secrets().Write(statePath, body) - if api.IsErrNotFound(err) { - return b.respondError(http.StatusNotFound, err.Error()), nil - } else if err != nil { - return nil, err - } - return &statusResponse{ - code: http.StatusOK, - }, nil - case "LOCK": - currentLock, err := b.client.Secrets().Versions().GetWithData(lockPath + ":1") - if err == nil { - return &statusResponse{ - code: http.StatusLocked, - body: string(currentLock.Data), - }, nil - } else if !api.IsErrNotFound(err) { - return nil, err - } - - res, err := b.client.Secrets().Write(lockPath, body) - if api.IsErrNotFound(err) { - return b.respondError(http.StatusNotFound, err.Error()), nil - } else if err != nil { - return nil, err - } - if res.Version != 1 { - return &statusResponse{ - code: http.StatusLocked, - }, nil - } - return &statusResponse{ - code: http.StatusOK, - }, nil - - case "UNLOCK": - secret, err := b.client.Secrets().Read(lockPath) - if api.IsErrNotFound(err) { - return &statusResponse{ - code: http.StatusOK, - body: "not locked", - }, nil - } - - if len(body) > 0 && !bytes.Equal(body, secret.Data) { - return b.respondError(http.StatusBadRequest, "incorrect lock"), nil - } - - err = b.client.Secrets().Delete(lockPath) - if err != nil { - return nil, err - } - return &statusResponse{ - code: http.StatusOK, - }, nil - default: - return nil, errors.New("received an unexpected request") - } +type Backend interface { + Serve() error } type prefixWriter struct { diff --git a/internals/integrations/tfstate/backend_other.go b/internals/integrations/tfstate/backend_other.go new file mode 100644 index 00000000..db4bac48 --- /dev/null +++ b/internals/integrations/tfstate/backend_other.go @@ -0,0 +1,185 @@ +// +build !windows !386 + +package tfstate + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + + "github.com/secrethub/secrethub-go/internals/api" + "github.com/secrethub/secrethub-go/pkg/secrethub" + "github.com/secrethub/secrethub-go/pkg/secretpath" +) + +type backend struct { + client secrethub.ClientInterface + port uint16 + logger io.Writer +} + +func New(client secrethub.ClientInterface, port uint16, logger io.Writer) Backend { + return &backend{ + client: client, + port: port, + logger: prefixWriter{ + Writer: logger, + prefix: "[SecretHub]: ", + }, + } +} + +func (b *backend) Serve() error { + server := &http.Server{ + Addr: fmt.Sprintf("127.0.0.1:%d", b.port), + Handler: http.HandlerFunc(b.Handle), + } + err := server.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + return err + } + + return nil +} + +func (b *backend) Handle(w http.ResponseWriter, r *http.Request) { + resp, err := b.handle(r) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(b.logger, "Encountered an unexpected error: %s\n", err) + return + } + w.WriteHeader(resp.code) + fmt.Fprintf(w, resp.body) +} + +type statusResponse struct { + code int + body string +} + +func (b *backend) respondError(statusCode int, format string, a ...interface{}) *statusResponse { + msg := fmt.Sprintf(format, a...) + fmt.Fprintf(b.logger, "%s\n", msg) + return &statusResponse{ + code: statusCode, + body: msg, + } +} + +func (b *backend) handle(r *http.Request) (*statusResponse, error) { + isChild, err := connectionFromChildProcess(os.Getpid(), r) + if err != nil { + return nil, err + } + if !isChild { + return b.respondError(http.StatusForbidden, "can only be reached from a process spawned with secrethub run"), nil + } + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, fmt.Errorf("reading request body: %s", err) + } + + path, password, ok := r.BasicAuth() + if !ok { + return b.respondError(http.StatusBadRequest, "set the SecretHub path to the state as the username"), nil + } + + if secretpath.Count(path) < 2 { + return b.respondError(http.StatusBadRequest, "set user to a valid repository or directory, got: %s", path), nil + } + + statePath := secretpath.Join(path, "state") + lockPath := secretpath.Join(path, "lock") + passwordPath := secretpath.Join(path, "password") + + secret, err := b.client.Secrets().ReadString(passwordPath) + if err != nil && !api.IsErrNotFound(err) { + return nil, err + } else if err == nil { + if password == "" { + return b.respondError(http.StatusUnauthorized, "password stored at %s should be set as auth password", passwordPath), nil + } + if password != secret { + return b.respondError(http.StatusForbidden, "provided password does not password stored at %s", passwordPath), nil + } + } + + switch r.Method { + case http.MethodGet: + secret, err := b.client.Secrets().Read(statePath) + if api.IsErrNotFound(err) { + return &statusResponse{code: http.StatusNotFound}, nil + } else if err != nil { + return nil, err + } + + return &statusResponse{ + code: http.StatusOK, + body: string(secret.Data), + }, nil + case http.MethodPost: + _, err = b.client.Secrets().Write(statePath, body) + if api.IsErrNotFound(err) { + return b.respondError(http.StatusNotFound, err.Error()), nil + } else if err != nil { + return nil, err + } + return &statusResponse{ + code: http.StatusOK, + }, nil + case "LOCK": + currentLock, err := b.client.Secrets().Versions().GetWithData(lockPath + ":1") + if err == nil { + return &statusResponse{ + code: http.StatusLocked, + body: string(currentLock.Data), + }, nil + } else if !api.IsErrNotFound(err) { + return nil, err + } + + res, err := b.client.Secrets().Write(lockPath, body) + if api.IsErrNotFound(err) { + return b.respondError(http.StatusNotFound, err.Error()), nil + } else if err != nil { + return nil, err + } + if res.Version != 1 { + return &statusResponse{ + code: http.StatusLocked, + }, nil + } + return &statusResponse{ + code: http.StatusOK, + }, nil + + case "UNLOCK": + secret, err := b.client.Secrets().Read(lockPath) + if api.IsErrNotFound(err) { + return &statusResponse{ + code: http.StatusOK, + body: "not locked", + }, nil + } + + if len(body) > 0 && !bytes.Equal(body, secret.Data) { + return b.respondError(http.StatusBadRequest, "incorrect lock"), nil + } + + err = b.client.Secrets().Delete(lockPath) + if err != nil { + return nil, err + } + return &statusResponse{ + code: http.StatusOK, + }, nil + default: + return nil, errors.New("received an unexpected request") + } +} diff --git a/internals/integrations/tfstate/backend_windows.go b/internals/integrations/tfstate/backend_windows.go new file mode 100644 index 00000000..23ea5cbb --- /dev/null +++ b/internals/integrations/tfstate/backend_windows.go @@ -0,0 +1,21 @@ +// +build windows,386 + +package tfstate + +import ( + "errors" + "io" + + "github.com/secrethub/secrethub-go/pkg/secrethub" +) + +type notSupportedBackend struct { +} + +func New(client secrethub.ClientInterface, port uint16, logger io.Writer) Backend { + return ¬SupportedBackend{} +} + +func (b *notSupportedBackend) Serve() error { + return errors.New("tfstate backend currently not supported on Windows i386") +} diff --git a/internals/integrations/tfstate/pid.go b/internals/integrations/tfstate/pid.go index 760c6bd0..aee9d015 100644 --- a/internals/integrations/tfstate/pid.go +++ b/internals/integrations/tfstate/pid.go @@ -1,3 +1,5 @@ +// +build !windows !386 + package tfstate import ( diff --git a/internals/integrations/tfstate/socks_other.go b/internals/integrations/tfstate/socks_other.go index 4ab337a3..01c6ef13 100644 --- a/internals/integrations/tfstate/socks_other.go +++ b/internals/integrations/tfstate/socks_other.go @@ -1,4 +1,5 @@ // +build !darwin +// +build !windows !386 package tfstate From 2483234ef8812499234892b6b3605a91e1ab1877 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Tue, 16 Jun 2020 13:20:07 +0200 Subject: [PATCH 10/11] Add check to make sure provided path is a directory --- internals/integrations/tfstate/backend_other.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internals/integrations/tfstate/backend_other.go b/internals/integrations/tfstate/backend_other.go index db4bac48..25d6d941 100644 --- a/internals/integrations/tfstate/backend_other.go +++ b/internals/integrations/tfstate/backend_other.go @@ -98,6 +98,10 @@ func (b *backend) handle(r *http.Request) (*statusResponse, error) { lockPath := secretpath.Join(path, "lock") passwordPath := secretpath.Join(path, "password") + if _, err := b.client.Dirs().GetTree(path, 0, false); api.IsErrNotFound(err) { + return b.respondError(http.StatusBadRequest, "`%s` is not a directory on SecretHub", path), nil + } + secret, err := b.client.Secrets().ReadString(passwordPath) if err != nil && !api.IsErrNotFound(err) { return nil, err From 31af87200184d7c6aaf5c7a223ce297558b5431f Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Tue, 16 Jun 2020 13:20:51 +0200 Subject: [PATCH 11/11] Make returned error message more explanatory --- internals/integrations/tfstate/backend_other.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internals/integrations/tfstate/backend_other.go b/internals/integrations/tfstate/backend_other.go index 25d6d941..53ee03c5 100644 --- a/internals/integrations/tfstate/backend_other.go +++ b/internals/integrations/tfstate/backend_other.go @@ -87,11 +87,11 @@ func (b *backend) handle(r *http.Request) (*statusResponse, error) { path, password, ok := r.BasicAuth() if !ok { - return b.respondError(http.StatusBadRequest, "set the SecretHub path to the state as the username"), nil + return b.respondError(http.StatusBadRequest, "`username` field must be set to the SecretHub directory where the Terraform state should be stored (for example `//terraform-state`)"), nil } if secretpath.Count(path) < 2 { - return b.respondError(http.StatusBadRequest, "set user to a valid repository or directory, got: %s", path), nil + return b.respondError(http.StatusBadRequest, "`username` field must be set to the SecretHub directory where the Terraform state should be stored (for example `//terraform-state`), got: %s", path), nil } statePath := secretpath.Join(path, "state") @@ -110,7 +110,7 @@ func (b *backend) handle(r *http.Request) (*statusResponse, error) { return b.respondError(http.StatusUnauthorized, "password stored at %s should be set as auth password", passwordPath), nil } if password != secret { - return b.respondError(http.StatusForbidden, "provided password does not password stored at %s", passwordPath), nil + return b.respondError(http.StatusForbidden, "provided password does not match password stored at %s", passwordPath), nil } }