package settings import ( "encoding/base64" "encoding/json" "fmt" "os" "path/filepath" ) type encryptedBlob struct { Nonce string `json:"nonce"` Ciphertext string `json:"ciphertext"` } // KeyFromEnv reads and decodes CLUSTERCANVAS_CONFIG_KEY (base64, 16/24/32 bytes). func KeyFromEnv() ([]byte, error) { rawKey := os.Getenv(ConfigKeyEnvVar) if rawKey == "" { return nil, fmt.Errorf("%s is not set", ConfigKeyEnvVar) } return decodeKey(rawKey) } // LoadSecrets decrypts secrets.enc from dir using key. func LoadSecrets(dir string, key []byte) (Secrets, error) { path := filepath.Join(dir, SecretsFileName) payload, err := os.ReadFile(path) if err != nil { return Secrets{}, fmt.Errorf("read secrets: %w", err) } var blob encryptedBlob if err := json.Unmarshal(payload, &blob); err != nil { return Secrets{}, fmt.Errorf("parse secrets envelope: %w", err) } nonce, err := base64.StdEncoding.DecodeString(blob.Nonce) if err != nil { return Secrets{}, fmt.Errorf("decode nonce: %w", err) } ciphertext, err := base64.StdEncoding.DecodeString(blob.Ciphertext) if err != nil { return Secrets{}, fmt.Errorf("decode ciphertext: %w", err) } plaintext, err := open(nonce, ciphertext, key) if err != nil { return Secrets{}, err } var secrets Secrets if err := json.Unmarshal(plaintext, &secrets); err != nil { return Secrets{}, fmt.Errorf("parse secrets: %w", err) } return secrets, nil } // SaveSecrets encrypts secrets and writes secrets.enc to dir with mode 0600. func SaveSecrets(dir string, secrets Secrets, key []byte) error { if err := ensureConfigDir(dir); err != nil { return err } plaintext, err := json.Marshal(secrets) if err != nil { return fmt.Errorf("encode secrets: %w", err) } nonce, ciphertext, err := seal(plaintext, key) if err != nil { return err } blob := encryptedBlob{ Nonce: base64.StdEncoding.EncodeToString(nonce), Ciphertext: base64.StdEncoding.EncodeToString(ciphertext), } payload, err := json.MarshalIndent(blob, "", " ") if err != nil { return fmt.Errorf("encode secrets envelope: %w", err) } payload = append(payload, '\n') path := filepath.Join(dir, SecretsFileName) if err := os.WriteFile(path, payload, 0o600); err != nil { return fmt.Errorf("write secrets: %w", err) } return nil }