diff --git a/Makefile b/Makefile index 91c3d60..ffd11e2 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,25 @@ docker-services: docker docker-update: docker docker service update --image icecave/honeycomb:dev --force honeycomb +.PHONY: docker-minio +docker-minio: docker-services + -@mkdir -p artifacts/minio/data + -docker service rm minio + docker service create \ + --name minio \ + --constraint "node.role==manager" \ + --mount "type=bind,target=/data,source=$(shell pwd)/artifacts/minio/data" \ + --network public \ + --env "MINIO_ACCESS_KEY=TESTACCESSKEY" \ + --env "MINIO_SECRET_KEY=TESTSECRETKEY" \ + --publish "9000:9000/tcp" \ + minio/minio:latest \ + server /data + docker service update \ + --env-add "MINIO_ACCESS_KEY=TESTACCESSKEY" \ + --env-add "MINIO_SECRET_KEY=TESTSECRETKEY" \ + honeycomb + MINIFY := artifacts/minify/bin/minify $(MINIFY): -@mkdir -p "$(@D)" diff --git a/cmd/config.go b/cmd/config.go index 5a8ba57..72190fd 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -28,6 +28,16 @@ type certificateConfig struct { ServerCertificate string ServerKey string CABundles []string + Minio minioStoreConfig +} + +type minioStoreConfig struct { + Endpoint string + Region string + BucketName string + AccessKeyID string + SecretAccessKey string + SSL bool } // GetConfigFromEnvironment creates Config object based on the shell environment. @@ -46,6 +56,14 @@ func GetConfigFromEnvironment() *Config { env("CA_PATH", "/app/etc/ca-bundle.pem,/run/secrets/ca-bundle.pem"), ",", ), + Minio: minioStoreConfig{ + Endpoint: env("MINIO_ENDPOINT", "minio:9000"), + Region: env("MINIO_REGION", "us-east-1"), + BucketName: env("MINIO_BUCKETNAME", "honeycomb"), + AccessKeyID: env("MINIO_ACCESS_KEY", ""), + SecretAccessKey: env("MINIO_SECRET_KEY", ""), + SSL: envBool("MINIO_SSL", false), + }, }, ProxyProtocol: envBool("PROXY_PROTOCOL", false), CheckTimeout: envDuration("CHECK_TIMEOUT", 500*time.Millisecond), diff --git a/cmd/honeycomb/main.go b/cmd/honeycomb/main.go index 3c4d61e..f0ff24a 100644 --- a/cmd/honeycomb/main.go +++ b/cmd/honeycomb/main.go @@ -79,8 +79,14 @@ func main() { logger.Fatalln(err) } + primaryCertProvider := cert.MultiProvider{ + primaryFileCertificateProvider(config, logger), + } + if config.Certificates.Minio.AccessKeyID != "" { + primaryCertProvider = append(primaryCertProvider, primaryMinioCertificateProvider(config, logger)) + } providerAdaptor := &cert.ProviderAdaptor{ - PrimaryProvider: primaryCertificateProvider(config, logger), + PrimaryProvider: primaryCertProvider, SecondaryProvider: secondaryCertProvider, } @@ -213,7 +219,27 @@ func loadDefaultCertificate(config *cmd.Config) (*tls.Certificate, error) { return &cert, err } -func primaryCertificateProvider( +func primaryMinioCertificateProvider( + config *cmd.Config, + logger *log.Logger, +) cert.Provider { + mc, err := cert.NewMinioProvider( + logger, + config.Certificates.Minio.Endpoint, + config.Certificates.Minio.Region, + config.Certificates.Minio.BucketName, + config.Certificates.Minio.AccessKeyID, + config.Certificates.Minio.SecretAccessKey, + config.Certificates.Minio.SSL, + ) + if err != nil { + logger.Fatalln(err) + } + + return mc +} + +func primaryFileCertificateProvider( config *cmd.Config, logger *log.Logger, ) cert.Provider { diff --git a/frontend/cert/adhoc_provider.go b/frontend/cert/adhoc_provider.go index af9317f..ff5c31d 100644 --- a/frontend/cert/adhoc_provider.go +++ b/frontend/cert/adhoc_provider.go @@ -44,6 +44,7 @@ func (provider *AdhocProvider) GetExistingCertificate( serverName name.ServerName, ) (*tls.Certificate, error) { cache, _ := provider.cache.Load().(certificateCache) + return provider.fetch(cache, serverName), nil } diff --git a/frontend/cert/file_provider.go b/frontend/cert/file_provider.go index 7dc1b51..6be20e9 100644 --- a/frontend/cert/file_provider.go +++ b/frontend/cert/file_provider.go @@ -4,7 +4,7 @@ import ( "context" "crypto/tls" "crypto/x509" - "errors" + "fmt" "log" "os" "path" @@ -15,8 +15,10 @@ import ( "github.com/icecave/honeycomb/name" ) -const certExtension = ".crt" -const keyExtension = ".key" +const ( + certExtension = ".crt" + keyExtension = ".key" +) // FileProvider a certificate provider that reads certificates from a loader. type FileProvider struct { @@ -37,7 +39,7 @@ func (p *FileProvider) GetCertificate(ctx context.Context, n name.ServerName) (* return cert, err } - return nil, errors.New("file provider can not generated certificates") + return nil, fmt.Errorf("file %w", ErrProviderGenerateUnsupported) } // GetExistingCertificate attempts to fetch an existing certificate for the @@ -50,7 +52,7 @@ func (p *FileProvider) GetExistingCertificate(ctx context.Context, n name.Server } for _, filename := range p.resolveFilenames(n) { - cert, err := p.loadCertificate(ctx, n, filename) + cert, err := p.loadCertificate(n, filename) if cert != nil || err != nil { return cert, err } @@ -60,7 +62,6 @@ func (p *FileProvider) GetExistingCertificate(ctx context.Context, n name.Server } func (p *FileProvider) loadCertificate( - ctx context.Context, n name.ServerName, filename string, ) (*tls.Certificate, error) { diff --git a/frontend/cert/minio_provider.go b/frontend/cert/minio_provider.go new file mode 100644 index 0000000..fa35469 --- /dev/null +++ b/frontend/cert/minio_provider.go @@ -0,0 +1,217 @@ +package cert + +import ( + "bytes" + "context" + "crypto/tls" + "errors" + "fmt" + "io" + "log" + "strings" + "sync" + "time" + + "github.com/icecave/honeycomb/name" + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" +) + +// MinioProvider a certificate provider that reads certificates from a loader. +type MinioProvider struct { + Client *minio.Client + BucketName string + CacheAge time.Duration + Logger *log.Logger + + mutex sync.RWMutex + cache map[string]*minioCacheItem +} + +type minioCacheItem struct { + Certificate *tls.Certificate + LastSeen time.Time +} + +// errMinioCertNotFound is returned when the certificate is not found or not correctly formed in minio. +var errMinioCertNotFound = errors.New("certificate not found") + +// NewMinioProvider returns a MinioProvider preconfigured and setup for use as a Provider. +func NewMinioProvider( + logger *log.Logger, + endpoint, region, bucketName, accessKeyID, secretAccessKey string, + useSSL bool, +) (Provider, error) { + c, err := minio.New(endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), + Secure: useSSL, + Region: region, + }) + if err != nil { + return nil, err + } + + if ok, err := c.BucketExists(context.Background(), bucketName); err == nil && !ok { + err := c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{ + Region: region, + }) + if err != nil { + return nil, err + } + } + + return &MinioProvider{ + Client: c, + BucketName: bucketName, + Logger: logger, + }, err +} + +// GetCertificate attempts to fetch an existing certificate for the given +// server name. If no such certificate exists, it generates one. +func (p *MinioProvider) GetCertificate(ctx context.Context, n name.ServerName) (*tls.Certificate, error) { + cert, err := p.GetExistingCertificate(ctx, n) + if err != nil { + return nil, err + } else if cert != nil { + return cert, err + } + + return nil, fmt.Errorf("minio %w", ErrProviderGenerateUnsupported) +} + +// GetExistingCertificate attempts to fetch an existing certificate for the +// given server name. It never generates new certificates. A non-nil error +// indicates an error with the provider itself; otherwise, a nil certificate +// indicates a failure to find an existing certificate. +func (p *MinioProvider) GetExistingCertificate(ctx context.Context, n name.ServerName) (*tls.Certificate, error) { + // If cache has not expired, attempt to find in cache. + if !p.expiredInCache(n) { + if cert, ok := p.findInCache(n); ok { + return cert, nil + } + } + + for _, objectName := range p.resolveObjectNames(n) { + // No cache (or expired), attempt to look up in redis + // but if redis is down or broken suddenly, we should reuse the + // cached certificate until it's replaced. + if cert, err := p.getMinioCertificate(ctx, objectName); err == nil { + p.writeToCache(n, cert) + + return cert, nil + } + } + + // fail through to getting it from the cache. + if cert, ok := p.findInCache(n); ok { + p.Logger.Printf("expired but falling through to cache for %s", n.Unicode) + + return cert, nil + } + + // and finally we just fail. + return nil, nil +} + +func (p *MinioProvider) getMinioObject(ctx context.Context, objectName string) (*minio.Object, error) { + return p.Client.GetObject( + ctx, + p.BucketName, + fmt.Sprintf("%s.crt", objectName), + minio.GetObjectOptions{}, + ) +} + +func (p *MinioProvider) getMinioCertificate(ctx context.Context, objectName string) (*tls.Certificate, error) { + var ( + certObj, keyObj *minio.Object + err error + ) + + if certObj, err = p.getMinioObject(ctx, fmt.Sprintf("%s.crt", objectName)); err != nil { + return nil, err + } + + if keyObj, err = p.getMinioObject(ctx, fmt.Sprintf("%s.key", objectName)); err != nil { + return nil, err + } + + certBuf := bytes.NewBuffer(nil) + if _, err = io.Copy(certBuf, certObj); err != nil { + return nil, err + } + + keyBuf := bytes.NewBuffer(nil) + if _, err = io.Copy(keyBuf, keyObj); err != nil { + return nil, err + } + + if cert, err := tls.X509KeyPair(certBuf.Bytes(), keyBuf.Bytes()); err == nil { + return &cert, nil + } + + return nil, errMinioCertNotFound +} + +func (p *MinioProvider) resolveObjectNames( + n name.ServerName, +) (filenames []string) { + tail := n.Punycode + filenames = []string{tail} + + for { + parts := strings.SplitN(tail, ".", 2) + if len(parts) == 1 { + return + } + + tail = parts[1] + filenames = append(filenames, "_."+tail, tail) + } +} + +func (p *MinioProvider) expiredInCache(n name.ServerName) bool { + p.mutex.RLock() + defer p.mutex.RUnlock() + + if item, ok := p.cache[n.Unicode]; ok { + if p.CacheAge > 0 && item.LastSeen.Before(time.Now().Add(-1*p.CacheAge)) { + return true + } + } + + return false +} + +func (p *MinioProvider) findInCache( + n name.ServerName, +) (*tls.Certificate, bool) { + p.mutex.RLock() + defer p.mutex.RUnlock() + + if item, ok := p.cache[n.Unicode]; ok { + return item.Certificate, ok + } + + return nil, false +} + +func (p *MinioProvider) writeToCache( + n name.ServerName, + cert *tls.Certificate, +) { + p.mutex.Lock() + defer p.mutex.Unlock() + + if p.cache == nil { + p.cache = map[string]*minioCacheItem{} + } + + item := &minioCacheItem{ + Certificate: cert, + LastSeen: time.Now(), + } + + p.cache[n.Unicode] = item +} diff --git a/frontend/cert/multi_provider.go b/frontend/cert/multi_provider.go new file mode 100644 index 0000000..77efc11 --- /dev/null +++ b/frontend/cert/multi_provider.go @@ -0,0 +1,41 @@ +package cert + +import ( + "context" + "crypto/tls" + + "github.com/icecave/honeycomb/name" +) + +// MultiProvider is a provider that combines a slice of providers sequentially. +type MultiProvider []Provider + +// GetCertificate attempts to fetch an existing certificate for the given +// server name. If no such certificate exists, it generates one. +func (m MultiProvider) GetCertificate(ctx context.Context, n name.ServerName) (*tls.Certificate, error) { + for _, p := range m { + // Look for an existing certificate from the provider. + if certificate, err := p.GetCertificate(ctx, n); certificate != nil || err != nil { + return certificate, err + } + } + + // finally return nil, nil + return nil, nil +} + +// GetExistingCertificate attempts to fetch an existing certificate for the +// given server name. It never generates new certificates. A non-nil error +// indicates an error with the provider itself; otherwise, a nil certificate +// indicates a failure to find an existing certificate. +func (m MultiProvider) GetExistingCertificate(ctx context.Context, n name.ServerName) (*tls.Certificate, error) { + for _, p := range m { + // Look for an existing certificate from the provider. + if certificate, err := p.GetExistingCertificate(ctx, n); certificate != nil || err != nil { + return certificate, err + } + } + + // finally return nil, nil + return nil, nil +} diff --git a/frontend/cert/multi_provider_test.go b/frontend/cert/multi_provider_test.go new file mode 100644 index 0000000..0930edc --- /dev/null +++ b/frontend/cert/multi_provider_test.go @@ -0,0 +1,169 @@ +package cert_test + +import ( + "context" + "crypto/tls" + "errors" + + "github.com/icecave/honeycomb/frontend/cert" + "github.com/icecave/honeycomb/name" + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var ( + multiTLSCertificatePrimary = &tls.Certificate{ + Certificate: [][]byte{ + []byte("imacert"), + }, + } + multiTLSCertificateSecondary = &tls.Certificate{ + Certificate: [][]byte{ + []byte("imanothercert"), + }, + } +) + +type multiTestType int + +const ( + primaryCertReturn multiTestType = iota + secondaryCertReturn + errorReturn + nilReturn +) + +var errCertError = errors.New("error certificate") + +type mockPrimaryProvider struct{} + +func (m *mockPrimaryProvider) GetCertificate(ctx context.Context, n name.ServerName) (*tls.Certificate, error) { + if n.Unicode == "primary.cert" { + return multiTLSCertificatePrimary, nil + } + + if n.Unicode == "primary.error" { + return nil, errCertError + } + + return nil, nil +} + +func (m *mockPrimaryProvider) GetExistingCertificate(ctx context.Context, n name.ServerName) (*tls.Certificate, error) { + if n.Unicode == "primary.cert" { + return multiTLSCertificatePrimary, nil + } + + if n.Unicode == "primary.error" { + return nil, errCertError + } + + return nil, nil +} + +type mockSecondaryProvider struct{} + +func (m *mockSecondaryProvider) GetCertificate(ctx context.Context, n name.ServerName) (*tls.Certificate, error) { + if n.Unicode == "secondary.cert" { + return multiTLSCertificateSecondary, nil + } + + if n.Unicode == "secondary.error" { + return nil, errCertError + } + + return nil, nil +} + +func (m *mockSecondaryProvider) GetExistingCertificate( + ctx context.Context, + n name.ServerName, +) (*tls.Certificate, error) { + if n.Unicode == "secondary.cert" { + return multiTLSCertificateSecondary, nil + } + + if n.Unicode == "secondary.error" { + return nil, errCertError + } + + return nil, nil +} + +var multiTestList = []TableEntry{ + Entry( + "returns the primary provider certificate", + name.Parse("primary.cert"), + primaryCertReturn, + ), + Entry( + "returns the secondary provider certificate", + name.Parse("secondary.cert"), + secondaryCertReturn, + ), + Entry( + "returns an error from the primary provider", + name.Parse("primary.error"), + errorReturn, + ), + Entry( + "returns an error from the secondary provider", + name.Parse("secondary.error"), + errorReturn, + ), + Entry( + "returns no error or certificate", + name.Parse("somedomain.com"), + nilReturn, + ), +} + +var _ = Describe("MultiProvider", func() { + DescribeTable( + "GetCertificate", + func(n name.ServerName, tt multiTestType) { + p := &cert.MultiProvider{ + &mockPrimaryProvider{}, + &mockSecondaryProvider{}, + } + c, err := p.GetCertificate(context.Background(), n) + switch tt { + case primaryCertReturn: + Expect(c).To(BeEquivalentTo(multiTLSCertificatePrimary)) + Expect(err).NotTo(HaveOccurred()) + case secondaryCertReturn: + Expect(c).To(BeEquivalentTo(multiTLSCertificateSecondary)) + Expect(err).NotTo(HaveOccurred()) + case errorReturn: + Expect(err).To(HaveOccurred()) + case nilReturn: + Expect(err).NotTo(HaveOccurred()) + } + }, + multiTestList..., + ) + DescribeTable( + "GetExistingCertificate", + func(n name.ServerName, tt multiTestType) { + p := &cert.MultiProvider{ + &mockPrimaryProvider{}, + &mockSecondaryProvider{}, + } + c, err := p.GetExistingCertificate(context.Background(), n) + switch tt { + case primaryCertReturn: + Expect(c).To(BeEquivalentTo(multiTLSCertificatePrimary)) + Expect(err).NotTo(HaveOccurred()) + case secondaryCertReturn: + Expect(c).To(BeEquivalentTo(multiTLSCertificateSecondary)) + Expect(err).NotTo(HaveOccurred()) + case errorReturn: + Expect(err).To(HaveOccurred()) + case nilReturn: + Expect(err).NotTo(HaveOccurred()) + } + }, + multiTestList..., + ) +}) diff --git a/frontend/cert/provider.go b/frontend/cert/provider.go index c9de74b..993a878 100644 --- a/frontend/cert/provider.go +++ b/frontend/cert/provider.go @@ -3,6 +3,7 @@ package cert import ( "context" "crypto/tls" + "errors" "github.com/icecave/honeycomb/name" ) @@ -19,3 +20,6 @@ type Provider interface { // indicates a failure to find an existing certificate. GetExistingCertificate(context.Context, name.ServerName) (*tls.Certificate, error) } + +// ErrProviderGenerateUnsupported is returned when providers do not generate new certificates. +var ErrProviderGenerateUnsupported = errors.New("provider can not generate certificates") diff --git a/frontend/cert/provider_adaptor.go b/frontend/cert/provider_adaptor.go index 9d70b35..23d6410 100644 --- a/frontend/cert/provider_adaptor.go +++ b/frontend/cert/provider_adaptor.go @@ -72,5 +72,6 @@ func (adaptor *ProviderAdaptor) context() (context.Context, context.CancelFunc) if timeout == 0 { timeout = DefaultTimeout } + return context.WithTimeout(context.Background(), timeout) } diff --git a/go.mod b/go.mod index 8075162..b77a790 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/gogo/protobuf v1.2.2-0.20190306082329-c5a62797aee0 // indirect github.com/golang/gddo v0.0.0-20181116215533-9bd4a3295021 github.com/gorilla/mux v1.7.3 // indirect + github.com/minio/minio-go/v7 v7.0.5 github.com/morikuni/aec v1.0.0 // indirect github.com/onsi/ginkgo v1.9.1-0.20190828165658-66915d68818e github.com/onsi/gomega v1.7.0 @@ -26,7 +27,7 @@ require ( github.com/sirupsen/logrus v1.4.2 // indirect go.uber.org/atomic v1.4.0 // indirect go.uber.org/multierr v1.1.0 - golang.org/x/net v0.0.0-20190311183353-d8887717615a + golang.org/x/net v0.0.0-20200707034311-ab3426394381 golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0 // indirect google.golang.org/grpc v1.22.2 // indirect gotest.tools v2.2.0+incompatible // indirect diff --git a/go.sum b/go.sum index c290d56..a11ba90 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,7 @@ github.com/Microsoft/go-winio v0.4.13-0.20190312221528-4de24ed3e8c5/go.mod h1:Vh github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/containerd/containerd v1.3.0 h1:xjvXQWABwS2uiv3TWgQt5Uth60Gu86LTGZXMJkjc7rY= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/docker/distribution v2.6.0-rc.1.0.20171011171712-7484e51bf6af+incompatible h1:32oCUFZJu1F3YxBMijYtDZZqpeZVltPCbWTY+ZbcQyU= @@ -31,14 +32,43 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +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 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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 v1.0.0 h1:ooSujki+Z1PRGZsYffJw5jnF5eMBvzMVV86TLAlM0UM= +github.com/minio/minio-go v3.0.2+incompatible h1:TgjYEriSwSpcawLVPw4LpkENIJdqqtVwkyMEnNapZ8w= +github.com/minio/minio-go/v7 v7.0.5 h1:I2NIJ2ojwJqD/YByemC1M59e1b4FW9kS7NlOar7HPV4= +github.com/minio/minio-go/v7 v7.0.5/go.mod h1:TA0CQCjJZHM5SJj9IjqR0NmpmQJ6bCbXifAJ3mUU6Hw= +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/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +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 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -56,20 +86,33 @@ github.com/pkg/errors v0.8.2-0.20190227000051-27936f6d90f9 h1:PCj9X21C4pet4sEcEl github.com/pkg/errors v0.8.2-0.20190227000051-27936f6d90f9/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= +golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= @@ -77,14 +120,22 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0 h1:xQwXv67TxFo9nC1GJFyab5eq/5B590r6RlnL/G8Sz7w= golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= @@ -93,12 +144,18 @@ google.golang.org/grpc v1.22.2 h1:isruki0DBfLFkl6UDkykCh6U/77y1sX6jcHp6MG6phs= google.golang.org/grpc v1.22.2/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=