From 2932e9243868732cc6000598f88bd118f1b28923 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 19 Oct 2021 16:07:39 +0200 Subject: [PATCH 1/7] refactor amqp variables --- start.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/start.go b/start.go index b8088d2..0dc05f2 100644 --- a/start.go +++ b/start.go @@ -115,14 +115,14 @@ func main() { routes.RegisterEndpoints(r, api) // Start AMQP client - AMQPhost, _ := configuration.GlobalConfig.String("amqp.host") - AMQPuser, _ := configuration.GlobalConfig.String("amqp.user") - AMQPpass, _ := configuration.GlobalConfig.String("amqp.pass") + amqpHost, _ := configuration.GlobalConfig.String("amqp.host") + amqpUser, _ := configuration.GlobalConfig.String("amqp.user") + amqpPass, _ := configuration.GlobalConfig.String("amqp.pass") - if AMQPhost != "" { + if amqpHost != "" { // create amqp URL based on username, password and host - amqpurl := "amqp://" + AMQPuser + ":" + AMQPpass + "@" + AMQPhost - err = infrastructure_component.StartAMQP(amqpurl, api) + amqpURL := "amqp://" + amqpUser + ":" + amqpPass + "@" + amqpHost + err = infrastructure_component.StartAMQP(amqpURL, api) if err != nil { log.Fatal(err) } From 140973ab272318888adaadb5df7e40fc1a414c6a Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 19 Oct 2021 16:11:39 +0200 Subject: [PATCH 2/7] refactor database.DBAddAdminUser() to database.AddAdminUser() --- database/admin.go | 95 ++++++++++++++++++++++++++++++++++++++++ database/database.go | 63 -------------------------- routes/user/user_test.go | 18 ++++---- start.go | 2 +- 4 files changed, 105 insertions(+), 73 deletions(-) create mode 100644 database/admin.go diff --git a/database/admin.go b/database/admin.go new file mode 100644 index 0000000..290a6a7 --- /dev/null +++ b/database/admin.go @@ -0,0 +1,95 @@ +/** Package database +* +* @author Sonja Happ +* @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC +* @license GNU General Public License (version 3) +* +* VILLASweb-backend-go +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*********************************************************************************/ +package database + +import ( + "fmt" + "math/rand" + "strings" + "time" + + "github.com/zpatrick/go-config" + "golang.org/x/crypto/bcrypt" +) + +// AddAdminUser adds a default admin user to the DB +func AddAdminUser(cfg *config.Config) (string, error) { + DBpool.AutoMigrate(User{}) + + // Check if admin user exists in DB + var users []User + DBpool.Where("Role = ?", "Admin").Find(&users) + + adminPW := "" + + if len(users) == 0 { + fmt.Println("No admin user found in DB, adding default admin user.") + + adminName, err := cfg.String("admin.user") + if err != nil || adminName == "" { + adminName = "admin" + } + + adminPW, err = cfg.String("admin.pass") + if err != nil || adminPW == "" { + adminPW = generatePassword(16) + fmt.Printf(" Generated admin password: %s for admin user %s\n", adminPW, adminName) + } + + mail, err := cfg.String("admin.mail") + if err == nil || mail == "" { + mail = "admin@example.com" + } + + pwEnc, _ := bcrypt.GenerateFromPassword([]byte(adminPW), 10) + + // create a copy of global test data + user := User{ + Username: adminName, + Password: string(pwEnc), + Role: "Admin", + Mail: mail, + Active: true, + } + + // add admin user to DB + err = DBpool.Create(&user).Error + if err != nil { + return "", err + } + } + return adminPW, nil +} + +func generatePassword(Len int) string { + rand.Seed(time.Now().UnixNano()) + chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "abcdefghijklmnopqrstuvwxyz" + + "0123456789") + + var b strings.Builder + for i := 0; i < Len; i++ { + b.WriteRune(chars[rand.Intn(len(chars))]) + } + + return b.String() +} diff --git a/database/database.go b/database/database.go index e0bc95f..ecff9e3 100644 --- a/database/database.go +++ b/database/database.go @@ -24,11 +24,6 @@ package database import ( "fmt" "log" - "math/rand" - "strings" - "time" - - "golang.org/x/crypto/bcrypt" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/postgres" @@ -123,61 +118,3 @@ func MigrateModels() { DBpool.AutoMigrate(&Widget{}) DBpool.AutoMigrate(&Result{}) } - -// DBAddAdminUser adds a default admin user to the DB -func DBAddAdminUser(cfg *config.Config) (string, error) { - DBpool.AutoMigrate(User{}) - - // Check if admin user exists in DB - var users []User - DBpool.Where("Role = ?", "Admin").Find(&users) - - adminPW := "" - - if len(users) == 0 { - fmt.Println("No admin user found in DB, adding default admin user.") - - adminName, err := cfg.String("admin.user") - if err != nil || adminName == "" { - adminName = "admin" - } - - adminPW, err = cfg.String("admin.pass") - if err != nil || adminPW == "" { - adminPW = generatePassword(16) - fmt.Printf(" Generated admin password: %s for admin user %s\n", adminPW, adminName) - } - - mail, err := cfg.String("admin.mail") - if err == nil || mail == "" { - mail = "admin@example.com" - } - - pwEnc, _ := bcrypt.GenerateFromPassword([]byte(adminPW), 10) - - // create a copy of global test data - user := User{Username: adminName, Password: string(pwEnc), - Role: "Admin", Mail: mail, Active: true} - - // add admin user to DB - err = DBpool.Create(&user).Error - if err != nil { - return "", err - } - } - return adminPW, nil -} - -func generatePassword(Len int) string { - rand.Seed(time.Now().UnixNano()) - chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "abcdefghijklmnopqrstuvwxyz" + - "0123456789") - - var b strings.Builder - for i := 0; i < Len; i++ { - b.WriteRune(chars[rand.Intn(len(chars))]) - } - - return b.String() -} diff --git a/routes/user/user_test.go b/routes/user/user_test.go index 984f084..9309c87 100644 --- a/routes/user/user_test.go +++ b/routes/user/user_test.go @@ -75,7 +75,7 @@ func TestMain(m *testing.M) { func TestAuthenticate(t *testing.T) { database.DropTables() database.MigrateModels() - adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig) + adminpw, err := database.AddAdminUser(configuration.GlobalConfig) assert.NoError(t, err) // try to authenticate with non JSON body @@ -172,7 +172,7 @@ func TestAuthenticateQueryToken(t *testing.T) { database.DropTables() database.MigrateModels() - adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig) + adminpw, err := database.AddAdminUser(configuration.GlobalConfig) assert.NoError(t, err) // authenticate as admin @@ -193,7 +193,7 @@ func TestAddGetUser(t *testing.T) { database.DropTables() database.MigrateModels() - adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig) + adminpw, err := database.AddAdminUser(configuration.GlobalConfig) assert.NoError(t, err) // authenticate as admin @@ -317,7 +317,7 @@ func TestUsersNotAllowedActions(t *testing.T) { database.DropTables() database.MigrateModels() - adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig) + adminpw, err := database.AddAdminUser(configuration.GlobalConfig) assert.NoError(t, err) // authenticate as admin @@ -376,7 +376,7 @@ func TestGetAllUsers(t *testing.T) { database.DropTables() database.MigrateModels() - adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig) + adminpw, err := database.AddAdminUser(configuration.GlobalConfig) assert.NoError(t, err) // authenticate as admin @@ -429,7 +429,7 @@ func TestModifyAddedUserAsUser(t *testing.T) { database.DropTables() database.MigrateModels() - adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig) + adminpw, err := database.AddAdminUser(configuration.GlobalConfig) assert.NoError(t, err) // authenticate as admin @@ -584,7 +584,7 @@ func TestInvalidUserUpdate(t *testing.T) { database.DropTables() database.MigrateModels() - adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig) + adminpw, err := database.AddAdminUser(configuration.GlobalConfig) assert.NoError(t, err) // authenticate as admin @@ -656,7 +656,7 @@ func TestModifyAddedUserAsAdmin(t *testing.T) { database.DropTables() database.MigrateModels() - adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig) + adminpw, err := database.AddAdminUser(configuration.GlobalConfig) assert.NoError(t, err) // authenticate as admin @@ -773,7 +773,7 @@ func TestDeleteUser(t *testing.T) { database.DropTables() database.MigrateModels() - adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig) + adminpw, err := database.AddAdminUser(configuration.GlobalConfig) assert.NoError(t, err) // authenticate as admin diff --git a/start.go b/start.go index 0dc05f2..980a32b 100644 --- a/start.go +++ b/start.go @@ -135,7 +135,7 @@ func main() { } // Make sure that at least one admin user exists in DB - _, err = database.DBAddAdminUser(configuration.GlobalConfig) + _, err = database.AddAdminUser(configuration.GlobalConfig) if err != nil { fmt.Println("error: adding admin user failed:", err.Error()) log.Fatal(err) From e32336490cde47870b876676f166d8f008e2c80c Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 19 Oct 2021 14:08:25 +0200 Subject: [PATCH 3/7] allow update of admin user via CLI args --- database/admin.go | 64 ++++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/database/admin.go b/database/admin.go index 290a6a7..025301b 100644 --- a/database/admin.go +++ b/database/admin.go @@ -35,39 +35,42 @@ import ( func AddAdminUser(cfg *config.Config) (string, error) { DBpool.AutoMigrate(User{}) + updatedPW := false + generatedPW := false + + adminName, _ := cfg.StringOr("admin.user", "admin") + adminMail, _ := cfg.StringOr("admin.mail", "admin@example.com") + adminPW, err := cfg.String("admin.pass") + if err == nil && adminPW != "" { + updatedPW = true + } else if err != nil || adminPW == "" { + adminPW = generatePassword(16) + generatedPW = true + } + + adminPWEnc, err := bcrypt.GenerateFromPassword([]byte(adminPW), 10) + if err != nil { + return "", err + } + // Check if admin user exists in DB var users []User - DBpool.Where("Role = ?", "Admin").Find(&users) - - adminPW := "" + err = DBpool.Where("Username = ?", adminName).Find(&users).Error + if err != nil { + return "", err + } if len(users) == 0 { fmt.Println("No admin user found in DB, adding default admin user.") - - adminName, err := cfg.String("admin.user") - if err != nil || adminName == "" { - adminName = "admin" - } - - adminPW, err = cfg.String("admin.pass") - if err != nil || adminPW == "" { - adminPW = generatePassword(16) + if generatedPW { fmt.Printf(" Generated admin password: %s for admin user %s\n", adminPW, adminName) } - mail, err := cfg.String("admin.mail") - if err == nil || mail == "" { - mail = "admin@example.com" - } - - pwEnc, _ := bcrypt.GenerateFromPassword([]byte(adminPW), 10) - - // create a copy of global test data user := User{ Username: adminName, - Password: string(pwEnc), + Password: string(adminPWEnc), Role: "Admin", - Mail: mail, + Mail: adminMail, Active: true, } @@ -76,8 +79,23 @@ func AddAdminUser(cfg *config.Config) (string, error) { if err != nil { return "", err } + } else if updatedPW { + fmt.Println("Found existing admin user in DB, updating user from CLI parameters.") + + user := users[0] + + user.Password = string(adminPWEnc) + user.Role = "Admin" + user.Mail = adminMail + user.Active = true + + err = DBpool.Model(user).Update(&user).Error + if err != nil { + return "", err + } } - return adminPW, nil + + return adminPW, err } func generatePassword(Len int) string { From 49fe2e6e1f69d390bd17c23f7f75016f704460d1 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 19 Oct 2021 17:38:03 +0200 Subject: [PATCH 4/7] fix healthz endpoint when AMQP is disabled --- routes/healthz/healthz_endpoint.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/routes/healthz/healthz_endpoint.go b/routes/healthz/healthz_endpoint.go index d26c263..6975196 100644 --- a/routes/healthz/healthz_endpoint.go +++ b/routes/healthz/healthz_endpoint.go @@ -54,11 +54,8 @@ func getHealth(c *gin.Context) { } // check if connection to AMQP broker is alive if backend was started with AMQP client - url, err := configuration.GlobalConfig.StringOr("amqp.host", "not-set") - if err != nil && url == "not-set" { - c.JSON(http.StatusOK, gin.H{}) - return - } else if err != nil { + url, err := configuration.GlobalConfig.StringOr("amqp.host", "") + if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "success:": false, "message": err.Error(), @@ -66,7 +63,10 @@ func getHealth(c *gin.Context) { return } - if len(url) != 0 { + if url == "" { + c.Writer.WriteHeader(http.StatusNoContent) + return + } else { err = helper.CheckConnection() if err != nil { log.Println(err.Error()) From 66d6c3f944826adfdf2a2b8e71de7663360ff3e6 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 19 Oct 2021 17:40:01 +0200 Subject: [PATCH 5/7] s3: fix broken presigned urls when internal s3 endpoint was different from external --- configuration/config.go | 2 ++ routes/file/file_s3.go | 52 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/configuration/config.go b/configuration/config.go index fef618f..51cd549 100644 --- a/configuration/config.go +++ b/configuration/config.go @@ -67,6 +67,7 @@ func InitConfig() error { adminMail = flag.String("admin-mail", "", "Initial admin mail address") s3Bucket = flag.String("s3-bucket", "", "S3 Bucket for uploading files") s3Endpoint = flag.String("s3-endpoint", "", "Endpoint of S3 API for file uploads") + s3EndpointPublic = flag.String("s3-endpoint-public", "", "Public endpoint address of S3 API for file uploads") s3Region = flag.String("s3-region", "default", "S3 Region for file uploads") s3NoSSL = flag.Bool("s3-nossl", false, "Use encrypted connections to the S3 API") s3PathStyle = flag.Bool("s3-pathstyle", false, "Use path-style S3 API") @@ -103,6 +104,7 @@ func InitConfig() error { "admin.mail": *adminMail, "s3.bucket": *s3Bucket, "s3.endpoint": *s3Endpoint, + "s3.endpoint-public": *s3EndpointPublic, "s3.region": *s3Region, "jwt.secret": *jwtSecret, "jwt.expires-after": *jwtExpiresAfter, diff --git a/routes/file/file_s3.go b/routes/file/file_s3.go index 3a247fd..b1eb7fd 100644 --- a/routes/file/file_s3.go +++ b/routes/file/file_s3.go @@ -22,13 +22,16 @@ package file import ( + "errors" "fmt" "io" + "net/url" "time" "git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" @@ -83,7 +86,7 @@ func createS3Session() (*session.Session, error) { }, ) if err != nil { - return nil, fmt.Errorf("failed to create session: %s", err) + return nil, fmt.Errorf("failed to create session: %w", err) } return sess, nil @@ -110,7 +113,7 @@ func (f *File) putS3(fileContent io.Reader) error { Body: fileContent, }) if err != nil { - return fmt.Errorf("failed to upload file, %v", err) + return fmt.Errorf("failed to upload file: %w", err) } return nil @@ -128,16 +131,21 @@ func (f *File) getS3Url() (string, error) { svc := s3.New(sess) req, _ := svc.GetObjectRequest(&s3.GetObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String(f.Key), - ResponseContentType: aws.String(f.Type), - // ResponseContentDisposition: aws.String("attachment; filename=" + f.Name), + Bucket: aws.String(bucket), + Key: aws.String(f.Key), + ResponseContentType: aws.String(f.Type), + ResponseContentDisposition: aws.String("attachment; filename=" + f.Name), // ResponseContentEncoding: aws.String(), // ResponseContentLanguage: aws.String(), // ResponseCacheControl: aws.String(), // ResponseExpires: aws.String(), }) + err = updateS3Request(req) + if err != nil { + return "", err + } + urlStr, err := req.Presign(5 * 24 * 60 * time.Minute) if err != nil { return "", err @@ -170,3 +178,35 @@ func (f *File) deleteS3() error { return nil } + +// updateS3Request updates the request host to the public accessible S3 +// endpoint host so that presigned URLs are still valid when accessed +// by the user +func updateS3Request(req *request.Request) error { + epURL, err := getS3EndpointURL() + if err != nil { + return err + } + + req.HTTPRequest.URL.Scheme = epURL.Scheme + req.HTTPRequest.URL.Host = epURL.Host + + return nil +} + +func getS3EndpointURL() (*url.URL, error) { + ep, err := configuration.GlobalConfig.String("s3.endpoint-public") + if err != nil { + ep, err = configuration.GlobalConfig.String("s3.endpoint") + if err != nil { + return nil, errors.New("missing s3.endpoint setting") + } + } + + epURL, err := url.Parse(ep) + if err != nil { + return nil, err + } + + return epURL, nil +} From 8e5e9c2257fa53a72fe6ea9ef69aa5f348426f8e Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 19 Oct 2021 19:04:10 +0200 Subject: [PATCH 6/7] align name of kubernetes related backend options to naming scheme --- configuration/config.go | 8 ++++---- routes/config/config_endpoint.go | 12 ++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/configuration/config.go b/configuration/config.go index 51cd549..17949a5 100644 --- a/configuration/config.go +++ b/configuration/config.go @@ -84,8 +84,8 @@ func InitConfig() error { testDataPath = flag.String("test-data-path", "", "The path to a test data json file") groupsPath = flag.String("groups-path", "", "The path to a YAML file that maps user groups to scenario IDs") apiUpdateInterval = flag.String("api-update-interval", "10s" /* 10 sec */, "Interval in which API URL is queried for status updates of ICs") - rancherURL = flag.String("rancher-url", "rancher.k8s.eonerc.rwth-aachen.de", "URL of Rancher instance that is used to deploy the backend") - k8sCluster = flag.String("k8s-cluster", "local", "Name of the Kubernetes cluster where the backend is deployed") + k8sRancherURL = flag.String("k8s-rancher-url", "https://rancher.k8s.eonerc.rwth-aachen.de", "URL of Rancher instance that is used to deploy the backend") + k8sClusterName = flag.String("k8s-cluster-name", "local", "Name of the Kubernetes cluster where the backend is deployed") ) flag.Parse() @@ -119,8 +119,8 @@ func InitConfig() error { "groups.path": *groupsPath, "config.file": *configFile, "apiupdateinterval": *apiUpdateInterval, - "rancherURL": *rancherURL, - "k8sCluster": *k8sCluster, + "k8s.rancher-url": *k8sRancherURL, + "k8s.cluster-name": *k8sClusterName, } if *dbClear { diff --git a/routes/config/config_endpoint.go b/routes/config/config_endpoint.go index 67ebbac..9b6ceaa 100644 --- a/routes/config/config_endpoint.go +++ b/routes/config/config_endpoint.go @@ -47,14 +47,18 @@ type ConfigContact struct { Mail string `json:"mail"` } +type ConfigKubernetes struct { + RancherURL string `json:"rancher_url"` + ClusterName string `json:"cluster_name"` +} + type Config struct { Title string `json:"title"` SubTitle string `json:"sub_title"` Mode string `json:"mode"` Contact ConfigContact `json:"contact"` Authentication ConfigAuthentication `json:"authentication"` - RancherURL string `json:"rancherURL"` - K8sCluster string `json:"k8sCluster"` + Kubernetes ConfigKubernetes `json:"kubernetes"` } // getHealth godoc @@ -79,8 +83,8 @@ func getConfig(c *gin.Context) { resp.SubTitle, _ = cfg.String("sub-title") resp.Contact.Name, _ = cfg.String("contact.name") resp.Contact.Mail, _ = cfg.String("contact.mail") - resp.RancherURL, _ = cfg.String("rancherURL") - resp.K8sCluster, _ = cfg.String("k8sCluster") + resp.Kubernetes.RancherURL, _ = cfg.String("k8s.rancher-url") + resp.Kubernetes.ClusterName, _ = cfg.String("k8s.cluster-name") c.JSON(200, resp) } From 96572c35b13c19ea9d56745560e3dc2cdd6ba04e Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Mon, 25 Oct 2021 11:02:38 +0200 Subject: [PATCH 7/7] allow widget name to be empty string in update --- routes/widget/widget_validators.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/routes/widget/widget_validators.go b/routes/widget/widget_validators.go index a3e4ce2..535bc01 100644 --- a/routes/widget/widget_validators.go +++ b/routes/widget/widget_validators.go @@ -103,11 +103,7 @@ func (r *addWidgetRequest) createWidget() Widget { func (r *updateWidgetRequest) updatedWidget(oldWidget Widget) Widget { // Use the old Widget as a basis for the updated Widget `s` s := oldWidget - - if r.Widget.Name != "" { - s.Name = r.Widget.Name - } - + s.Name = r.Widget.Name s.Type = r.Widget.Type s.Width = r.Widget.Width s.Height = r.Widget.Height