Skip to content
This repository has been archived by the owner on Feb 16, 2023. It is now read-only.

Integrated Terraform State Backend #285

Draft
wants to merge 11 commits into
base: develop
Choose a base branch
from
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand All @@ -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=
Expand All @@ -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=
Expand All @@ -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=
Expand All @@ -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=
Expand All @@ -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=
Expand Down
20 changes: 20 additions & 0 deletions internals/integrations/tfstate/backend.go
Original file line number Diff line number Diff line change
@@ -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
}
189 changes: 189 additions & 0 deletions internals/integrations/tfstate/backend_other.go
Original file line number Diff line number Diff line change
@@ -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 `<org>/<repo>/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 `<org>/<repo>/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")
}
}
21 changes: 21 additions & 0 deletions internals/integrations/tfstate/backend_windows.go
Original file line number Diff line number Diff line change
@@ -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 &notSupportedBackend{}
}

func (b *notSupportedBackend) Serve() error {
return errors.New("tfstate backend currently not supported on Windows i386")
}
50 changes: 50 additions & 0 deletions internals/integrations/tfstate/pid.go
Original file line number Diff line number Diff line change
@@ -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
}
18 changes: 18 additions & 0 deletions internals/integrations/tfstate/socks.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading