From f520aa1f4d18a65919c22240a4d0352022d6ca1b Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Tue, 30 May 2017 16:12:24 -0700 Subject: Support AWS Signature V2 for Mattermost for S3 storage. (#6462) Certain S3 compatible servers only use Legacy Signature (AWS Signature V2), current code only supports signature v4. This PR adds facility to click a button on the UI to enable legacy signature with S3 compatible servers. --- api/file_test.go | 10 +++++++++- api/user_test.go | 8 ++++---- api4/apitestlib.go | 10 +++++++++- app/diagnostics.go | 1 + app/file.go | 19 +++++++++++++++--- config/config.json | 3 ++- model/config.go | 6 ++++++ .../components/admin_console/storage_settings.jsx | 23 +++++++++++++++++++++- 8 files changed, 69 insertions(+), 11 deletions(-) diff --git a/api/file_test.go b/api/file_test.go index 59adc8a71..dfe561e50 100644 --- a/api/file_test.go +++ b/api/file_test.go @@ -820,13 +820,21 @@ func readTestFile(name string) ([]byte, error) { } } +func s3New(endpoint, accessKey, secretKey string, secure bool, signV2 bool) (*s3.Client, error) { + if signV2 { + return s3.NewV2(endpoint, accessKey, secretKey, secure) + } + return s3.NewV4(endpoint, accessKey, secretKey, secure) +} + func cleanupTestFile(info *model.FileInfo) error { if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 { endpoint := utils.Cfg.FileSettings.AmazonS3Endpoint accessKey := utils.Cfg.FileSettings.AmazonS3AccessKeyId secretKey := utils.Cfg.FileSettings.AmazonS3SecretAccessKey secure := *utils.Cfg.FileSettings.AmazonS3SSL - s3Clnt, err := s3.New(endpoint, accessKey, secretKey, secure) + signV2 := *utils.Cfg.FileSettings.AmazonS3SignV2 + s3Clnt, err := s3New(endpoint, accessKey, secretKey, secure, signV2) if err != nil { return err } diff --git a/api/user_test.go b/api/user_test.go index 484cf6495..49c031923 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -20,8 +20,6 @@ import ( "github.com/mattermost/platform/model" "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" - - s3 "github.com/minio/minio-go" ) func TestCreateUser(t *testing.T) { @@ -696,7 +694,8 @@ func TestUserCreateImage(t *testing.T) { accessKey := utils.Cfg.FileSettings.AmazonS3AccessKeyId secretKey := utils.Cfg.FileSettings.AmazonS3SecretAccessKey secure := *utils.Cfg.FileSettings.AmazonS3SSL - s3Clnt, err := s3.New(endpoint, accessKey, secretKey, secure) + signV2 := *utils.Cfg.FileSettings.AmazonS3SignV2 + s3Clnt, err := s3New(endpoint, accessKey, secretKey, secure, signV2) if err != nil { t.Fatal(err) } @@ -800,7 +799,8 @@ func TestUserUploadProfileImage(t *testing.T) { accessKey := utils.Cfg.FileSettings.AmazonS3AccessKeyId secretKey := utils.Cfg.FileSettings.AmazonS3SecretAccessKey secure := *utils.Cfg.FileSettings.AmazonS3SSL - s3Clnt, err := s3.New(endpoint, accessKey, secretKey, secure) + signV2 := *utils.Cfg.FileSettings.AmazonS3SignV2 + s3Clnt, err := s3New(endpoint, accessKey, secretKey, secure, signV2) if err != nil { t.Fatal(err) } diff --git a/api4/apitestlib.go b/api4/apitestlib.go index 7075488cb..ef200bf2f 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -626,13 +626,21 @@ func readTestFile(name string) ([]byte, error) { } } +func s3New(endpoint, accessKey, secretKey string, secure bool, signV2 bool) (*s3.Client, error) { + if signV2 { + return s3.NewV2(endpoint, accessKey, secretKey, secure) + } + return s3.NewV4(endpoint, accessKey, secretKey, secure) +} + func cleanupTestFile(info *model.FileInfo) error { if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 { endpoint := utils.Cfg.FileSettings.AmazonS3Endpoint accessKey := utils.Cfg.FileSettings.AmazonS3AccessKeyId secretKey := utils.Cfg.FileSettings.AmazonS3SecretAccessKey secure := *utils.Cfg.FileSettings.AmazonS3SSL - s3Clnt, err := s3.New(endpoint, accessKey, secretKey, secure) + signV2 := *utils.Cfg.FileSettings.AmazonS3SignV2 + s3Clnt, err := s3New(endpoint, accessKey, secretKey, secure, signV2) if err != nil { return err } diff --git a/app/diagnostics.go b/app/diagnostics.go index 295164c97..a80b747ef 100644 --- a/app/diagnostics.go +++ b/app/diagnostics.go @@ -244,6 +244,7 @@ func trackConfig() { "enable_public_links": utils.Cfg.FileSettings.EnablePublicLink, "driver_name": utils.Cfg.FileSettings.DriverName, "amazon_s3_ssl": *utils.Cfg.FileSettings.AmazonS3SSL, + "amazon_s3_signv2": *utils.Cfg.FileSettings.AmazonS3SignV2, "thumbnail_width": utils.Cfg.FileSettings.ThumbnailWidth, "thumbnail_height": utils.Cfg.FileSettings.ThumbnailHeight, "preview_width": utils.Cfg.FileSettings.PreviewWidth, diff --git a/app/file.go b/app/file.go index 3b7a6860c..f46dd50ec 100644 --- a/app/file.go +++ b/app/file.go @@ -56,13 +56,23 @@ const ( MaxImageSize = 6048 * 4032 // 24 megapixels, roughly 36MB as a raw image ) +// Similar to s3.New() but allows initialization of signature v2 or signature v4 client. +// If signV2 input is false, function always returns signature v4. +func s3New(endpoint, accessKey, secretKey string, secure bool, signV2 bool) (*s3.Client, error) { + if signV2 { + return s3.NewV2(endpoint, accessKey, secretKey, secure) + } + return s3.NewV4(endpoint, accessKey, secretKey, secure) +} + func ReadFile(path string) ([]byte, *model.AppError) { if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 { endpoint := utils.Cfg.FileSettings.AmazonS3Endpoint accessKey := utils.Cfg.FileSettings.AmazonS3AccessKeyId secretKey := utils.Cfg.FileSettings.AmazonS3SecretAccessKey secure := *utils.Cfg.FileSettings.AmazonS3SSL - s3Clnt, err := s3.New(endpoint, accessKey, secretKey, secure) + signV2 := *utils.Cfg.FileSettings.AmazonS3SignV2 + s3Clnt, err := s3New(endpoint, accessKey, secretKey, secure, signV2) if err != nil { return nil, model.NewLocAppError("ReadFile", "api.file.read_file.s3.app_error", nil, err.Error()) } @@ -94,7 +104,8 @@ func MoveFile(oldPath, newPath string) *model.AppError { accessKey := utils.Cfg.FileSettings.AmazonS3AccessKeyId secretKey := utils.Cfg.FileSettings.AmazonS3SecretAccessKey secure := *utils.Cfg.FileSettings.AmazonS3SSL - s3Clnt, err := s3.New(endpoint, accessKey, secretKey, secure) + signV2 := *utils.Cfg.FileSettings.AmazonS3SignV2 + s3Clnt, err := s3New(endpoint, accessKey, secretKey, secure, signV2) if err != nil { return model.NewLocAppError("moveFile", "api.file.write_file.s3.app_error", nil, err.Error()) } @@ -128,10 +139,12 @@ func WriteFile(f []byte, path string) *model.AppError { accessKey := utils.Cfg.FileSettings.AmazonS3AccessKeyId secretKey := utils.Cfg.FileSettings.AmazonS3SecretAccessKey secure := *utils.Cfg.FileSettings.AmazonS3SSL - s3Clnt, err := s3.New(endpoint, accessKey, secretKey, secure) + signV2 := *utils.Cfg.FileSettings.AmazonS3SignV2 + s3Clnt, err := s3New(endpoint, accessKey, secretKey, secure, signV2) if err != nil { return model.NewLocAppError("WriteFile", "api.file.write_file.s3.app_error", nil, err.Error()) } + bucket := utils.Cfg.FileSettings.AmazonS3Bucket ext := filepath.Ext(path) diff --git a/config/config.json b/config/config.json index 6460f661b..fe47c20d8 100644 --- a/config/config.json +++ b/config/config.json @@ -124,7 +124,8 @@ "AmazonS3Bucket": "", "AmazonS3Region": "us-east-1", "AmazonS3Endpoint": "s3.amazonaws.com", - "AmazonS3SSL": true + "AmazonS3SSL": true, + "AmazonS3SignV2": false }, "EmailSettings": { "EnableSignUpWithEmail": true, diff --git a/model/config.go b/model/config.go index 311a34610..b5ba7845f 100644 --- a/model/config.go +++ b/model/config.go @@ -238,6 +238,7 @@ type FileSettings struct { AmazonS3Region string AmazonS3Endpoint string AmazonS3SSL *bool + AmazonS3SignV2 *bool } type EmailSettings struct { @@ -502,6 +503,11 @@ func (o *Config) SetDefaults() { *o.FileSettings.AmazonS3SSL = true // Secure by default. } + if o.FileSettings.AmazonS3SignV2 == nil { + o.FileSettings.AmazonS3SignV2 = new(bool) + // Signature v2 is not enabled by default. + } + if o.FileSettings.EnableFileAttachments == nil { o.FileSettings.EnableFileAttachments = new(bool) *o.FileSettings.EnableFileAttachments = true diff --git a/webapp/components/admin_console/storage_settings.jsx b/webapp/components/admin_console/storage_settings.jsx index 1400b673c..74c53770a 100644 --- a/webapp/components/admin_console/storage_settings.jsx +++ b/webapp/components/admin_console/storage_settings.jsx @@ -34,6 +34,7 @@ export default class StorageSettings extends AdminSettings { config.FileSettings.AmazonS3Bucket = this.state.amazonS3Bucket; config.FileSettings.AmazonS3Endpoint = this.state.amazonS3Endpoint; config.FileSettings.AmazonS3SSL = this.state.amazonS3SSL; + config.FileSettings.AmazonS3SignV2 = this.state.amazonS3SignV2; return config; } @@ -48,7 +49,8 @@ export default class StorageSettings extends AdminSettings { amazonS3SecretAccessKey: config.FileSettings.AmazonS3SecretAccessKey, amazonS3Bucket: config.FileSettings.AmazonS3Bucket, amazonS3Endpoint: config.FileSettings.AmazonS3Endpoint, - amazonS3SSL: config.FileSettings.AmazonS3SSL + amazonS3SSL: config.FileSettings.AmazonS3SSL, + amazonS3SignV2: config.FileSettings.AmazonS3SignV2 }; } @@ -201,6 +203,25 @@ export default class StorageSettings extends AdminSettings { onChange={this.handleChange} disabled={this.state.driverName !== DRIVER_S3} /> + + } + placeholder={Utils.localizeMessage('admin.image.amazonS3SignV2Example', 'Ex "false"')} + helpText={ + + } + value={this.state.amazonS3SignV2} + onChange={this.handleChange} + disabled={this.state.driverName !== DRIVER_S3} + />