fix: harden target recovery metadata safety

This commit is contained in:
proxyctl coordinator
2026-07-17 10:59:14 +08:00
parent a5360028ca
commit 613fe56540
7 changed files with 436 additions and 10 deletions
+89
View File
@@ -4,12 +4,14 @@ import (
"bytes"
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"proxyctl/internal/gnome"
"proxyctl/internal/operationlock"
"proxyctl/internal/oplog"
"proxyctl/internal/profile"
@@ -206,6 +208,93 @@ func isolatedProcessEnvironment(root string) []string {
)
}
type passwordEchoRunner struct {
values map[string]string
failPasswordSet bool
}
func newPasswordEchoRunner(savedPassword string) *passwordEchoRunner {
return &passwordEchoRunner{values: map[string]string{
"org.gnome.system.proxy mode": "'auto'", "org.gnome.system.proxy autoconfig-url": "''", "org.gnome.system.proxy ignore-hosts": "[]",
"org.gnome.system.proxy.http host": "''", "org.gnome.system.proxy.http port": "0", "org.gnome.system.proxy.http use-authentication": "false",
"org.gnome.system.proxy.http authentication-user": "''", "org.gnome.system.proxy.http authentication-password": "'" + savedPassword + "'",
"org.gnome.system.proxy.https host": "''", "org.gnome.system.proxy.https port": "0", "org.gnome.system.proxy.socks host": "''", "org.gnome.system.proxy.socks port": "0",
}}
}
func (r *passwordEchoRunner) Run(_ context.Context, command gnome.Command) (string, error) {
if len(command.Args) == 1 && command.Args[0] == "list-schemas" {
return "org.gnome.system.proxy\norg.gnome.system.proxy.http\norg.gnome.system.proxy.https\norg.gnome.system.proxy.socks\n", nil
}
if len(command.Args) == 3 && command.Args[0] == "get" {
return r.values[command.Args[1]+" "+command.Args[2]] + "\n", nil
}
if len(command.Args) == 4 && command.Args[0] == "set" {
if r.failPasswordSet && command.Args[2] == "authentication-password" {
return "", fmt.Errorf("runner echoed saved argument %s: %w", command.Args[3], os.ErrPermission)
}
r.values[command.Args[1]+" "+command.Args[2]] = command.Args[3]
return "", nil
}
return "", fmt.Errorf("unexpected command: %v", command.Args)
}
func passwordRestoreFixture(t *testing.T, secret string) (*gnome.Adapter, *passwordEchoRunner) {
t.Helper()
root := t.TempDir()
runner := newPasswordEchoRunner(secret)
statePath := filepath.Join(root, "state", "gnome.json")
adapter := gnome.New(gnome.Configuration{CommandName: "gsettings", UID: os.Getuid(), GID: os.Getgid(), HomeDir: root, StateFile: statePath}, runner, gnome.NewFileState(statePath, -1, -1, root))
active := &profile.Profile{Name: "office", Mode: profile.AdvancedMode, HTTPURL: "http://proxy.example:8080", Bypass: []string{"localhost"}}
if result := adapter.SetDesired(context.Background(), true, active); result.Err != nil || result.Inspection.State != target.Enabled {
t.Fatalf("prepare managed GNOME Target: %#v", result)
}
runner.failPasswordSet = true
return adapter, runner
}
func TestResetSuppressesSavedGNOMEPasswordFromOperationCLIAndLogsWithoutActiveProfile(t *testing.T) {
const secret = "GNOME-ORIGINAL-PASSWORD-FULL-CHAIN-07"
direct, _ := passwordRestoreFixture(t, secret)
operation := direct.SetDesired(context.Background(), false, nil)
if operation.Err == nil {
t.Fatal("password restore unexpectedly succeeded")
}
if !errors.Is(operation.Err, os.ErrPermission) {
t.Fatalf("OperationResult lost safe permission classification: %v", operation.Err)
}
if rendered := fmt.Sprint(operation); strings.Contains(rendered, secret) || strings.Contains(rendered, "runner echoed") {
t.Fatalf("unsafe OperationResult: %s", rendered)
}
managed, _ := passwordRestoreFixture(t, secret)
root := t.TempDir()
config := filepath.Join(root, "config", "profiles.json")
writeConfig(t, config, `{"profiles":[]}`)
store := profile.NewFileStore(config)
stateDir := filepath.Join(root, "state")
lock := operationlock.New(filepath.Join(stateDir, "operation.lock"))
logger := oplog.New(filepath.Join(stateDir, "operations.log"), 1024, 1)
var output bytes.Buffer
err := runMaintenanceCommand([]string{"reset"}, strings.NewReader("yes\n"), &output, store, []target.Target{managed}, lock, logger, nil)
if err == nil {
t.Fatal("reset unexpectedly succeeded")
}
logData, readErr := os.ReadFile(filepath.Join(stateDir, "operations.log"))
if readErr != nil {
t.Fatal(readErr)
}
for surface, value := range map[string]string{"CLI output": output.String(), "returned error": err.Error(), "operation log": string(logData)} {
if strings.Contains(value, secret) || strings.Contains(value, "runner echoed") {
t.Fatalf("%s exposed saved password or raw runner error: %q", surface, value)
}
if !strings.Contains(value, "sensitive argument and runner error suppressed") {
t.Fatalf("%s lacks fixed safe failure message: %q", surface, value)
}
}
}
func TestResetCommandCancellationAndTruthfulPartialFailureWithFakeTargets(t *testing.T) {
root := t.TempDir()
config := filepath.Join(root, "xdg-config", "proxyctl", "profiles.json")