feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
Этот коммит содержится в:
97
app/models/credential_crypto.go
Обычный файл
97
app/models/credential_crypto.go
Обычный файл
@@ -0,0 +1,97 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"rsgit.ru/rsmon/rsmon/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 ""
|
||||
}
|
||||
Ссылка в новой задаче
Block a user