From 4b57cea7898e8e07f0ce177bfc1dcd92d0a89b32 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Thu, 15 Oct 2020 22:39:23 +0530 Subject: [PATCH] MM-29572: Add Bifrost support to codebase (#15955) * MM-29572: Add Bifrost support to codebase We use a custom transport for minio when an environment variable indicates that Mattermost is running in cloud. The transport is used to redirect the request to the S3 endpoint set in the config. And the scheme is set depending on if S3SSL is set. A custom credentials provider is needed to return empty credentials which will be overridden by the service anyways. This is just to allow the minio client library to work transparently without knowing that there is something else in the middle intercepting requests. https://mattermost.atlassian.net/browse/MM-29572 * incorporate review comments * Update services/filesstore/s3_overrides.go Co-authored-by: Ibrahim Serdar Acikgoz * Change to new env key Co-authored-by: Ibrahim Serdar Acikgoz --- services/filesstore/s3_overrides.go | 56 +++++++++++++++++++++++++++++ services/filesstore/s3store.go | 23 +++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 services/filesstore/s3_overrides.go diff --git a/services/filesstore/s3_overrides.go b/services/filesstore/s3_overrides.go new file mode 100644 index 0000000000..975889fb7a --- /dev/null +++ b/services/filesstore/s3_overrides.go @@ -0,0 +1,56 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package filesstore + +import ( + "context" + "net/http" + + "github.com/minio/minio-go/v7/pkg/credentials" +) + +// customTransport is used to point the request to a different server. +// This is helpful in situations where a different service is handling AWS S3 requests +// from multiple Mattermost applications, and the Mattermost service itself does not +// have any S3 credentials. +type customTransport struct { + base http.RoundTripper + host string + scheme string + client http.Client +} + +// RoundTrip implements the http.Roundtripper interface. +func (t *customTransport) RoundTrip(req *http.Request) (*http.Response, error) { + // Rountrippers should not modify the original request. + newReq := req.Clone(context.Background()) + *newReq.URL = *req.URL + req.URL.Scheme = t.scheme + req.URL.Host = t.host + return t.client.Do(req) +} + +// customProvider is a dummy credentials provider for the minio client to work +// without actually providing credentials. This is needed with a custom transport +// in cases where the minio client does not actually have credentials with itself, +// rather needs responses from another entity. +// +// It satisfies the credentials.Provider interface. +type customProvider struct { + isSignV2 bool +} + +// Retrieve just returns empty credentials. +func (cp customProvider) Retrieve() (credentials.Value, error) { + sign := credentials.SignatureV4 + if cp.isSignV2 { + sign = credentials.SignatureV2 + } + return credentials.Value{ + SignerType: sign, + }, nil +} + +// IsExpired always returns false. +func (cp customProvider) IsExpired() bool { return false } diff --git a/services/filesstore/s3store.go b/services/filesstore/s3store.go index 03f59c8595..0868da5859 100644 --- a/services/filesstore/s3store.go +++ b/services/filesstore/s3store.go @@ -72,7 +72,10 @@ func NewS3FileBackend(settings *model.FileSettings, enableComplianceFeatures boo func (b *S3FileBackend) s3New() (*s3.Client, error) { var creds *credentials.Credentials - if b.accessKey == "" && b.secretKey == "" { + isCloud := os.Getenv("MM_CLOUD_FILESTORE_BIFROST") != "" + if isCloud { + creds = credentials.New(customProvider{isSignV2: b.signV2}) + } else if b.accessKey == "" && b.secretKey == "" { creds = credentials.NewIAM("") } else if b.signV2 { creds = credentials.NewStatic(b.accessKey, b.secretKey, "", credentials.SignatureV2) @@ -85,6 +88,24 @@ func (b *S3FileBackend) s3New() (*s3.Client, error) { Secure: b.secure, Region: b.region, } + + // If this is a cloud installation, we override the default transport. + if isCloud { + tr, err := s3.DefaultTransport(b.secure) + if err != nil { + return nil, err + } + scheme := "http" + if b.secure { + scheme = "https" + } + opts.Transport = &customTransport{ + base: tr, + host: b.endpoint, + scheme: scheme, + } + } + s3Clnt, err := s3.New(b.endpoint, &opts) if err != nil { return nil, err