[ci skip] codex 5.3 review
This commit is contained in:
160
main.go
160
main.go
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
@@ -34,7 +35,11 @@ var (
|
||||
cronAggregateFrequency time.Duration
|
||||
)
|
||||
|
||||
const fallbackEncryptionKey = "5L1l3B5KvwOCzUHMAlCgsgUTRAYMfSpa"
|
||||
const (
|
||||
encryptedVcenterPasswordPrefix = "enc:v1:"
|
||||
encryptionKeyEnvVar = "VCTP_ENCRYPTION_KEY"
|
||||
legacyFallbackEncryptionKey = "5L1l3B5KvwOCzUHMAlCgsgUTRAYMfSpa"
|
||||
)
|
||||
|
||||
func main() {
|
||||
settingsPath := flag.String("settings", "/etc/dtms/vctp.yml", "Path to settings YAML")
|
||||
@@ -135,24 +140,27 @@ func main() {
|
||||
utils.GenerateCerts(tlsCertFilename, tlsKeyFilename)
|
||||
}
|
||||
|
||||
// Load vcenter credentials from serttings, decrypt if required
|
||||
encKey := deriveEncryptionKey(logger)
|
||||
// Load vcenter credentials from settings, decrypt if required.
|
||||
encKey := deriveEncryptionKey(logger, *settingsPath)
|
||||
a := secrets.New(logger, encKey)
|
||||
legacyDecryptKeys := deriveLegacyDecryptionKeys(*settingsPath, encKey)
|
||||
vcEp := strings.TrimSpace(s.Values.Settings.VcenterPassword)
|
||||
if len(vcEp) == 0 {
|
||||
logger.Error("No vcenter password configured")
|
||||
os.Exit(1)
|
||||
}
|
||||
vcPass, err := a.Decrypt(vcEp)
|
||||
vcPass, rewrittenCredential, err := resolveVcenterPassword(logger, a, legacyDecryptKeys, vcEp)
|
||||
if err != nil {
|
||||
logger.Error("failed to decrypt vcenter credentials. Assuming un-encrypted", "error", err)
|
||||
vcPass = []byte(vcEp)
|
||||
if cipherText, encErr := a.Encrypt([]byte(vcEp)); encErr != nil {
|
||||
logger.Warn("failed to encrypt vcenter credentials", "error", encErr)
|
||||
logger.Error("failed to resolve vcenter credentials", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if rewrittenCredential != "" && rewrittenCredential != vcEp {
|
||||
s.Values.Settings.VcenterPassword = rewrittenCredential
|
||||
if err := s.WriteYMLSettings(); err != nil {
|
||||
logger.Warn("failed to update settings with encrypted vcenter password", "error", err)
|
||||
} else {
|
||||
s.Values.Settings.VcenterPassword = cipherText
|
||||
if err := s.WriteYMLSettings(); err != nil {
|
||||
logger.Warn("failed to update settings with encrypted vcenter password", "error", err)
|
||||
if strings.HasPrefix(vcEp, encryptedVcenterPasswordPrefix) {
|
||||
logger.Info("rewrote vcenter password with refreshed encryption format")
|
||||
} else {
|
||||
logger.Info("encrypted vcenter password stored in settings file")
|
||||
}
|
||||
@@ -337,25 +345,141 @@ func durationFromSeconds(value int, fallback int) time.Duration {
|
||||
return time.Second * time.Duration(value)
|
||||
}
|
||||
|
||||
func deriveEncryptionKey(logger *slog.Logger) []byte {
|
||||
func resolveVcenterPassword(logger *slog.Logger, cipher *secrets.Secrets, legacyDecryptKeys [][]byte, raw string) ([]byte, string, error) {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return nil, "", fmt.Errorf("vcenter password is empty")
|
||||
}
|
||||
|
||||
// New format: explicit prefix so we can distinguish ciphertext from plaintext safely.
|
||||
if strings.HasPrefix(raw, encryptedVcenterPasswordPrefix) {
|
||||
enc := strings.TrimPrefix(raw, encryptedVcenterPasswordPrefix)
|
||||
pass, usedLegacyKey, err := decryptVcenterPasswordWithFallback(logger, cipher, legacyDecryptKeys, enc)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("prefixed password decrypt failed: %w", err)
|
||||
}
|
||||
if usedLegacyKey {
|
||||
rewrite, rewriteErr := encryptWithPrefix(cipher, pass)
|
||||
if rewriteErr != nil {
|
||||
logger.Warn("failed to refresh prefixed vcenter password after fallback decrypt", "error", rewriteErr)
|
||||
return pass, "", nil
|
||||
}
|
||||
logger.Info("rewrote prefixed vcenter password using active encryption key")
|
||||
return pass, rewrite, nil
|
||||
}
|
||||
return pass, "", nil
|
||||
}
|
||||
|
||||
// Backward compatibility: existing deployments may have unprefixed ciphertext.
|
||||
if pass, _, err := decryptVcenterPasswordWithFallback(logger, cipher, legacyDecryptKeys, raw); err == nil {
|
||||
rewrite, rewriteErr := encryptWithPrefix(cipher, pass)
|
||||
if rewriteErr != nil {
|
||||
logger.Warn("failed to re-encrypt legacy vcenter password with prefix", "error", rewriteErr)
|
||||
return pass, "", nil
|
||||
}
|
||||
return pass, rewrite, nil
|
||||
} else {
|
||||
// If decrypt fails and the input is non-trivial, treat it as plaintext and auto-encrypt.
|
||||
if len(raw) <= 2 {
|
||||
return nil, "", fmt.Errorf("vcenter password too short to auto-encrypt")
|
||||
}
|
||||
logger.Warn("unable to decrypt unprefixed vcenter password; treating value as plaintext", "error", err)
|
||||
rewrite, rewriteErr := encryptWithPrefix(cipher, []byte(raw))
|
||||
if rewriteErr != nil {
|
||||
return nil, "", fmt.Errorf("failed to encrypt plaintext vcenter password: %w", rewriteErr)
|
||||
}
|
||||
return []byte(raw), rewrite, nil
|
||||
}
|
||||
}
|
||||
|
||||
func decryptVcenterPasswordWithFallback(logger *slog.Logger, cipher *secrets.Secrets, legacyDecryptKeys [][]byte, encrypted string) ([]byte, bool, error) {
|
||||
pass, err := cipher.Decrypt(encrypted)
|
||||
if err == nil {
|
||||
return pass, false, nil
|
||||
}
|
||||
primaryErr := err
|
||||
for _, key := range legacyDecryptKeys {
|
||||
candidate := secrets.New(logger, key)
|
||||
pass, decErr := candidate.Decrypt(encrypted)
|
||||
if decErr == nil {
|
||||
return pass, true, nil
|
||||
}
|
||||
}
|
||||
return nil, false, primaryErr
|
||||
}
|
||||
|
||||
func encryptWithPrefix(cipher *secrets.Secrets, plain []byte) (string, error) {
|
||||
enc, encErr := cipher.Encrypt(plain)
|
||||
if encErr != nil {
|
||||
return "", encErr
|
||||
}
|
||||
return encryptedVcenterPasswordPrefix + enc, nil
|
||||
}
|
||||
|
||||
func deriveLegacyDecryptionKeys(settingsPath string, activeKey []byte) [][]byte {
|
||||
legacyKeys := make([][]byte, 0, 2)
|
||||
addCandidate := func(candidate []byte) {
|
||||
if len(candidate) == 0 || bytes.Equal(candidate, activeKey) {
|
||||
return
|
||||
}
|
||||
for _, existing := range legacyKeys {
|
||||
if bytes.Equal(existing, candidate) {
|
||||
return
|
||||
}
|
||||
}
|
||||
keyCopy := make([]byte, len(candidate))
|
||||
copy(keyCopy, candidate)
|
||||
legacyKeys = append(legacyKeys, keyCopy)
|
||||
}
|
||||
|
||||
platformKey, _ := deriveHostKeyCandidate(settingsPath)
|
||||
addCandidate(platformKey)
|
||||
addCandidate([]byte(legacyFallbackEncryptionKey))
|
||||
|
||||
return legacyKeys
|
||||
}
|
||||
|
||||
func deriveEncryptionKey(logger *slog.Logger, settingsPath string) []byte {
|
||||
if provided := strings.TrimSpace(os.Getenv(encryptionKeyEnvVar)); provided != "" {
|
||||
sum := sha256.Sum256([]byte(provided))
|
||||
logger.Debug("derived encryption key from environment variable", "env_var", encryptionKeyEnvVar)
|
||||
return sum[:]
|
||||
}
|
||||
|
||||
key, source := deriveHostKeyCandidate(settingsPath)
|
||||
switch source {
|
||||
case "bios-uuid":
|
||||
logger.Debug("derived encryption key from BIOS UUID")
|
||||
case "machine-id":
|
||||
logger.Debug("derived encryption key from machine-id")
|
||||
default:
|
||||
logger.Warn("using host-derived encryption key fallback; set environment variable for explicit key", "env_var", encryptionKeyEnvVar)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func deriveHostKeyCandidate(settingsPath string) ([]byte, string) {
|
||||
if runtime.GOOS == "linux" {
|
||||
if data, err := os.ReadFile("/sys/class/dmi/id/product_uuid"); err == nil {
|
||||
src := strings.TrimSpace(string(data))
|
||||
if src != "" {
|
||||
sum := sha256.Sum256([]byte(src))
|
||||
logger.Debug("derived encryption key from BIOS UUID")
|
||||
return sum[:]
|
||||
return sum[:], "bios-uuid"
|
||||
}
|
||||
}
|
||||
if data, err := os.ReadFile("/etc/machine-id"); err == nil {
|
||||
src := strings.TrimSpace(string(data))
|
||||
if src != "" {
|
||||
sum := sha256.Sum256([]byte(src))
|
||||
logger.Debug("derived encryption key from machine-id")
|
||||
return sum[:]
|
||||
return sum[:], "machine-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.Warn("using fallback encryption key; hardware UUID not available")
|
||||
return []byte(fallbackEncryptionKey)
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
hostname = "unknown-host"
|
||||
}
|
||||
src := strings.Join([]string{"vctp", runtime.GOOS, strings.TrimSpace(hostname), strings.TrimSpace(settingsPath)}, "|")
|
||||
sum := sha256.Sum256([]byte(src))
|
||||
return sum[:], "host-derived"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user