From 6e024c45b50d31c20eb0d509263d3e0f888847de Mon Sep 17 00:00:00 2001 From: Carlos Tadeu Panato Junior Date: Thu, 1 Mar 2018 00:12:11 +0100 Subject: [PLT-8186] add support for ec2 instance profile authentication (#8243) --- api4/system.go | 31 +++++++++++++++++++++ api4/system_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++ i18n/en.json | 12 ++++++++ model/client4.go | 14 ++++++++++ utils/file_backend_s3.go | 21 +++++++++++++- utils/file_backend_s3_test.go | 32 ++++++++++++++++++++++ 6 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 utils/file_backend_s3_test.go diff --git a/api4/system.go b/api4/system.go index 2355cb476..aab65bf20 100644 --- a/api4/system.go +++ b/api4/system.go @@ -29,6 +29,7 @@ func (api *API) InitSystem() { api.BaseRoutes.ApiRoot.Handle("/audits", api.ApiSessionRequired(getAudits)).Methods("GET") api.BaseRoutes.ApiRoot.Handle("/email/test", api.ApiSessionRequired(testEmail)).Methods("POST") + api.BaseRoutes.ApiRoot.Handle("/file/s3_test", api.ApiSessionRequired(testS3)).Methods("POST") api.BaseRoutes.ApiRoot.Handle("/database/recycle", api.ApiSessionRequired(databaseRecycle)).Methods("POST") api.BaseRoutes.ApiRoot.Handle("/caches/invalidate", api.ApiSessionRequired(invalidateCaches)).Methods("POST") @@ -384,3 +385,33 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(rows.ToJson())) } + +func testS3(c *Context, w http.ResponseWriter, r *http.Request) { + cfg := model.ConfigFromJson(r.Body) + if cfg == nil { + cfg = c.App.Config() + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + err := utils.CheckMandatoryS3Fields(&cfg.FileSettings) + if err != nil { + c.Err = err + return + } + + license := c.App.License() + backend, appErr := utils.NewFileBackend(&cfg.FileSettings, license != nil && *license.Features.Compliance) + if appErr == nil { + appErr = backend.TestConnection() + } + if appErr != nil { + c.Err = appErr + return + } + + ReturnStatusOK(w) +} diff --git a/api4/system_test.go b/api4/system_test.go index 01b4934ae..e39486b77 100644 --- a/api4/system_test.go +++ b/api4/system_test.go @@ -1,7 +1,9 @@ package api4 import ( + "fmt" "net/http" + "os" "strings" "testing" @@ -466,3 +468,65 @@ func TestGetAnalyticsOld(t *testing.T) { _, resp = Client.GetAnalyticsOld("", th.BasicTeam.Id) CheckUnauthorizedStatus(t, resp) } + +func TestS3TestConnection(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + Client := th.Client + + s3Host := os.Getenv("CI_HOST") + if s3Host == "" { + s3Host = "dockerhost" + } + + s3Port := os.Getenv("CI_MINIO_PORT") + if s3Port == "" { + s3Port = "9001" + } + + s3Endpoint := fmt.Sprintf("%s:%s", s3Host, s3Port) + config := model.Config{ + FileSettings: model.FileSettings{ + DriverName: model.NewString(model.IMAGE_DRIVER_S3), + AmazonS3AccessKeyId: model.MINIO_ACCESS_KEY, + AmazonS3SecretAccessKey: model.MINIO_SECRET_KEY, + AmazonS3Bucket: "", + AmazonS3Endpoint: "", + AmazonS3SSL: model.NewBool(false), + }, + } + + _, resp := Client.TestS3Connection(&config) + CheckForbiddenStatus(t, resp) + + _, resp = th.SystemAdminClient.TestS3Connection(&config) + CheckBadRequestStatus(t, resp) + if resp.Error.Message != "S3 Bucket is required" { + t.Fatal("should return error - missing s3 bucket") + } + + config.FileSettings.AmazonS3Bucket = model.MINIO_BUCKET + _, resp = th.SystemAdminClient.TestS3Connection(&config) + CheckBadRequestStatus(t, resp) + if resp.Error.Message != "S3 Endpoint is required" { + t.Fatal("should return error - missing s3 endpoint") + } + + config.FileSettings.AmazonS3Endpoint = s3Endpoint + _, resp = th.SystemAdminClient.TestS3Connection(&config) + CheckBadRequestStatus(t, resp) + if resp.Error.Message != "S3 Region is required" { + t.Fatal("should return error - missing s3 region") + } + + config.FileSettings.AmazonS3Region = "us-east-1" + _, resp = th.SystemAdminClient.TestS3Connection(&config) + CheckOKStatus(t, resp) + + config.FileSettings.AmazonS3Bucket = "Wrong_bucket" + _, resp = th.SystemAdminClient.TestS3Connection(&config) + CheckInternalErrorStatus(t, resp) + if resp.Error.Message != "Error checking if bucket exists." { + t.Fatal("should return error ") + } +} diff --git a/i18n/en.json b/i18n/en.json index 37639ba60..1e4ac9012 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -107,6 +107,18 @@ "id": "api.admin.test_email.subject", "translation": "Mattermost - Testing Email Settings" }, + { + "id": "api.admin.test_s3.missing_s3_bucket", + "translation": "S3 Bucket is required" + }, + { + "id": "api.admin.test_s3.missing_s3_region", + "translation": "S3 Region is required" + }, + { + "id": "api.admin.test_s3.missing_s3_endpoint", + "translation": "S3 Endpoint is required" + }, { "id": "api.admin.upload_brand_image.array.app_error", "translation": "Empty array under 'image' in request" diff --git a/model/client4.go b/model/client4.go index 4772d38b3..c1587f882 100644 --- a/model/client4.go +++ b/model/client4.go @@ -198,6 +198,10 @@ func (c *Client4) GetTestEmailRoute() string { return fmt.Sprintf("/email/test") } +func (c *Client4) GetTestS3Route() string { + return fmt.Sprintf("/file/s3_test") +} + func (c *Client4) GetDatabaseRoute() string { return fmt.Sprintf("/database") } @@ -2092,6 +2096,16 @@ func (c *Client4) TestEmail() (bool, *Response) { } } +// TestS3Connection will attempt to connect to the AWS S3. +func (c *Client4) TestS3Connection(config *Config) (bool, *Response) { + if r, err := c.DoApiPost(c.GetTestS3Route(), config.ToJson()); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + // GetConfig will retrieve the server config with some sanitized items. func (c *Client4) GetConfig() (*Config, *Response) { if r, err := c.DoApiGet(c.GetConfigRoute(), ""); err != nil { diff --git a/utils/file_backend_s3.go b/utils/file_backend_s3.go index 8e72272a1..b0601bc8a 100644 --- a/utils/file_backend_s3.go +++ b/utils/file_backend_s3.go @@ -37,7 +37,10 @@ type S3FileBackend struct { // disables automatic region lookup. func (b *S3FileBackend) s3New() (*s3.Client, error) { var creds *credentials.Credentials - if b.signV2 { + + if b.accessKey == "" && b.secretKey == "" { + creds = credentials.NewIAM("") + } else if b.signV2 { creds = credentials.NewStatic(b.accessKey, b.secretKey, "", credentials.SignatureV2) } else { creds = credentials.NewStatic(b.accessKey, b.secretKey, "", credentials.SignatureV4) @@ -244,3 +247,19 @@ func s3CopyMetadata(encrypt bool) map[string]string { metaData["x-amz-server-side-encryption"] = "AES256" return metaData } + +func CheckMandatoryS3Fields(settings *model.FileSettings) *model.AppError { + if len(settings.AmazonS3Bucket) == 0 { + return model.NewAppError("S3File", "api.admin.test_s3.missing_s3_bucket", nil, "", http.StatusBadRequest) + } + + if len(settings.AmazonS3Endpoint) == 0 { + return model.NewAppError("S3File", "api.admin.test_s3.missing_s3_endpoint", nil, "", http.StatusBadRequest) + } + + if len(settings.AmazonS3Region) == 0 { + return model.NewAppError("S3File", "api.admin.test_s3.missing_s3_region", nil, "", http.StatusBadRequest) + } + + return nil +} diff --git a/utils/file_backend_s3_test.go b/utils/file_backend_s3_test.go new file mode 100644 index 000000000..ff42a4d19 --- /dev/null +++ b/utils/file_backend_s3_test.go @@ -0,0 +1,32 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package utils + +import ( + "testing" + + "github.com/mattermost/mattermost-server/model" +) + +func TestCheckMandatoryS3Fields(t *testing.T) { + cfg := model.FileSettings{} + + err := CheckMandatoryS3Fields(&cfg) + if err == nil || err.Message != "api.admin.test_s3.missing_s3_bucket" { + t.Fatal("should've failed with missing s3 bucket") + } + + cfg.AmazonS3Bucket = "test-mm" + err = CheckMandatoryS3Fields(&cfg) + if err == nil || err.Message != "api.admin.test_s3.missing_s3_endpoint" { + t.Fatal("should've failed with missing s3 endpoint") + } + + cfg.AmazonS3Endpoint = "s3.newendpoint.com" + err = CheckMandatoryS3Fields(&cfg) + if err == nil || err.Message != "api.admin.test_s3.missing_s3_region" { + t.Fatal("should've failed with missing s3 region") + } + +} -- cgit v1.2.3-1-g7c22