diff --git a/db/db.go b/db/db.go index 47611982e..ad8f8b5fd 100644 --- a/db/db.go +++ b/db/db.go @@ -89,6 +89,7 @@ func createTables(MetadataDbClient MetadataStorage) error { id SERIAL NOT NULL, name VARCHAR NOT NULL UNIQUE DEFAULT '$G', firebase_organization_id VARCHAR NOT NULL DEFAULT '' , + internal_ws_pass VARCHAR NOT NULL, PRIMARY KEY (id));` alterAuditLogsTable := ` @@ -5337,7 +5338,7 @@ func DeleteDlsMsgsByTenant(tenantName string) error { } // Tenants functions -func UpsertTenant(name string) (models.Tenant, error) { +func UpsertTenant(name string, encryptrdInternalWSPass string) (models.Tenant, error) { ctx, cancelfunc := context.WithTimeout(context.Background(), DbOperationTimeout*time.Second) defer cancelfunc() @@ -5347,7 +5348,7 @@ func UpsertTenant(name string) (models.Tenant, error) { } defer conn.Release() - query := `INSERT INTO tenants (name) VALUES($1) ON CONFLICT (name) DO NOTHING` + query := `INSERT INTO tenants (name, internal_ws_pass) VALUES($1, $2) ON CONFLICT (name) DO NOTHING` stmt, err := conn.Conn().Prepare(ctx, "upsert_tenant", query) if err != nil { @@ -5355,7 +5356,7 @@ func UpsertTenant(name string) (models.Tenant, error) { } var tenantId int - rows, err := conn.Conn().Query(ctx, stmt.Name, name) + rows, err := conn.Conn().Query(ctx, stmt.Name, name, encryptrdInternalWSPass) if err != nil { return models.Tenant{}, err } @@ -5416,7 +5417,7 @@ func UpsertTenant(name string) (models.Tenant, error) { return newTenant, nil } -func UpsertBatchOfTenants(tenants []string) error { +func UpsertBatchOfTenants(tenants []models.TenantForUpsert) error { ctx, cancelfunc := context.WithTimeout(context.Background(), DbOperationTimeout*time.Second) defer cancelfunc() conn, err := MetadataDbClient.Client.Acquire(ctx) @@ -5428,10 +5429,11 @@ func UpsertBatchOfTenants(tenants []string) error { valueStrings := make([]string, 0, len(tenants)) valueArgs := make([]interface{}, 0, len(tenants)) for i, tenant := range tenants { - valueStrings = append(valueStrings, fmt.Sprintf("($%d)", i+1)) - valueArgs = append(valueArgs, tenant) + valueStrings = append(valueStrings, fmt.Sprintf("($%d, $%d)", i*2+1, i*2+2)) + valueArgs = append(valueArgs, tenant.Name) + valueArgs = append(valueArgs, tenant.InternalWSPass) } - query := fmt.Sprintf("INSERT INTO tenants (name) VALUES %s ON CONFLICT (name) DO NOTHING", strings.Join(valueStrings, ",")) + query := fmt.Sprintf("INSERT INTO tenants (name, internal_ws_pass) VALUES %s ON CONFLICT (name) DO NOTHING", strings.Join(valueStrings, ",")) stmt, err := conn.Conn().Prepare(ctx, "upsert_tenants", query) if err != nil { return err @@ -5587,7 +5589,7 @@ func GetTenantByName(name string) (bool, models.Tenant, error) { return true, tenants[0], nil } -func CreateTenant(name, firebaseOrganizationId string) (models.Tenant, error) { +func CreateTenant(name, firebaseOrganizationId, encryptrdInternalWSPass string) (models.Tenant, error) { ctx, cancelfunc := context.WithTimeout(context.Background(), DbOperationTimeout*time.Second) defer cancelfunc() @@ -5597,7 +5599,7 @@ func CreateTenant(name, firebaseOrganizationId string) (models.Tenant, error) { } defer conn.Release() - query := `INSERT INTO tenants (name, firebase_organization_id) VALUES($1, $2)` + query := `INSERT INTO tenants (name, firebase_organization_id, internal_ws_pass) VALUES($1, $2, $3)` stmt, err := conn.Conn().Prepare(ctx, "create_new_tenant", query) if err != nil { @@ -5605,7 +5607,7 @@ func CreateTenant(name, firebaseOrganizationId string) (models.Tenant, error) { } var tenantId int - rows, err := conn.Conn().Query(ctx, stmt.Name, name, firebaseOrganizationId) + rows, err := conn.Conn().Query(ctx, stmt.Name, name, firebaseOrganizationId, encryptrdInternalWSPass) if err != nil { return models.Tenant{}, err } diff --git a/models/tenants.go b/models/tenants.go index 2a0bc9253..4b3f72160 100644 --- a/models/tenants.go +++ b/models/tenants.go @@ -15,4 +15,10 @@ type Tenant struct { ID int `json:"id"` Name string `json:"name"` FirebaseOrganizationId string `json:"firebase_organization_id"` + InternalWSPass string `json:"internal_ws_pass"` +} + +type TenantForUpsert struct { + Name string `json:"name"` + InternalWSPass string `json:"internal_ws_pass"` } diff --git a/server/memphis_cloud.go b/server/memphis_cloud.go index a6dc9ad77..ba4eb7488 100644 --- a/server/memphis_cloud.go +++ b/server/memphis_cloud.go @@ -44,6 +44,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const shouldCreateRootUserforGlobalAcc = true + type BillingHandler struct{ S *Server } type TenantHandler struct{ S *Server } type LoginSchema struct { @@ -1147,6 +1149,13 @@ func (umh UserMgmtHandler) Login(c *gin.Context) { c.AbortWithStatusJSON(500, gin.H{"message": "Server error"}) return } + decriptionKey := getAESKey() + decryptedUserPassword, err := DecryptAES(decriptionKey, tenant.InternalWSPass) + if err != nil { + serv.Errorf("Login: User " + body.Username + ": " + err.Error()) + c.AbortWithStatusJSON(500, gin.H{"message": "Server error"}) + return + } domain := "" secure := false @@ -1175,6 +1184,7 @@ func (umh UserMgmtHandler) Login(c *gin.Context) { "user_pass_based_auth": configuration.USER_PASS_BASED_AUTH, "connection_token": configuration.CONNECTION_TOKEN, "account_id": tenant.ID, + "internal_ws_pass": decryptedUserPassword, }) } @@ -1549,7 +1559,7 @@ func (mh MonitoringHandler) getMainOverviewDataDetails(tenantName string) (MainO func (umh UserMgmtHandler) RefreshToken(c *gin.Context) { user, err := getUserDetailsFromMiddleware(c) if err != nil { - serv.Errorf("refreshToken: " + err.Error()) + serv.Errorf("RefreshToken: " + err.Error()) c.AbortWithStatusJSON(401, gin.H{"message": "Unauthorized"}) return } @@ -1568,7 +1578,7 @@ func (umh UserMgmtHandler) RefreshToken(c *gin.Context) { return } if !exist { - serv.Warnf("refreshToken: user " + username + " does not exist") + serv.Warnf("RefreshToken: user " + username + " does not exist") c.AbortWithStatusJSON(401, gin.H{"message": "Unauthorized"}) return } @@ -1592,7 +1602,14 @@ func (umh UserMgmtHandler) RefreshToken(c *gin.Context) { return } if !exist { - serv.Warnf("Login: User " + username + ": tenant " + user.TenantName + " does not exist") + serv.Warnf("RefreshToken: User " + username + ": tenant " + user.TenantName + " does not exist") + c.AbortWithStatusJSON(500, gin.H{"message": "Server error"}) + return + } + decriptionKey := getAESKey() + decryptedUserPassword, err := DecryptAES(decriptionKey, tenant.InternalWSPass) + if err != nil { + serv.Errorf("RefreshToken: User " + username + ": " + err.Error()) c.AbortWithStatusJSON(500, gin.H{"message": "Server error"}) return } @@ -1625,5 +1642,6 @@ func (umh UserMgmtHandler) RefreshToken(c *gin.Context) { "user_pass_based_auth": configuration.USER_PASS_BASED_AUTH, "connection_token": configuration.CONNECTION_TOKEN, "account_id": tenant.ID, + "internal_ws_pass": decryptedUserPassword, }) } diff --git a/server/memphis_handlers_tenants.go b/server/memphis_handlers_tenants.go index f5f1ebb80..290c5609e 100644 --- a/server/memphis_handlers_tenants.go +++ b/server/memphis_handlers_tenants.go @@ -16,7 +16,11 @@ import ( ) func CreateGlobalTenantOnFirstSystemLoad() error { - _, err := db.UpsertTenant(globalAccountName) + encryptedPass, err := EncryptAES([]byte(generateRandomPassword(12))) + if err != nil { + return err + } + _, err = db.UpsertTenant(globalAccountName, encryptedPass) if err != nil { return err } diff --git a/server/memphis_helper.go b/server/memphis_helper.go index b453282ff..a6533cdd6 100644 --- a/server/memphis_helper.go +++ b/server/memphis_helper.go @@ -14,10 +14,12 @@ package server import ( "bufio" "bytes" + "crypto/rand" "encoding/hex" "encoding/json" "errors" "fmt" + "math/big" "memphis/conf" "memphis/db" "memphis/models" @@ -1161,6 +1163,8 @@ func GetMemphisOpts(opts Options, reload bool) (*Account, Options, error) { return &Account{}, Options{}, err } + decriptionKey := getAESKey() + for _, conf := range configs { switch conf.Key { case "dls_retention": @@ -1193,12 +1197,16 @@ func GetMemphisOpts(opts Options, reload bool) (*Account, Options, error) { if configuration.USER_PASS_BASED_AUTH { if !reload { if len(opts.Accounts) > 0 { - tenantsToUpsert := []string{globalAccountName} + tenantsToUpsert := []models.TenantForUpsert{} for _, account := range opts.Accounts { name := account.GetName() if account.GetName() != DEFAULT_SYSTEM_ACCOUNT { name = strings.ToLower(name) - tenantsToUpsert = append(tenantsToUpsert, name) + encryptedPass, err := EncryptAES([]byte(generateRandomPassword(12))) + if err != nil { + return &Account{}, Options{}, err + } + tenantsToUpsert = append(tenantsToUpsert, models.TenantForUpsert{Name: name, InternalWSPass: encryptedPass}) } } err = db.UpsertBatchOfTenants(tenantsToUpsert) @@ -1306,7 +1314,7 @@ func GetMemphisOpts(opts Options, reload bool) (*Account, Options, error) { if err != nil { return &Account{}, Options{}, err } - + tenantsId := map[string]int{} appUsers := []*User{} accounts := []*Account{} @@ -1323,10 +1331,13 @@ func GetMemphisOpts(opts Options, reload bool) (*Account, Options, error) { streams: siList, }, } - // generate random password for each tenant + decryptedUserPassword, err := DecryptAES(decriptionKey, tenant.InternalWSPass) + if err != nil { + return &Account{}, Options{}, err + } appUsers = append(appUsers, &User{ Username: MEMPHIS_USERNAME + "$" + strconv.Itoa(tenant.ID), - Password: configuration.CONNECTION_TOKEN, + Password: decryptedUserPassword, Account: account, }) accounts = append(accounts, account) @@ -1352,38 +1363,45 @@ func GetMemphisOpts(opts Options, reload bool) (*Account, Options, error) { } accounts = append(accounts, gacc) } - // ** not relevant for cloud - if reload { - appUsers = append(appUsers, &User{ - Username: "root$1", - Password: configuration.ROOT_PASSWORD, - Account: serv.gacc, - }) - appUsers = append(appUsers, &User{ - Username: MEMPHIS_USERNAME + "$" + strconv.Itoa(1), - Password: configuration.CONNECTION_TOKEN, - Account: serv.gacc, - }) - addedTenant[conf.GlobalAccountName] = serv.gacc - } else { - appUsers = append(appUsers, &User{ - Username: "root$1", - Password: configuration.ROOT_PASSWORD, - Account: gacc, - }) - appUsers = append(appUsers, &User{ - Username: MEMPHIS_USERNAME + "$" + strconv.Itoa(1), - Password: configuration.CONNECTION_TOKEN, - Account: gacc, - }) - addedTenant[conf.GlobalAccountName] = gacc + if shouldCreateRootUserforGlobalAcc { + _, globalT, err := db.GetGlobalTenant() + if err != nil { + return &Account{}, Options{}, err + } + decryptedPass, err := DecryptAES(decriptionKey, globalT.InternalWSPass) + if err != nil { + return &Account{}, Options{}, err + } + if reload { + appUsers = append(appUsers, &User{ + Username: "root$1", + Password: configuration.ROOT_PASSWORD, + Account: serv.gacc, + }) + appUsers = append(appUsers, &User{ + Username: MEMPHIS_USERNAME + "$" + strconv.Itoa(1), + Password: decryptedPass, + Account: serv.gacc, + }) + addedTenant[conf.GlobalAccountName] = serv.gacc + } else { + appUsers = append(appUsers, &User{ + Username: "root$1", + Password: configuration.ROOT_PASSWORD, + Account: gacc, + }) + appUsers = append(appUsers, &User{ + Username: MEMPHIS_USERNAME + "$" + strconv.Itoa(1), + Password: decryptedPass, + Account: gacc, + }) + addedTenant[conf.GlobalAccountName] = gacc + } } - // not relevant for cloud ** tenantsId[globalAccountName] = 1 - key := getAESKey() for _, user := range users { name := user.TenantName - decryptedUserPassword, err := DecryptAES(key, user.Password) + decryptedUserPassword, err := DecryptAES(decriptionKey, user.Password) if err != nil { return &Account{}, Options{}, err } @@ -1430,3 +1448,16 @@ func (s *Server) getTenantNameAndMessage(msg []byte) (string, string, error) { return tenantName, message, nil } + +func generateRandomPassword(length int) string { + allowedPasswordChars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+,.?/:;{}[]~" + charsetLength := big.NewInt(int64(len(allowedPasswordChars))) + password := make([]byte, length) + + for i := 0; i < length; i++ { + randomIndex, _ := rand.Int(rand.Reader, charsetLength) + password[i] = allowedPasswordChars[randomIndex.Int64()] + } + + return string(password) +} diff --git a/ui_src/src/App.js b/ui_src/src/App.js index 1683e92b5..ef1423db1 100644 --- a/ui_src/src/App.js +++ b/ui_src/src/App.js @@ -20,6 +20,7 @@ import { message } from 'antd'; import { LOCAL_STORAGE_ACCOUNT_ID, + LOCAL_STORAGE_INTERNAL_WS_PASS, LOCAL_STORAGE_CONNECTION_TOKEN, LOCAL_STORAGE_TOKEN, LOCAL_STORAGE_USER_PASS_BASED_AUTH, @@ -75,16 +76,17 @@ const App = withRouter((props) => { const ws_port = data.ws_port; const SOCKET_URL = ENVIRONMENT === 'production' ? `${WS_PREFIX}://${WS_SERVER_URL_PRODUCTION}:${ws_port}` : `${WS_PREFIX}://localhost:${ws_port}`; let conn; - const connection_token = localStorage.getItem(LOCAL_STORAGE_CONNECTION_TOKEN); if (localStorage.getItem(LOCAL_STORAGE_USER_PASS_BASED_AUTH) === 'true') { const account_id = localStorage.getItem(LOCAL_STORAGE_ACCOUNT_ID); + const internal_ws_pass = localStorage.getItem(LOCAL_STORAGE_INTERNAL_WS_PASS); conn = await connect({ servers: [SOCKET_URL], user: '$memphis_user$' + account_id, - pass: connection_token, + pass: internal_ws_pass, timeout: '5000' }); } else { + const connection_token = localStorage.getItem(LOCAL_STORAGE_CONNECTION_TOKEN); conn = await connect({ servers: [SOCKET_URL], token: '::' + connection_token, @@ -140,16 +142,17 @@ const App = withRouter((props) => { if (firstTime) { try { let conn; - const connection_token = localStorage.getItem(LOCAL_STORAGE_CONNECTION_TOKEN); if (localStorage.getItem(LOCAL_STORAGE_USER_PASS_BASED_AUTH) === 'true') { const account_id = localStorage.getItem(LOCAL_STORAGE_ACCOUNT_ID); + const internal_ws_pass = localStorage.getItem(LOCAL_STORAGE_INTERNAL_WS_PASS); conn = await connect({ servers: [SOCKET_URL], user: '$memphis_user$' + account_id, - pass: connection_token, + pass: internal_ws_pass, timeout: '5000' }); } else { + const connection_token = localStorage.getItem(LOCAL_STORAGE_CONNECTION_TOKEN); conn = await connect({ servers: [SOCKET_URL], token: '::' + connection_token, diff --git a/ui_src/src/const/localStorageConsts.js b/ui_src/src/const/localStorageConsts.js index a8d22cd5f..0e52b7fc2 100644 --- a/ui_src/src/const/localStorageConsts.js +++ b/ui_src/src/const/localStorageConsts.js @@ -25,6 +25,7 @@ export const LOCAL_STORAGE_EXPIRED_TOKEN = 'expires_in'; export const LOCAL_STORAGE_CLIENTS_PORT = 'client_port'; export const LOCAL_STORAGE_BROKER_HOST = 'broker_host'; export const LOCAL_STORAGE_ACCOUNT_ID = 'account_id'; +export const LOCAL_STORAGE_INTERNAL_WS_PASS = 'internal_ws_pass'; export const LOCAL_STORAGE_AVATAR_ID = 'avatar_id'; export const LOCAL_STORAGE_USER_NAME = 'user_name'; export const LOCAL_STORAGE_FULL_NAME = 'full_name'; diff --git a/ui_src/src/domain/login/index.js b/ui_src/src/domain/login/index.js index 83cb5a648..71dd905dd 100644 --- a/ui_src/src/domain/login/index.js +++ b/ui_src/src/domain/login/index.js @@ -16,7 +16,7 @@ import React, { useState, useContext, useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { Form } from 'antd'; -import { LOCAL_STORAGE_ACCOUNT_ID, LOCAL_STORAGE_CONNECTION_TOKEN, LOCAL_STORAGE_TOKEN, LOCAL_STORAGE_USER_PASS_BASED_AUTH } from '../../const/localStorageConsts'; +import { LOCAL_STORAGE_ACCOUNT_ID, LOCAL_STORAGE_INTERNAL_WS_PASS, LOCAL_STORAGE_CONNECTION_TOKEN, LOCAL_STORAGE_TOKEN, LOCAL_STORAGE_USER_PASS_BASED_AUTH } from '../../const/localStorageConsts'; import FullLogo from '../../assets/images/fullLogo.svg'; import { ApiEndpoints } from '../../const/apiEndpoints'; import sharps from '../../assets/images/sharps.svg'; @@ -90,16 +90,17 @@ const Login = (props) => { const ws_port = data.ws_port; const SOCKET_URL = ENVIRONMENT === 'production' ? `${WS_PREFIX}://${WS_SERVER_URL_PRODUCTION}:${ws_port}` : `${WS_PREFIX}://localhost:${ws_port}`; let conn; - const connection_token = localStorage.getItem(LOCAL_STORAGE_CONNECTION_TOKEN) if (localStorage.getItem(LOCAL_STORAGE_USER_PASS_BASED_AUTH) === 'true') { const account_id = localStorage.getItem(LOCAL_STORAGE_ACCOUNT_ID) + const internal_ws_pass = localStorage.getItem(LOCAL_STORAGE_INTERNAL_WS_PASS); conn = await connect({ servers: [SOCKET_URL], user: '$memphis_user$' + account_id, - pass: connection_token, + pass: internal_ws_pass, timeout: '5000' }); } else { + const connection_token = localStorage.getItem(LOCAL_STORAGE_CONNECTION_TOKEN) conn = await connect({ servers: [SOCKET_URL], token: '::'+connection_token, diff --git a/ui_src/src/domain/signup/index.js b/ui_src/src/domain/signup/index.js index c04ec7c7b..e3ff19009 100644 --- a/ui_src/src/domain/signup/index.js +++ b/ui_src/src/domain/signup/index.js @@ -18,7 +18,7 @@ import { KeyboardArrowRightRounded } from '@material-ui/icons'; import { useHistory } from 'react-router-dom'; import { Form } from 'antd'; -import { LOCAL_STORAGE_ACCOUNT_ID, LOCAL_STORAGE_CONNECTION_TOKEN, LOCAL_STORAGE_TOKEN, LOCAL_STORAGE_USER_PASS_BASED_AUTH } from '../../const/localStorageConsts'; +import { LOCAL_STORAGE_ACCOUNT_ID, LOCAL_STORAGE_INTERNAL_WS_PASS, LOCAL_STORAGE_CONNECTION_TOKEN, LOCAL_STORAGE_TOKEN, LOCAL_STORAGE_USER_PASS_BASED_AUTH } from '../../const/localStorageConsts'; import FullLogo from '../../assets/images/fullLogo.svg'; import signupInfo from '../../assets/images/signupInfo.svg'; import { ApiEndpoints } from '../../const/apiEndpoints'; @@ -110,16 +110,17 @@ const Signup = (props) => { const ws_port = data.ws_port; const SOCKET_URL = ENVIRONMENT === 'production' ? `${WS_PREFIX}://${WS_SERVER_URL_PRODUCTION}:${ws_port}` : `${WS_PREFIX}://localhost:${ws_port}`; let conn; + if (localStorage.getItem(LOCAL_STORAGE_USER_PASS_BASED_AUTH) === 'true') { + const account_id = localStorage.getItem(LOCAL_STORAGE_ACCOUNT_ID) + const internal_ws_pass = localStorage.getItem(LOCAL_STORAGE_INTERNAL_WS_PASS); + conn = await connect({ + servers: [SOCKET_URL], + user: '$memphis_user$' + account_id, + pass: internal_ws_pass, + timeout: '5000' + }); + } else { const connection_token = localStorage.getItem(LOCAL_STORAGE_CONNECTION_TOKEN) - if (localStorage.getItem(LOCAL_STORAGE_USER_PASS_BASED_AUTH) === 'true') { - const account_id = localStorage.getItem(LOCAL_STORAGE_ACCOUNT_ID) - conn = await connect({ - servers: [SOCKET_URL], - user: '$memphis_user$' + account_id, - pass: connection_token, - timeout: '5000' - }); - } else { conn = await connect({ servers: [SOCKET_URL], token: '::'+connection_token, diff --git a/ui_src/src/services/auth.js b/ui_src/src/services/auth.js index 286047c84..df00a263a 100644 --- a/ui_src/src/services/auth.js +++ b/ui_src/src/services/auth.js @@ -34,6 +34,7 @@ import { LOCAL_STORAGE_CONNECTION_TOKEN, LOCAL_STORAGE_USER_PASS_BASED_AUTH, LOCAL_STORAGE_ACCOUNT_ID, + LOCAL_STORAGE_INTERNAL_WS_PASS, LOCAL_STORAGE_WELCOME_MESSAGE } from '../const/localStorageConsts'; import pathDomains from '../router'; @@ -66,6 +67,7 @@ const AuthService = (function () { localStorage.setItem(LOCAL_STORAGE_CONNECTION_TOKEN, userData.connection_token); localStorage.setItem(LOCAL_STORAGE_USER_PASS_BASED_AUTH, userData.user_pass_based_auth); localStorage.setItem(LOCAL_STORAGE_ACCOUNT_ID, userData.account_id); + localStorage.setItem(LOCAL_STORAGE_INTERNAL_WS_PASS, userData.internal_ws_pass); if (userData.already_logged_in === false) { localStorage.setItem(LOCAL_STORAGE_WELCOME_MESSAGE, true);