diff --git a/go.mod b/go.mod index fb42264c..c653bb42 100644 --- a/go.mod +++ b/go.mod @@ -7,12 +7,15 @@ 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/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 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 diff --git a/go.sum b/go.sum index 977a9e18..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= @@ -20,6 +22,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= @@ -42,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= @@ -62,10 +68,14 @@ 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= 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= @@ -84,6 +94,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= @@ -92,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/backend.go b/internals/integrations/tfstate/backend.go new file mode 100644 index 00000000..c6f143d1 --- /dev/null +++ b/internals/integrations/tfstate/backend.go @@ -0,0 +1,20 @@ +package tfstate + +import ( + "fmt" + "io" +) + +type Backend interface { + Serve() error +} + +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/backend_other.go b/internals/integrations/tfstate/backend_other.go new file mode 100644 index 00000000..53ee03c5 --- /dev/null +++ b/internals/integrations/tfstate/backend_other.go @@ -0,0 +1,189 @@ +// +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, "`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, "`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") + 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 + } 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 match 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 new file mode 100644 index 00000000..aee9d015 --- /dev/null +++ b/internals/integrations/tfstate/pid.go @@ -0,0 +1,50 @@ +// +build !windows !386 + +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..77359a15 --- /dev/null +++ b/internals/integrations/tfstate/socks_darwin.go @@ -0,0 +1,29 @@ +// +build darwin + +package tfstate + +import ( + "github.com/john-pierce/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, nil +} diff --git a/internals/integrations/tfstate/socks_other.go b/internals/integrations/tfstate/socks_other.go new file mode 100644 index 00000000..01c6ef13 --- /dev/null +++ b/internals/integrations/tfstate/socks_other.go @@ -0,0 +1,34 @@ +// +build !darwin +// +build !windows !386 + +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/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:]...)