fix: harden ops runbooks and execution
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -15,9 +16,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"xiaji-go/config"
|
||||
"xiaji-go/models"
|
||||
"ops-assistant/config"
|
||||
"ops-assistant/models"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -29,27 +31,44 @@ type UnifiedMessage struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
var secretCipher *cipherContext
|
||||
const (
|
||||
encPrefixV1 = "enc:v1:"
|
||||
encPrefixV2 = "enc:v2:"
|
||||
)
|
||||
|
||||
var secretCipherV1 *cipherContext
|
||||
var secretCipherV2 *cipherContext
|
||||
|
||||
type cipherContext struct {
|
||||
aead cipher.AEAD
|
||||
}
|
||||
|
||||
func InitSecretCipher(key string) error {
|
||||
k := deriveKey32(key)
|
||||
block, err := aes.NewCipher(k)
|
||||
k1 := deriveKey32Legacy(key)
|
||||
block1, err := aes.NewCipher(k1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aead, err := cipher.NewGCM(block)
|
||||
aead1, err := cipher.NewGCM(block1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secretCipher = &cipherContext{aead: aead}
|
||||
secretCipherV1 = &cipherContext{aead: aead1}
|
||||
|
||||
k2 := deriveKey32V2(key)
|
||||
block2, err := aes.NewCipher(k2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aead2, err := cipher.NewGCM(block2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secretCipherV2 = &cipherContext{aead: aead2}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deriveKey32(s string) []byte {
|
||||
func deriveKey32Legacy(s string) []byte {
|
||||
b := []byte(s)
|
||||
out := make([]byte, 32)
|
||||
if len(b) >= 32 {
|
||||
@@ -63,37 +82,66 @@ func deriveKey32(s string) []byte {
|
||||
return out
|
||||
}
|
||||
|
||||
func deriveKey32V2(s string) []byte {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return make([]byte, 32)
|
||||
}
|
||||
// PBKDF2 for deterministic 32-byte key derivation
|
||||
return pbkdf2.Key([]byte(s), []byte("ops-assistant-v1"), 200000, 32, sha256.New)
|
||||
}
|
||||
|
||||
func encryptString(plain string) (string, error) {
|
||||
if secretCipher == nil {
|
||||
if secretCipherV2 == nil {
|
||||
return plain, errors.New("cipher not initialized")
|
||||
}
|
||||
nonce := make([]byte, secretCipher.aead.NonceSize())
|
||||
nonce := make([]byte, secretCipherV2.aead.NonceSize())
|
||||
if _, err := rand.Read(nonce); err != nil {
|
||||
return "", err
|
||||
}
|
||||
ciphertext := secretCipher.aead.Seal(nil, nonce, []byte(plain), nil)
|
||||
ciphertext := secretCipherV2.aead.Seal(nil, nonce, []byte(plain), nil)
|
||||
buf := append(nonce, ciphertext...)
|
||||
return "enc:v1:" + base64.StdEncoding.EncodeToString(buf), nil
|
||||
return encPrefixV2 + base64.StdEncoding.EncodeToString(buf), nil
|
||||
}
|
||||
|
||||
func decryptString(raw string) (string, error) {
|
||||
if !strings.HasPrefix(raw, "enc:v1:") {
|
||||
if !strings.HasPrefix(raw, encPrefixV1) && !strings.HasPrefix(raw, encPrefixV2) {
|
||||
return raw, nil
|
||||
}
|
||||
if secretCipher == nil {
|
||||
if strings.HasPrefix(raw, encPrefixV2) {
|
||||
if secretCipherV2 == nil {
|
||||
return "", errors.New("cipher not initialized")
|
||||
}
|
||||
data, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(raw, encPrefixV2))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ns := secretCipherV2.aead.NonceSize()
|
||||
if len(data) <= ns {
|
||||
return "", errors.New("invalid ciphertext")
|
||||
}
|
||||
nonce := data[:ns]
|
||||
ct := data[ns:]
|
||||
pt, err := secretCipherV2.aead.Open(nil, nonce, ct, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(pt), nil
|
||||
}
|
||||
|
||||
if secretCipherV1 == nil {
|
||||
return "", errors.New("cipher not initialized")
|
||||
}
|
||||
data, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(raw, "enc:v1:"))
|
||||
data, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(raw, encPrefixV1))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ns := secretCipher.aead.NonceSize()
|
||||
ns := secretCipherV1.aead.NonceSize()
|
||||
if len(data) <= ns {
|
||||
return "", errors.New("invalid ciphertext")
|
||||
}
|
||||
nonce := data[:ns]
|
||||
ct := data[ns:]
|
||||
pt, err := secretCipher.aead.Open(nil, nonce, ct, nil)
|
||||
pt, err := secretCipherV1.aead.Open(nil, nonce, ct, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -119,10 +167,10 @@ func EncryptSecretJSON(raw string) string {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return raw
|
||||
}
|
||||
if strings.HasPrefix(raw, "enc:v1:") {
|
||||
if strings.HasPrefix(raw, encPrefixV1) || strings.HasPrefix(raw, encPrefixV2) {
|
||||
return raw
|
||||
}
|
||||
if secretCipher == nil {
|
||||
if secretCipherV2 == nil {
|
||||
return raw
|
||||
}
|
||||
enc, err := encryptString(raw)
|
||||
|
||||
Reference in New Issue
Block a user