diff --git a/cmd/terraform-backend.go b/cmd/terraform-backend.go index dc96bb3..47aa32d 100644 --- a/cmd/terraform-backend.go +++ b/cmd/terraform-backend.go @@ -9,7 +9,8 @@ import ( "github.com/gorilla/mux" "github.com/nimbolus/terraform-backend/kms" "github.com/nimbolus/terraform-backend/terraform" - "github.com/nimbolus/terraform-backend/terraform/locker" + "github.com/nimbolus/terraform-backend/terraform/auth" + "github.com/nimbolus/terraform-backend/terraform/lock" "github.com/nimbolus/terraform-backend/terraform/store" log "github.com/sirupsen/logrus" "github.com/spf13/viper" @@ -28,7 +29,7 @@ func getStateID(req *http.Request) string { return fmt.Sprintf("%x", hash[:]) } -func stateHandler(stateStore store.Store, locker locker.Locker, kms kms.KMS) func(http.ResponseWriter, *http.Request) { +func stateHandler(stateStore store.Store, locker lock.Locker, kms kms.KMS, authenticator auth.Authenticator) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, req *http.Request) { body, err := io.ReadAll(req.Body) defer req.Body.Close() @@ -37,12 +38,23 @@ func stateHandler(stateStore store.Store, locker locker.Locker, kms kms.KMS) fun return } - state := &terraform.State{} - state.ID = getStateID(req) + state := &terraform.State{ + ID: getStateID(req), + } log.Infof("%s %s", req.Method, req.URL.Path) log.Trace("request: %s %s: %s", req.Method, req.URL.Path, body) + if ok, err := authenticator.Authenticate(req, state); err != nil { + log.Warnf("failed to evaluate request authentication for state id %s", state.ID) + httpResponse(w, http.StatusBadRequest, "Authentication missing") + return + } else if !ok { + log.Warnf("failed to authenticate request for state id %s", state.ID) + httpResponse(w, http.StatusBadRequest, "Permission denied") + return + } + switch req.Method { case "LOCK": log.Debugf("try to lock state with id %s", state.ID) @@ -76,9 +88,10 @@ func stateHandler(stateStore store.Store, locker locker.Locker, kms kms.KMS) fun return case http.MethodGet: log.Debugf("get state with id %s", state.ID) + stateID := state.ID state, err = stateStore.GetState(state.ID) if err != nil { - log.Warnf("failed to get state with id %s: %v", state.ID, err) + log.Warnf("failed to get state with id %s: %v", stateID, err) httpResponse(w, http.StatusBadRequest, err.Error()) return } @@ -126,12 +139,9 @@ func stateHandler(stateStore store.Store, locker locker.Locker, kms kms.KMS) fun } func main() { + viper.AutomaticEnv() viper.SetDefault("log_level", "info") viper.SetDefault("listen_addr", ":8080") - viper.SetDefault("store_backend", "file") - viper.SetDefault("lock_backend", "local") - viper.SetDefault("kms_backend", "local") - viper.AutomaticEnv() level, err := log.ParseLevel(viper.GetString("log_level")) if err != nil { @@ -140,78 +150,33 @@ func main() { log.Infof("set log level to %s", level.String()) log.SetLevel(level) - // var stateStore terraform.Store - // switch viper.GetString("store_backend") { - // case "file": - // stateStore, err = filestore.NewFileStore("./example/states") - // default: - // log.Fatalf("failed to initialize lock backend: %s is unknown", viper.GetString("store_backend")) - // } - - // var locker locker.Locker - // switch viper.GetString("lock_backend") { - // case "redis": - // log.Println("initializing Redis lock") - // locker = redislock.NewRedisLock() - // case "local": - // log.Println("initializing local lock") - // locker = locallock.NewLocalLock() - // default: - // log.Fatalf("failed to initialize lock backend: %s is unknown", viper.GetString("lock_backend")) - // } - - // var kms kms.KMS - // switch viper.GetString("kms_backend") { - // case "transit": - // log.Println("initializing Vault Transit KMS") - // kms, err = vaulttransit.NewVaultTransit(viper.GetString("kms_transit_engine"), viper.GetString("kms_transit_key")) - // case "local": - // var key string - // if keyPath := viper.GetString("kms_vault_key_path"); keyPath != "" { - // log.Infof("initializing local KMS with key from Vault K/V engine") - // vaultClient, err := vaultclient.NewVaultClient() - // if err != nil { - // log.Fatalf("failed to setup Vault client for local KMS: %v", err) - // } - - // key, err = vaultclient.GetKvValue(vaultClient, keyPath, "key") - // if err != nil { - // log.Fatalf("failed to get key for local KMS from Vault: %v", err) - // } - // } else { - // log.Infof("initializing local KMS with key from environment") - // if key = viper.GetString("kms_key"); key == "" { - // key, _ = simplekms.GenerateKey() - // log.Printf("No key defined. Set KMS_KEY to this generated key: %s", key) - // return - // } - // } - // kms, err = simplekms.NewSimpleKMS(key) - // default: - // log.Fatalf("failed to initialize KMS backend %s: %s is unknown", viper.GetString("kms_backend"), viper.GetString("kms_backend")) - // } - stateStore, err := store.GetStore() if err != nil { - log.Fatalf("failed to initialize store backend: %v", err) + log.Fatal(err.Error()) } log.Infof("initialized %s store backend", stateStore.GetName()) - locker, err := locker.GetLocker() + locker, err := lock.GetLocker() if err != nil { - log.Fatalf("failed to initialize lock backend: %v", err) + log.Fatal(err.Error()) } log.Infof("initialized %s lock backend", locker.GetName()) kms, err := kms.GetKMS() if err != nil { - log.Fatalf("failed to initialize KMS backend: %v", err) + log.Fatal(err.Error()) } log.Infof("initialized %s KMS backend", kms.GetName()) + authenticator, err := auth.GetAuthenticator() + if err != nil { + log.Fatal(err.Error()) + } + log.Infof("initialized %s auth backend", authenticator.GetName()) + addr := viper.GetString("listen_addr") log.Printf("listening on %s", addr) r := mux.NewRouter().StrictSlash(true) - r.HandleFunc("/state/{project}/{id}", stateHandler(stateStore, locker, kms)) + r.HandleFunc("/state/{project}/{id}", stateHandler(stateStore, locker, kms, authenticator)) log.Fatalf("failed to listen on %s: %v", addr, http.ListenAndServe(addr, r)) } diff --git a/go.mod b/go.mod index d00067c..957b173 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,8 @@ require ( github.com/go-redsync/redsync/v4 v4.5.0 github.com/gorilla/mux v1.8.0 github.com/hashicorp/vault/api v1.4.1 + github.com/minio/minio-go/v7 v7.0.23 + github.com/sirupsen/logrus v1.8.1 github.com/spf13/viper v1.10.1 ) @@ -16,10 +18,12 @@ require ( github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.0 // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/uuid v1.1.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.0.0 // indirect @@ -38,19 +42,26 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/vault/sdk v0.4.1 // indirect github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.13.5 // indirect + github.com/klauspost/cpuid v1.3.1 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect + github.com/minio/md5-simd v1.1.0 // indirect + github.com/minio/sha256-simd v0.1.1 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/oklog/run v1.0.0 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/pierrec/lz4 v2.5.2+incompatible // indirect + github.com/rs/xid v1.2.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/go.sum b/go.sum index 3ffd76c..5b6a79a 100644 --- a/go.sum +++ b/go.sum @@ -99,6 +99,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -221,11 +223,14 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -304,13 +309,21 @@ github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZ github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.5 h1:9O69jUPDcsT9fEm74W92rZL9FQY7rCdaXVneq+yyzl4= +github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= +github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -341,6 +354,12 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= +github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= +github.com/minio/minio-go/v7 v7.0.23 h1:NleyGQvAn9VQMU+YHVrgV4CX+EPtxPt/78lHOOTncy4= +github.com/minio/minio-go/v7 v7.0.23/go.mod h1:ei5JjmxwHaMrgsMrn4U/+Nmg+d8MKS1U2DAn1ou4+Do= +github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= +github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= @@ -360,9 +379,11 @@ github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -409,6 +430,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= @@ -419,6 +442,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= @@ -475,6 +500,7 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -627,6 +653,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -658,6 +685,7 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486 h1:5hpz5aRr+W1erYCL5JRhSUBJRph7l9XkNveoExlrKYk= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -680,6 +708,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -888,6 +917,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= diff --git a/kms/kms.go b/kms/kms.go index d894e86..4f55fc2 100644 --- a/kms/kms.go +++ b/kms/kms.go @@ -3,8 +3,8 @@ package kms import ( "fmt" - "github.com/nimbolus/terraform-backend/kms/localkms" - "github.com/nimbolus/terraform-backend/kms/vaulttransit" + "github.com/nimbolus/terraform-backend/kms/local" + "github.com/nimbolus/terraform-backend/kms/transit" "github.com/nimbolus/terraform-backend/vaultclient" "github.com/spf13/viper" ) @@ -23,11 +23,11 @@ func GetKMS() (k KMS, err error) { case "local": key := viper.GetString("kms_key") if key == "" { - key, _ = localkms.GenerateKey() + key, _ = local.GenerateKey() return nil, fmt.Errorf("no key for local KMS defined, set KMS_KEY (e.g. to this generated key: %s)", key) } - k, err = localkms.NewLocalKMS(key) + k, err = local.NewLocalKMS(key) case "vault": var key string keyPath := viper.GetString("kms_vault_key_path") @@ -41,9 +41,9 @@ func GetKMS() (k KMS, err error) { return nil, fmt.Errorf("failed to get key for Vault KMS: %v", err) } - k, err = localkms.NewLocalKMS(key) + k, err = local.NewLocalKMS(key) case "transit": - k, err = vaulttransit.NewVaultTransit(viper.GetString("kms_transit_engine"), viper.GetString("kms_transit_key")) + k, err = transit.NewVaultTransit(viper.GetString("kms_transit_engine"), viper.GetString("kms_transit_key")) default: return nil, fmt.Errorf("failed to initialize KMS backend %s: %v", backend, err) } diff --git a/kms/localkms/localkms.go b/kms/local/local.go similarity index 98% rename from kms/localkms/localkms.go rename to kms/local/local.go index 3b07afe..ca4a6c9 100644 --- a/kms/localkms/localkms.go +++ b/kms/local/local.go @@ -1,4 +1,4 @@ -package localkms +package local import ( "crypto/aes" diff --git a/kms/vaulttransit/vaulttransit.go b/kms/transit/transit.go similarity index 98% rename from kms/vaulttransit/vaulttransit.go rename to kms/transit/transit.go index be2232d..cdd9d4e 100644 --- a/kms/vaulttransit/vaulttransit.go +++ b/kms/transit/transit.go @@ -1,4 +1,4 @@ -package vaulttransit +package transit import ( "encoding/base64" diff --git a/terraform/auth/auth.go b/terraform/auth/auth.go new file mode 100644 index 0000000..19956c9 --- /dev/null +++ b/terraform/auth/auth.go @@ -0,0 +1,31 @@ +package auth + +import ( + "fmt" + "net/http" + + "github.com/nimbolus/terraform-backend/terraform" + "github.com/nimbolus/terraform-backend/terraform/auth/basic" + "github.com/spf13/viper" +) + +type Authenticator interface { + GetName() string + Authenticate(*http.Request, *terraform.State) (bool, error) +} + +func GetAuthenticator() (a Authenticator, err error) { + viper.SetDefault("auth_backend", "basic") + backend := viper.GetString("auth_backend") + + switch backend { + case "basic": + a = basic.NewBasicAuth() + default: + err = fmt.Errorf("backend is not implemented") + } + if err != nil { + return nil, fmt.Errorf("failed to initialize auth backend %s: %v", backend, err) + } + return +} diff --git a/terraform/auth/basic/basic.go b/terraform/auth/basic/basic.go new file mode 100644 index 0000000..e631919 --- /dev/null +++ b/terraform/auth/basic/basic.go @@ -0,0 +1,31 @@ +package basic + +import ( + "crypto/sha256" + "fmt" + "net/http" + + "github.com/nimbolus/terraform-backend/terraform" +) + +type BasicAuth struct{} + +func NewBasicAuth() *BasicAuth { + return &BasicAuth{} +} + +func (l *BasicAuth) GetName() string { + return "basic" +} + +func (b *BasicAuth) Authenticate(req *http.Request, s *terraform.State) (bool, error) { + username, password, ok := req.BasicAuth() + if !ok { + return false, fmt.Errorf("no basic auth header found") + } + + id := fmt.Sprintf("%s:%s;%s", username, password, s.ID) + hash := sha256.Sum256([]byte(id)) + s.ID = fmt.Sprintf("%x", hash[:]) + return true, nil +} diff --git a/terraform/auth/jwt/jwt.go b/terraform/auth/jwt/jwt.go new file mode 100644 index 0000000..e22cb07 --- /dev/null +++ b/terraform/auth/jwt/jwt.go @@ -0,0 +1 @@ +package jwt diff --git a/terraform/locker/locallock/locallock.go b/terraform/lock/local/local.go similarity index 97% rename from terraform/locker/locallock/locallock.go rename to terraform/lock/local/local.go index e83da1d..59efb7a 100644 --- a/terraform/locker/locallock/locallock.go +++ b/terraform/lock/local/local.go @@ -1,4 +1,4 @@ -package locallock +package local import ( "fmt" diff --git a/terraform/locker/locker.go b/terraform/lock/locker.go similarity index 63% rename from terraform/locker/locker.go rename to terraform/lock/locker.go index a0062aa..387458a 100644 --- a/terraform/locker/locker.go +++ b/terraform/lock/locker.go @@ -1,11 +1,11 @@ -package locker +package lock import ( "fmt" "github.com/nimbolus/terraform-backend/terraform" - "github.com/nimbolus/terraform-backend/terraform/locker/locallock" - "github.com/nimbolus/terraform-backend/terraform/locker/redislock" + "github.com/nimbolus/terraform-backend/terraform/lock/local" + "github.com/nimbolus/terraform-backend/terraform/lock/redis" "github.com/spf13/viper" ) @@ -20,15 +20,15 @@ func GetLocker() (l Locker, err error) { backend := viper.GetString("lock_backend") switch backend { - case "redis": - l = redislock.NewRedisLock() case "local": - l = locallock.NewLocalLock() + l = local.NewLocalLock() + case "redis": + l = redis.NewRedisLock() default: err = fmt.Errorf("backend is not implemented") } if err != nil { - return nil, fmt.Errorf("failed to initialize store backend %s: %v", backend, err) + return nil, fmt.Errorf("failed to initialize lock backend %s: %v", backend, err) } return } diff --git a/terraform/locker/redislock/redislock.go b/terraform/lock/redis/redis.go similarity index 99% rename from terraform/locker/redislock/redislock.go rename to terraform/lock/redis/redis.go index 1af6173..4bf3b03 100644 --- a/terraform/locker/redislock/redislock.go +++ b/terraform/lock/redis/redis.go @@ -1,4 +1,4 @@ -package redislock +package redis import ( "context" diff --git a/terraform/store/filestore/filestore.go b/terraform/store/file/file.go similarity index 91% rename from terraform/store/filestore/filestore.go rename to terraform/store/file/file.go index f31c2b8..b92095a 100644 --- a/terraform/store/filestore/filestore.go +++ b/terraform/store/file/file.go @@ -1,4 +1,4 @@ -package filestore +package file import ( "errors" @@ -15,7 +15,7 @@ type FileStore struct { func NewFileStore(directory string) (*FileStore, error) { err := os.MkdirAll(directory, 0700) if err != nil { - return nil, fmt.Errorf("failed to create file store at %s: %v", directory, err) + return nil, fmt.Errorf("failed to create directory %s: %v", directory, err) } return &FileStore{ diff --git a/terraform/store/s3/s3.go b/terraform/store/s3/s3.go new file mode 100644 index 0000000..1944ded --- /dev/null +++ b/terraform/store/s3/s3.go @@ -0,0 +1,76 @@ +package s3 + +import ( + "bytes" + "context" + "fmt" + + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" + "github.com/nimbolus/terraform-backend/terraform" +) + +type S3Store struct { + client *minio.Client + bucket string +} + +func NewS3Store(endpoint, bucket, accessKey, secretKey string, useSSL bool) (*S3Store, error) { + client, err := minio.New(endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(accessKey, secretKey, ""), + Secure: useSSL, + }) + if err != nil { + return nil, fmt.Errorf("failed to initialize minio client: %v", err) + } + + if exists, err := client.BucketExists(context.Background(), bucket); err != nil { + return nil, fmt.Errorf("failed to check for bucket: %v", err) + } else if !exists { + return nil, fmt.Errorf("bucket does not exist") + } + + return &S3Store{ + client: client, + bucket: bucket, + }, nil +} + +func (s *S3Store) GetName() string { + return "s3" +} + +func (s *S3Store) SaveState(state *terraform.State) error { + r := bytes.NewReader(state.Data) + _, err := s.client.PutObject(context.Background(), s.bucket, getObjectName(state.ID), r, r.Size(), minio.PutObjectOptions{ + ContentType: "application/octet-stream", + }) + return err +} + +func (s *S3Store) GetState(id string) (*terraform.State, error) { + state := &terraform.State{ + ID: id, + } + + obj, err := s.client.GetObject(context.Background(), s.bucket, getObjectName(id), minio.GetObjectOptions{}) + if err != nil { + return state, err + } + defer obj.Close() + + buf := new(bytes.Buffer) + if _, err := buf.ReadFrom(obj); err != nil { + if minio.ToErrorResponse(err).Code == "NoSuchKey" { + return state, nil + } + return state, err + } + + state.Data = buf.Bytes() + return state, nil +} + +func getObjectName(id string) string { + return fmt.Sprintf("%s.tfstate", id) +} diff --git a/terraform/store/store.go b/terraform/store/store.go index 52fe95d..c932a84 100644 --- a/terraform/store/store.go +++ b/terraform/store/store.go @@ -4,7 +4,8 @@ import ( "fmt" "github.com/nimbolus/terraform-backend/terraform" - "github.com/nimbolus/terraform-backend/terraform/store/filestore" + "github.com/nimbolus/terraform-backend/terraform/store/file" + "github.com/nimbolus/terraform-backend/terraform/store/s3" "github.com/spf13/viper" ) @@ -20,7 +21,22 @@ func GetStore() (s Store, err error) { switch backend { case "file": - s, err = filestore.NewFileStore("./example/states") + viper.SetDefault("store_local_dir", "./states") + s, err = file.NewFileStore(viper.GetString("store_local_dir")) + case "s3": + viper.SetDefault("store_s3_endpoint", "s3.amazonaws.com") + viper.SetDefault("store_s3_use_ssl", true) + viper.SetDefault("store_s3_access_key", "access-key-id") + viper.SetDefault("store_s3_secret_key", "secret-access-key") + viper.SetDefault("store_s3_bucket", "terraform-state") + + endpoint := viper.GetString("store_s3_endpoint") + useSSL := viper.GetBool("store_s3_use_ssl") + accessKey := viper.GetString("store_s3_access_key") + secretKey := viper.GetString("store_s3_secret_key") + bucket := viper.GetString("store_s3_bucket") + + s, err = s3.NewS3Store(endpoint, bucket, accessKey, secretKey, useSSL) default: err = fmt.Errorf("backend is not implemented") }