fix: complete target diagnostics and reset inspection
This commit is contained in:
+69
-1
@@ -34,6 +34,12 @@ const (
|
||||
|
||||
var requiredSchemas = []string{proxySchema, httpSchema, httpsSchema, socksSchema}
|
||||
|
||||
var managedSettings = []struct{ schema, key string }{
|
||||
{proxySchema, "mode"}, {proxySchema, "autoconfig-url"}, {proxySchema, "ignore-hosts"},
|
||||
{httpSchema, "host"}, {httpSchema, "port"}, {httpSchema, "use-authentication"}, {httpSchema, "authentication-user"}, {httpSchema, "authentication-password"},
|
||||
{httpsSchema, "host"}, {httpsSchema, "port"}, {socksSchema, "host"}, {socksSchema, "port"},
|
||||
}
|
||||
|
||||
// Command is the complete command contract used by the adapter. UID, GID, and
|
||||
// Env make user-session identity explicit when proxyctl itself runs under sudo.
|
||||
type Command struct {
|
||||
@@ -230,7 +236,10 @@ type Adapter struct {
|
||||
state StateStore
|
||||
}
|
||||
|
||||
var _ target.Target = (*Adapter)(nil)
|
||||
var (
|
||||
_ target.Target = (*Adapter)(nil)
|
||||
_ target.Diagnosable = (*Adapter)(nil)
|
||||
)
|
||||
|
||||
func New(configuration Configuration, runner CommandRunner, state StateStore) *Adapter {
|
||||
if configuration.CommandName == "" {
|
||||
@@ -271,6 +280,65 @@ func (a *Adapter) Description() target.Description {
|
||||
|
||||
func (a *Adapter) Confirmation(bool) string { return "" }
|
||||
|
||||
// DiagnosticChecks uses only gsettings list/get operations and filesystem
|
||||
// metadata probes. In particular, it never invokes gsettings set or locks or
|
||||
// writes the recovery StateStore.
|
||||
func (a *Adapter) DiagnosticChecks(ctx context.Context, _ *profile.Profile) []target.Check {
|
||||
commandCheck := target.Check{Name: "required command", OK: true, Evidence: a.configuration.CommandName + " is usable for the owning user"}
|
||||
schemaCheck := target.Check{Name: "required schemas", OK: true, Evidence: "all GNOME proxy schemas are present"}
|
||||
sessionCheck := target.Check{Name: "desktop session", OK: true, Evidence: "GNOME proxy settings are readable through the configured session bus"}
|
||||
output, err := a.run(ctx, "list-schemas")
|
||||
if err != nil {
|
||||
commandCheck.OK = false
|
||||
commandCheck.Evidence = profile.RedactText(a.configuration.CommandName + " failed: " + err.Error())
|
||||
schemaCheck.OK = false
|
||||
schemaCheck.Evidence = "GNOME schemas could not be listed"
|
||||
sessionCheck.OK = false
|
||||
sessionCheck.Evidence = "desktop session could not be inspected"
|
||||
} else {
|
||||
present := make(map[string]bool)
|
||||
for _, schema := range strings.Fields(output) {
|
||||
present[schema] = true
|
||||
}
|
||||
var missing []string
|
||||
for _, schema := range requiredSchemas {
|
||||
if !present[schema] {
|
||||
missing = append(missing, schema)
|
||||
}
|
||||
}
|
||||
if len(missing) != 0 {
|
||||
schemaCheck.OK = false
|
||||
schemaCheck.Evidence = "missing GNOME proxy schemas: " + strings.Join(missing, ", ")
|
||||
sessionCheck.OK = false
|
||||
sessionCheck.Evidence = "desktop session settings cannot be checked without required schemas"
|
||||
} else if _, err := a.readSettings(ctx); err != nil {
|
||||
sessionCheck.OK = false
|
||||
sessionCheck.Evidence = profile.RedactText("GNOME proxy settings are unreadable: " + err.Error())
|
||||
}
|
||||
}
|
||||
authorityCheck := target.ReadOnlyWriteAuthority(a.configuration.StateFile)
|
||||
if !commandCheck.OK || !schemaCheck.OK || !sessionCheck.OK {
|
||||
authorityCheck.OK = false
|
||||
authorityCheck.Evidence += "; GNOME setting authority could not be inspected"
|
||||
} else {
|
||||
for _, setting := range managedSettings {
|
||||
output, err := a.run(ctx, "writable", setting.schema, setting.key)
|
||||
if err != nil || strings.TrimSpace(output) != "true" {
|
||||
authorityCheck.OK = false
|
||||
detail := strings.TrimSpace(output)
|
||||
if err != nil {
|
||||
detail = err.Error()
|
||||
}
|
||||
authorityCheck.Evidence += profile.RedactText(fmt.Sprintf("; %s %s is not writable: %s", setting.schema, setting.key, detail))
|
||||
}
|
||||
}
|
||||
if authorityCheck.OK {
|
||||
authorityCheck.Evidence += "; all managed GNOME settings report writable"
|
||||
}
|
||||
}
|
||||
return []target.Check{commandCheck, schemaCheck, sessionCheck, authorityCheck}
|
||||
}
|
||||
|
||||
func (a *Adapter) Availability(ctx context.Context) target.Availability {
|
||||
output, err := a.run(ctx, "list-schemas")
|
||||
if err != nil {
|
||||
|
||||
@@ -55,6 +55,9 @@ func (r *fakeRunner) Run(_ context.Context, c gnome.Command) (string, error) {
|
||||
}
|
||||
return value + "\n", nil
|
||||
}
|
||||
if len(c.Args) == 3 && c.Args[0] == "writable" {
|
||||
return "true\n", nil
|
||||
}
|
||||
if len(c.Args) == 4 && c.Args[0] == "set" {
|
||||
r.values[c.Args[1]+" "+c.Args[2]] = c.Args[3]
|
||||
return "", nil
|
||||
@@ -105,6 +108,29 @@ func active() *profile.Profile {
|
||||
return &profile.Profile{Name: "office", Mode: profile.AdvancedMode, HTTPURL: "http://proxy.example:8080", HTTPSURL: "http://secure.example:8443", AllURL: "socks5://socks.example:1080", Bypass: []string{"localhost", ".example.com", "10.0.0.0/8", "2001:db8::1"}}
|
||||
}
|
||||
|
||||
func TestDiagnosticChecksCoverCommandSchemasSessionAndAuthorityWithoutMutation(t *testing.T) {
|
||||
r, state := newRunner(), &memoryState{}
|
||||
a := adapter(r, state)
|
||||
checks := a.DiagnosticChecks(context.Background(), active())
|
||||
for _, name := range []string{"required command", "required schemas", "desktop session", "write authority"} {
|
||||
found := false
|
||||
for _, check := range checks {
|
||||
found = found || check.Name == name
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("diagnostics missing %q: %#v", name, checks)
|
||||
}
|
||||
}
|
||||
for _, call := range r.calls {
|
||||
if len(call.Args) > 0 && call.Args[0] == "set" {
|
||||
t.Fatalf("diagnostics mutated GNOME settings: %#v", call)
|
||||
}
|
||||
}
|
||||
if state.data != nil {
|
||||
t.Fatalf("diagnostics wrote GNOME recovery state: %q", state.data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGNOMEAdapterRunsReusableTargetContract(t *testing.T) {
|
||||
targettest.Run(t, func(t *testing.T) targettest.Fixture {
|
||||
r, state := newRunner(), &memoryState{}
|
||||
|
||||
Reference in New Issue
Block a user