Некоторые проверки не удались
CI / test (push) Successful in 2m5s
Docker / Build and publish worker image (push) Failing after 31s
98 строки
2.3 KiB
Go
98 строки
2.3 KiB
Go
package models
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"rocketgit.ru/rsmon/worker/config/secrets"
|
|
)
|
|
|
|
const credentialKeyEnv = "RSMON_CRED_KEY"
|
|
|
|
// encryptSecret encrypts plaintext with AES-GCM. If no key is configured
|
|
// (dev/test), stores the value with a "plain:" prefix. Production deployments
|
|
// must set RSMON_CRED_KEY or config/secrets.yml crypto.pepper.
|
|
func encryptSecret(plaintext string) (string, error) {
|
|
key := credentialKey()
|
|
if key == "" {
|
|
return "plain:" + plaintext, nil
|
|
}
|
|
block, err := aes.NewCipher(deriveKey(key))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
nonce := make([]byte, gcm.NonceSize())
|
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
|
return "", err
|
|
}
|
|
sealed := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
|
|
return "enc:" + base64.StdEncoding.EncodeToString(sealed), nil
|
|
}
|
|
|
|
// decryptSecret decrypts a value produced by encryptSecret. Values without a
|
|
// known prefix are returned as-is for backward compatibility with legacy
|
|
// plaintext records.
|
|
func decryptSecret(stored string) (string, error) {
|
|
if stored == "" {
|
|
return "", nil
|
|
}
|
|
switch {
|
|
case strings.HasPrefix(stored, "plain:"):
|
|
return strings.TrimPrefix(stored, "plain:"), nil
|
|
case strings.HasPrefix(stored, "enc:"):
|
|
key := credentialKey()
|
|
if key == "" {
|
|
return "", errors.New("credential key not configured but secret is encrypted")
|
|
}
|
|
raw, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(stored, "enc:"))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
block, err := aes.NewCipher(deriveKey(key))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(raw) < gcm.NonceSize() {
|
|
return "", errors.New("ciphertext too short")
|
|
}
|
|
nonce, ciphertext := raw[:gcm.NonceSize()], raw[gcm.NonceSize():]
|
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(plaintext), nil
|
|
default:
|
|
return stored, nil
|
|
}
|
|
}
|
|
|
|
func deriveKey(s string) []byte {
|
|
h := sha256.Sum256([]byte(s))
|
|
return h[:32]
|
|
}
|
|
|
|
func credentialKey() string {
|
|
if k := os.Getenv(credentialKeyEnv); k != "" {
|
|
return k
|
|
}
|
|
if secrets.Crypto != nil && secrets.Crypto.Pepper != "" {
|
|
return secrets.Crypto.Pepper
|
|
}
|
|
return ""
|
|
}
|