fix: harden operations and configuration recovery

This commit is contained in:
proxyctl coordinator
2026-08-30 11:13:45 +08:00
parent e34bf7d989
commit 8928986830
27 changed files with 542 additions and 125 deletions
+29 -17
View File
@@ -329,6 +329,7 @@ func (a *Adapter) enable(active *profile.Profile) error {
if err != nil {
return fmt.Errorf("read Docker client configuration for safe JSON merge: %w", err)
}
observedConfig := fingerprintDocument(config, configPresent)
metadata, managed, recoveryErr := a.readRecovery()
if recoveryErr != nil && !errors.Is(recoveryErr, fs.ErrNotExist) {
return fmt.Errorf("read Docker client recovery metadata: %w", recoveryErr)
@@ -362,6 +363,9 @@ func (a *Adapter) enable(active *profile.Profile) error {
applyDesired(defaults, desired)
setChildObject(proxies, "default", defaults)
setChildObject(config, "proxies", proxies)
if err := a.requireConfigUnchanged(observedConfig); err != nil {
return err
}
if err := a.writeConfig(config); err != nil {
return fmt.Errorf("write Docker client proxy defaults after recovery metadata persisted: %w", err)
}
@@ -380,6 +384,7 @@ func (a *Adapter) disable() error {
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("read Docker client configuration for restoration: %w", err)
}
observedConfig := fingerprintDocument(config, configPresent)
if !configPresent {
config = make(map[string]json.RawMessage)
}
@@ -422,6 +427,9 @@ func (a *Adapter) disable() error {
} else {
setChildObject(config, "proxies", proxies)
}
if err := a.requireConfigUnchanged(observedConfig); err != nil {
return err
}
if len(config) == 0 && !metadata.ConfigPresent {
if err := a.files.Remove(a.configuration.ConfigFile); err != nil && !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("remove newly created empty Docker client configuration: %w", err)
@@ -543,23 +551,6 @@ func applyDesired(defaults map[string]json.RawMessage, desired map[string]string
}
}
}
func recognizesProxyDefaults(config map[string]json.RawMessage) bool {
proxies, _, err := childObject(config, "proxies")
if err != nil {
return false
}
defaults, _, err := childObject(proxies, "default")
if err != nil {
return false
}
for _, key := range append(append([]string(nil), managedKeys...), "ftpProxy") {
var value string
if raw, ok := defaults[key]; ok && json.Unmarshal(raw, &value) == nil && value != "" {
return true
}
}
return false
}
type managedValue struct {
Present bool `json:"present"`
@@ -612,6 +603,27 @@ func fingerprintSnapshot(snapshot map[string]managedValue) string {
sum := sha256.Sum256(data)
return hex.EncodeToString(sum[:])
}
func fingerprintDocument(config map[string]json.RawMessage, present bool) string {
data, _ := json.Marshal(struct {
Present bool `json:"present"`
Config map[string]json.RawMessage `json:"config"`
}{Present: present, Config: config})
sum := sha256.Sum256(data)
return hex.EncodeToString(sum[:])
}
func (a *Adapter) requireConfigUnchanged(expected string) error {
current, present, err := a.readConfig()
if err != nil {
return fmt.Errorf("re-read Docker client configuration before atomic replacement: %w", err)
}
if fingerprintDocument(current, present) != expected {
return errors.New("Docker client configuration changed concurrently; refusing to overwrite external updates")
}
return nil
}
func cloneRaw(value json.RawMessage) json.RawMessage { return append(json.RawMessage(nil), value...) }
func (a *Adapter) ownership() *Ownership {
if !a.configuration.OwnFiles {