From 74436710592122cf06d73489eed223f8706df5d3 Mon Sep 17 00:00:00 2001 From: Denis Gukov Date: Tue, 31 Aug 2021 04:02:41 +0500 Subject: [PATCH] feat(be): add access key encryption --- api/projects/keys.go | 10 ++++- api/tasks/runner.go | 8 +++- cli/cmd/setup.go | 2 +- db/AccessKey.go | 91 ++++++++++++++++++++++++++++++++++++++++++++ util/config.go | 7 +++- 5 files changed, 112 insertions(+), 6 deletions(-) diff --git a/api/projects/keys.go b/api/projects/keys.go index a1228bdb..7c10cb54 100644 --- a/api/projects/keys.go +++ b/api/projects/keys.go @@ -152,8 +152,14 @@ func UpdateKey(w http.ResponseWriter, r *http.Request) { // override secret key.Secret = oldKey.Secret } else { - secret := *key.Secret + "\n" - key.Secret = &secret + *key.Secret += "\n" + + err := key.EncryptSecret() + + if err != nil { + helpers.WriteError(w, err) + return + } } if err := helpers.Store(r).UpdateAccessKey(key); err != nil { diff --git a/api/tasks/runner.go b/api/tasks/runner.go index 4bc3ad9f..5d844b51 100644 --- a/api/tasks/runner.go +++ b/api/tasks/runner.go @@ -345,7 +345,13 @@ func (t *task) installKey(key db.AccessKey) error { } } - return ioutil.WriteFile(path, []byte(*key.Secret), 0600) + secret, err := key.DecryptSecret() + + if err != nil { + return err + } + + return ioutil.WriteFile(path, []byte(secret), 0600) } func (t *task) updateRepository() error { diff --git a/cli/cmd/setup.go b/cli/cmd/setup.go index ebdfa46c..44d54117 100644 --- a/cli/cmd/setup.go +++ b/cli/cmd/setup.go @@ -29,7 +29,7 @@ func doSetup() int { var config *util.ConfigType for { config = &util.ConfigType{} - config.GenerateCookieSecrets() + config.GenerateSecrets() setup.InteractiveSetup(config) if setup.AskConfigConfirmation(config) { diff --git a/db/AccessKey.go b/db/AccessKey.go index f2ec0972..8e42fc76 100644 --- a/db/AccessKey.go +++ b/db/AccessKey.go @@ -1,6 +1,12 @@ package db import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "fmt" + "io" "strconv" "github.com/ansible-semaphore/semaphore/util" @@ -30,3 +36,88 @@ type AccessKey struct { func (key AccessKey) GetPath() string { return util.Config.TmpPath + "/access_key_" + strconv.Itoa(key.ID) } + +func (key *AccessKey) EncryptSecret() error { + if key.Secret == nil || *key.Secret == "" { + return nil + } + + if util.Config.AccessKeyEncryption == "" { // do not encrypt if secret key not specified + return nil + } + + plaintext := []byte(*key.Secret) + + encryption, err := base64.StdEncoding.DecodeString(util.Config.CookieEncryption) + + if err != nil { + return err + } + + c, err := aes.NewCipher(encryption) + if err != nil { + return err + } + + gcm, err := cipher.NewGCM(c) + if err != nil { + return err + } + + nonce := make([]byte, gcm.NonceSize()) + if _, err = io.ReadFull(rand.Reader, nonce); err != nil { + return err + } + + secret := string(gcm.Seal(nonce, nonce, plaintext, nil)) + + key.Secret = &secret + + return nil +} + +func (key AccessKey) DecryptSecret() (string, error) { + if key.Secret == nil || *key.Secret == "" { + return "", nil + } + + ciphertext := []byte(*key.Secret) + + if ciphertext[len(ciphertext) - 1] == '\n' { // not encrypted string + return *key.Secret, nil + } + + if util.Config.AccessKeyEncryption == "" { // do not decrypt if secret key not specified + return *key.Secret, nil + } + + encryption, err := base64.StdEncoding.DecodeString(util.Config.CookieEncryption) + if err != nil { + return "", err + } + + c, err := aes.NewCipher(encryption) + if err != nil { + return "", err + } + + gcm, err := cipher.NewGCM(c) + if err != nil { + return "", err + } + + nonceSize := gcm.NonceSize() + if len(ciphertext) < nonceSize { + return "", fmt.Errorf("ciphertext too short") + } + + nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] + + encrypted, err := gcm.Open(nil, nonce, ciphertext, nil) + + if err != nil { + return "", err + } + + return string(encrypted), nil +} diff --git a/util/config.go b/util/config.go index cd614420..213c3c19 100644 --- a/util/config.go +++ b/util/config.go @@ -66,6 +66,7 @@ type ConfigType struct { // cookie hashing & encryption CookieHash string `json:"cookie_hash"` CookieEncryption string `json:"cookie_encryption"` + AccessKeyEncryption string `json:"access_key_encryption"` // email alerting EmailSender string `json:"email_sender"` @@ -292,11 +293,13 @@ func (conf *ConfigType) GetDBConfig() (dbConfig DbConfig, err error) { return } -//GenerateCookieSecrets generates cookie secret during setup -func (conf *ConfigType) GenerateCookieSecrets() { +//GenerateSecrets generates cookie secret during setup +func (conf *ConfigType) GenerateSecrets() { hash := securecookie.GenerateRandomKey(32) encryption := securecookie.GenerateRandomKey(32) + accessKeyEncryption := securecookie.GenerateRandomKey(32) conf.CookieHash = base64.StdEncoding.EncodeToString(hash) conf.CookieEncryption = base64.StdEncoding.EncodeToString(encryption) + conf.AccessKeyEncryption = base64.StdEncoding.EncodeToString(accessKeyEncryption) }