fix: harden ops runbooks and execution

This commit is contained in:
2026-03-15 11:09:26 +08:00
parent 27b038898d
commit 36f11fa846
10 changed files with 1912 additions and 101 deletions

View File

@@ -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)