diff --git a/conf/config.go b/conf/config.go index a3f4b0cb5..504fdfe0a 100644 --- a/conf/config.go +++ b/conf/config.go @@ -37,6 +37,7 @@ type Configuration struct { METADATA_DB_TLS_CA string USER_PASS_BASED_AUTH bool CONNECTION_TOKEN string + ENCRYPTION_SECRET_KEY string // SANDBOX_SLACK_BOT_TOKEN string // SANDBOX_SLACK_CHANNEL_ID string // SANDBOX_UI_URL string diff --git a/main.go b/main.go index 191560470..bedbc5757 100644 --- a/main.go +++ b/main.go @@ -113,6 +113,11 @@ func runMemphis(s *server.Server) db.MetadataStorage { s.InitializeMemphisHandlers() + err = server.EncryptOldUnencryptedValues() + if err != nil { + s.Errorf("Failed encrypt old unencrypted values: " + err.Error()) + } + err = server.InitializeIntegrations() if err != nil { s.Errorf("Failed initializing integrations: " + err.Error()) diff --git a/server/ciphersuites.go b/server/ciphersuites.go index 55dbc8bf0..aa611e256 100644 --- a/server/ciphersuites.go +++ b/server/ciphersuites.go @@ -14,7 +14,13 @@ package server import ( + "bytes" + "crypto/aes" + "crypto/cipher" "crypto/tls" + "encoding/base64" + "errors" + "fmt" ) // Where we maintain all of the available ciphers @@ -101,3 +107,60 @@ func defaultCurvePreferences() []tls.CurveID { tls.CurveP521, } } + +func EncryptAES(plaintext []byte) (string, error) { + key := getAESKey() + c, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + blockSize := c.BlockSize() + padding := blockSize - len(plaintext)%blockSize + paddedPlaintext := append(plaintext, bytes.Repeat([]byte{byte(padding)}, padding)...) + + ciphertext := make([]byte, len(paddedPlaintext)) + mode := cipher.NewCBCEncrypter(c, key[:blockSize]) + mode.CryptBlocks(ciphertext, paddedPlaintext) + return base64.URLEncoding.EncodeToString(ciphertext), nil +} + +func DecryptAES(encryptedValue string) (string, error) { + key := getAESKey() + ciphertextBytes, err := base64.URLEncoding.DecodeString(encryptedValue) + if err != nil { + return "", err + } + + c, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + blockSize := c.BlockSize() + if len(ciphertextBytes) < blockSize { + return "", errors.New("ciphertext too short") + } + + plaintext := make([]byte, len(ciphertextBytes)) + mode := cipher.NewCBCDecrypter(c, key[:blockSize]) + mode.CryptBlocks(plaintext, ciphertextBytes) + + padSize := int(plaintext[len(plaintext)-1]) + if padSize < 1 || padSize > aes.BlockSize { + return "", fmt.Errorf("invalid padding size") + } + + unpadded := plaintext[:len(plaintext)-padSize] + return string(unpadded), nil +} + +func getAESKey() []byte { + var key []byte + if configuration.DOCKER_ENV == "true" || configuration.LOCAL_CLUSTER_ENV { + key = []byte(DEFAULT_ENCRYPTION_SECRET_KEY) + } else { + key = []byte(configuration.ENCRYPTION_SECRET_KEY) + } + return key +} diff --git a/server/const.go b/server/const.go index a8cedc41b..1263bf216 100644 --- a/server/const.go +++ b/server/const.go @@ -222,4 +222,6 @@ const ( // DEFAULT_FETCH_TIMEOUT is the default time that the system will wait for an account fetch to return. DEFAULT_ACCOUNT_FETCH_TIMEOUT = 1900 * time.Millisecond + + DEFAULT_ENCRYPTION_SECRET_KEY = "thisis32bitlongpassphraseimusing" ) diff --git a/server/integrations.go b/server/integrations.go index 3043a6178..5a22c52a2 100644 --- a/server/integrations.go +++ b/server/integrations.go @@ -53,6 +53,19 @@ func InitializeConnection(integrationsType string) error { } else if !exist { return nil } + if value, ok := integration.Keys["secret_key"]; ok { + decryptedValue, err := DecryptAES(value) + if err != nil { + return err + } + integration.Keys["secret_key"] = decryptedValue + } else if value, ok := integration.Keys["auth_token"]; ok { + decryptedValue, err := DecryptAES(value) + if err != nil { + return err + } + integration.Keys["auth_token"] = decryptedValue + } CacheDetails(integrationsType, integration.Keys, integration.Properties) return nil } @@ -71,3 +84,76 @@ func CacheDetails(integrationType string, keys map[string]string, properties map } } + +func EncryptOldUnencryptedValues() error { + err := encryptUnencryptedKeysByIntegrationType("s3", "secret_key") + if err != nil { + return err + } + + err = encryptUnencryptedKeysByIntegrationType("slack", "auth_token") + if err != nil { + return err + } + + err = encryptUnencryptedAppUsersPasswords() + if err != nil { + return err + } + return nil +} + +func encryptUnencryptedKeysByIntegrationType(integrationType, keyTitle string) error { + exist, integration, err := db.GetIntegration(integrationType) + needToEncrypt := false + if err != nil { + return err + } else if !exist { + return nil + } + if value, ok := integration.Keys["secret_key"]; ok { + _, err := DecryptAES(value) + if err != nil { + needToEncrypt = true + } + } else if value, ok := integration.Keys["auth_token"]; ok { + _, err := DecryptAES(value) + if err != nil { + needToEncrypt = true + } + } + if needToEncrypt { + encryptedValue, err := EncryptAES([]byte(integration.Keys[keyTitle])) + if err != nil { + return err + } + integration.Keys[keyTitle] = encryptedValue + _, err = db.UpdateIntegration(integrationType, integration.Keys, integration.Properties) + if err != nil { + return err + } + } + return nil +} + +func encryptUnencryptedAppUsersPasswords() error { + users, err := db.GetAllUsersByType("application") + if err != nil { + return err + } + for _, user := range users { + _, err := DecryptAES(user.Password) + if err != nil { + password, err := EncryptAES([]byte(user.Password)) + if err != nil { + return err + } + + err = db.ChangeUserPassword(user.Username, password) + if err != nil { + return err + } + } + } + return nil +} diff --git a/server/memphis_handlers_user_mgmt.go b/server/memphis_handlers_user_mgmt.go index 84f999356..daabd565b 100644 --- a/server/memphis_handlers_user_mgmt.go +++ b/server/memphis_handlers_user_mgmt.go @@ -701,7 +701,12 @@ func (umh UserMgmtHandler) AddUser(c *gin.Context) { c.AbortWithStatusJSON(SHOWABLE_ERROR_STATUS_CODE, gin.H{"message": "Password was not provided"}) return } - password = body.Password + password, err = EncryptAES([]byte(body.Password)) + if err != nil { + serv.Errorf("AddUser: User " + body.Username + ": " + err.Error()) + c.AbortWithStatusJSON(500, gin.H{"message": "Server error"}) + return + } avatarId = 1 if body.AvatarId > 0 { avatarId = body.AvatarId diff --git a/server/memphis_helper.go b/server/memphis_helper.go index 6df909740..830c4caca 100644 --- a/server/memphis_helper.go +++ b/server/memphis_helper.go @@ -1107,7 +1107,11 @@ func (s *Server) GetMemphisOpts(opts Options) (Options, error) { appUsers := []*User{{Username: "root", Password: configuration.ROOT_PASSWORD}} appUsers = append(appUsers, &User{Username: MEMPHIS_USERNAME, Password: configuration.CONNECTION_TOKEN}) for _, user := range users { - appUsers = append(appUsers, &User{Username: user.Username, Password: user.Password}) + decryptedUserPassword, err := DecryptAES(user.Password) + if err != nil { + return Options{}, err + } + appUsers = append(appUsers, &User{Username: user.Username, Password: decryptedUserPassword}) } opts.Users = appUsers } diff --git a/server/notifications_slack.go b/server/notifications_slack.go index e7832a0bd..79976549d 100644 --- a/server/notifications_slack.go +++ b/server/notifications_slack.go @@ -189,7 +189,13 @@ func createSlackIntegration(keys map[string]string, properties map[string]bool, if err != nil { return slackIntegration, err } - integrationRes, insertErr := db.InsertNewIntegration("slack", keys, properties) + cloneKeys := copyMaps(keys) + encryptedValue, err := EncryptAES([]byte(keys["auth_token"])) + if err != nil { + return models.Integration{}, err + } + cloneKeys["auth_token"] = encryptedValue + integrationRes, insertErr := db.InsertNewIntegration("slack", cloneKeys, properties) if insertErr != nil { return slackIntegration, insertErr } @@ -236,10 +242,17 @@ func updateSlackIntegration(authToken string, channelID string, pmAlert bool, sv return slackIntegration, err } keys, properties := createIntegrationsKeysAndProperties("slack", authToken, channelID, pmAlert, svfAlert, disconnectAlert, "", "", "", "") - slackIntegration, err = db.UpdateIntegration("slack", keys, properties) + cloneKeys := copyMaps(keys) + encryptedValue, err := EncryptAES([]byte(authToken)) if err != nil { return models.Integration{}, err } + cloneKeys["auth_token"] = encryptedValue + slackIntegration, err = db.UpdateIntegration("slack", cloneKeys, properties) + if err != nil { + return models.Integration{}, err + } + integrationToUpdate := models.CreateIntegrationSchema{ Name: "slack", Keys: keys, diff --git a/server/storage_s3.go b/server/storage_s3.go index e76929e90..99e23f1bd 100644 --- a/server/storage_s3.go +++ b/server/storage_s3.go @@ -101,6 +101,13 @@ func (it IntegrationsHandler) handleS3Integrtation(keys map[string]string) (int, if !exist { return SHOWABLE_ERROR_STATUS_CODE, map[string]string{}, errors.New("secret key is invalid") } + if value, ok := integrationFromDb.Keys["secret_key"]; ok { + decryptedValue, err := DecryptAES(value) + if err != nil { + return 500, map[string]string{}, err + } + integrationFromDb.Keys["secret_key"] = decryptedValue + } secretKey = integrationFromDb.Keys["secret_key"] keys["secret_key"] = secretKey } @@ -141,7 +148,13 @@ func createS3Integration(keys map[string]string, properties map[string]bool) (mo if err != nil { return models.Integration{}, err } else if !exist { - integrationRes, insertErr := db.InsertNewIntegration("s3", keys, properties) + cloneKeys := copyMaps(keys) + encryptedValue, err := EncryptAES([]byte(keys["secret_key"])) + if err != nil { + return models.Integration{}, err + } + cloneKeys["secret_key"] = encryptedValue + integrationRes, insertErr := db.InsertNewIntegration("s3", cloneKeys, properties) if insertErr != nil { return models.Integration{}, insertErr } @@ -167,7 +180,13 @@ func createS3Integration(keys map[string]string, properties map[string]bool) (mo } func updateS3Integration(keys map[string]string, properties map[string]bool) (models.Integration, error) { - s3Integration, err := db.UpdateIntegration("s3", keys, properties) + cloneKeys := copyMaps(keys) + encryptedValue, err := EncryptAES([]byte(keys["secret_key"])) + if err != nil { + return models.Integration{}, err + } + cloneKeys["secret_key"] = encryptedValue + s3Integration, err := db.UpdateIntegration("s3", cloneKeys, properties) if err != nil { return models.Integration{}, err } diff --git a/server/util.go b/server/util.go index c7f5827ed..52e0861f1 100644 --- a/server/util.go +++ b/server/util.go @@ -334,3 +334,16 @@ func copyStrings(src []string) []string { copy(dst, src) return dst } + +// copyMaps make a new map of the same size than `src` and copy its content. +// If `src` is nil, then this returns `nil` +func copyMaps(src map[string]string) map[string]string { + if src == nil { + return nil + } + dst := make(map[string]string, len(src)) + for k, v := range src { + dst[k] = v + } + return dst +}