mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
227 lines
7.3 KiB
Go
227 lines
7.3 KiB
Go
package sandbox
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/safedep/dry/usefulerror"
|
|
"github.com/safedep/pmg/errcodes"
|
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
|
"github.com/safedep/pmg/sandbox/platform"
|
|
)
|
|
|
|
// stubProbe is a minimal probe used by tests.
|
|
type stubProbe struct {
|
|
name string
|
|
result pmgsandbox.ProbeResult
|
|
}
|
|
|
|
func (s *stubProbe) Name() string { return s.name }
|
|
func (s *stubProbe) Run(_ context.Context) pmgsandbox.ProbeResult { return s.result }
|
|
|
|
func newStub(name string, status pmgsandbox.ProbeStatus) pmgsandbox.Probe {
|
|
return &stubProbe{
|
|
name: name,
|
|
result: pmgsandbox.ProbeResult{
|
|
Name: name,
|
|
Status: status,
|
|
Summary: name + " summary",
|
|
Detail: name + " detail",
|
|
Fixes: []pmgsandbox.ProbeFix{{Description: name + " fix", Command: "do thing", Docs: "https://example/" + name}},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestFilterByDriver(t *testing.T) {
|
|
all := []pmgsandbox.Probe{
|
|
newStub("driver.seatbelt.available", pmgsandbox.ProbeStatusOK),
|
|
newStub("driver.bwrap.available", pmgsandbox.ProbeStatusOK),
|
|
newStub("driver.landlock.abi", pmgsandbox.ProbeStatusOK),
|
|
newStub("linux.apparmor.userns", pmgsandbox.ProbeStatusWarn),
|
|
newStub("canary.seatbelt", pmgsandbox.ProbeStatusOK),
|
|
newStub("canary.bubblewrap", pmgsandbox.ProbeStatusOK),
|
|
newStub("canary.landlock", pmgsandbox.ProbeStatusOK),
|
|
}
|
|
|
|
cases := []struct {
|
|
driver string
|
|
want []string
|
|
}{
|
|
{"", []string{
|
|
"driver.seatbelt.available", "driver.bwrap.available", "driver.landlock.abi",
|
|
"linux.apparmor.userns", "canary.seatbelt", "canary.bubblewrap", "canary.landlock",
|
|
}},
|
|
{"seatbelt", []string{"driver.seatbelt.available", "canary.seatbelt"}},
|
|
{"bubblewrap", []string{"driver.bwrap.available", "linux.apparmor.userns", "canary.bubblewrap"}},
|
|
{"landlock", []string{"driver.landlock.abi", "linux.apparmor.userns", "canary.landlock"}},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run("driver="+tc.driver, func(t *testing.T) {
|
|
got := filterByDriver(all, tc.driver)
|
|
names := make([]string, 0, len(got))
|
|
for _, p := range got {
|
|
names = append(names, p.Name())
|
|
}
|
|
assert.Equal(t, tc.want, names)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExitCodeForResults(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
results []pmgsandbox.ProbeResult
|
|
want int
|
|
}{
|
|
{"all ok", []pmgsandbox.ProbeResult{{Status: pmgsandbox.ProbeStatusOK}, {Status: pmgsandbox.ProbeStatusOK}}, 0},
|
|
{"warn is ok", []pmgsandbox.ProbeResult{{Status: pmgsandbox.ProbeStatusOK}, {Status: pmgsandbox.ProbeStatusWarn}}, 0},
|
|
{"skipped is ok", []pmgsandbox.ProbeResult{{Status: pmgsandbox.ProbeStatusSkipped}}, 0},
|
|
{"fail trips", []pmgsandbox.ProbeResult{{Status: pmgsandbox.ProbeStatusOK}, {Status: pmgsandbox.ProbeStatusFail}}, ExitCodeProbeFailure},
|
|
{"empty is ok", nil, 0},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert.Equal(t, tc.want, exitCodeForResults(tc.results))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRenderHuman_ContainsKeySubstrings(t *testing.T) {
|
|
results := []pmgsandbox.ProbeResult{
|
|
{
|
|
Name: "driver.seatbelt.available",
|
|
Status: pmgsandbox.ProbeStatusOK,
|
|
Summary: "sandbox-exec ready",
|
|
},
|
|
{
|
|
Name: "canary.seatbelt",
|
|
Status: pmgsandbox.ProbeStatusFail,
|
|
Summary: "canary blocked",
|
|
Detail: "policy denied read",
|
|
Fixes: []pmgsandbox.ProbeFix{{Description: "Update seatbelt policy", Command: "pmg fix --last", Docs: "https://docs/seatbelt"}},
|
|
},
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
require.NoError(t, renderHuman(&buf, results))
|
|
out := buf.String()
|
|
|
|
assert.Contains(t, out, "STATUS")
|
|
assert.Contains(t, out, "Seatbelt driver")
|
|
assert.Contains(t, out, "Seatbelt canary")
|
|
assert.Contains(t, out, "sandbox-exec ready")
|
|
assert.Contains(t, out, "Update seatbelt policy")
|
|
assert.NotContains(t, out, "driver.seatbelt.available")
|
|
|
|
assert.Contains(t, out, "policy denied read")
|
|
assert.Contains(t, out, "pmg fix --last")
|
|
assert.Contains(t, out, "https://docs/seatbelt")
|
|
}
|
|
|
|
func TestRunDoctor_JSONRoundtrip(t *testing.T) {
|
|
factory := func() []pmgsandbox.Probe {
|
|
return []pmgsandbox.Probe{
|
|
newStub("driver.seatbelt.available", pmgsandbox.ProbeStatusOK),
|
|
newStub("canary.seatbelt", pmgsandbox.ProbeStatusFail),
|
|
}
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
opts := &doctorOptions{jsonOut: true}
|
|
err := runDoctor(context.Background(), &buf, opts, factory)
|
|
|
|
require.Error(t, err)
|
|
_, ok := err.(*doctorFailError)
|
|
require.True(t, ok, "expected doctorFailError, got %T", err)
|
|
|
|
var report jsonReport
|
|
require.NoError(t, json.Unmarshal(buf.Bytes(), &report))
|
|
require.Len(t, report.Results, 2)
|
|
assert.Equal(t, "driver.seatbelt.available", report.Results[0].Name)
|
|
assert.Equal(t, "ok", report.Results[0].Status)
|
|
assert.Equal(t, "canary.seatbelt", report.Results[1].Name)
|
|
assert.Equal(t, "fail", report.Results[1].Status)
|
|
assert.Equal(t, "canary.seatbelt fix", report.Results[1].Fixes[0].Description)
|
|
}
|
|
|
|
func TestRunDoctor_HumanSuccess(t *testing.T) {
|
|
factory := func() []pmgsandbox.Probe {
|
|
return []pmgsandbox.Probe{newStub("driver.seatbelt.available", pmgsandbox.ProbeStatusOK)}
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err := runDoctor(context.Background(), &buf, &doctorOptions{}, factory)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, buf.String(), "Seatbelt driver")
|
|
}
|
|
|
|
func TestRunDoctor_UnknownDriver(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
err := runDoctor(context.Background(), &buf, &doctorOptions{driver: "bogus"}, platform.DefaultProbes)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "unknown driver")
|
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, errcodes.InvalidArgument, usefulErr.Code())
|
|
}
|
|
|
|
func TestDoctorCommandRejectsUnexpectedArgsWithUsage(t *testing.T) {
|
|
cmd := newDoctorCommand(func() []pmgsandbox.Probe { return nil })
|
|
var stdout, stderr bytes.Buffer
|
|
cmd.SetOut(&stdout)
|
|
cmd.SetErr(&stderr)
|
|
cmd.SetArgs([]string{"extra"})
|
|
|
|
err := cmd.Execute()
|
|
require.Error(t, err)
|
|
assert.Contains(t, stderr.String(), "unknown command")
|
|
assert.Contains(t, stdout.String(), "Usage:")
|
|
assert.Contains(t, stdout.String(), "doctor [flags]")
|
|
assert.Contains(t, stdout.String(), "pmg sandbox doctor --driver landlock")
|
|
}
|
|
|
|
func TestDoctorCommandRuntimeErrorUsesSandboxErrorExit(t *testing.T) {
|
|
cmd := newDoctorCommand(func() []pmgsandbox.Probe { return nil })
|
|
var stdout, stderr bytes.Buffer
|
|
cmd.SetOut(&stdout)
|
|
cmd.SetErr(&stderr)
|
|
cmd.SetArgs([]string{"--driver", "bogus"})
|
|
|
|
err := cmd.Execute()
|
|
require.Error(t, err)
|
|
assert.Empty(t, stdout.String())
|
|
assert.Empty(t, stderr.String())
|
|
assert.Contains(t, err.Error(), "unknown driver")
|
|
}
|
|
|
|
func TestRunDoctor_DriverFilter(t *testing.T) {
|
|
factory := func() []pmgsandbox.Probe {
|
|
return []pmgsandbox.Probe{
|
|
newStub("driver.seatbelt.available", pmgsandbox.ProbeStatusOK),
|
|
newStub("driver.bwrap.available", pmgsandbox.ProbeStatusOK),
|
|
newStub("canary.bubblewrap", pmgsandbox.ProbeStatusOK),
|
|
}
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err := runDoctor(context.Background(), &buf, &doctorOptions{driver: "bubblewrap"}, factory)
|
|
require.NoError(t, err)
|
|
out := buf.String()
|
|
assert.Contains(t, out, "Bubblewrap driver")
|
|
assert.Contains(t, out, "Bubblewrap canary")
|
|
assert.False(t, strings.Contains(out, "Seatbelt driver"), "seatbelt should be filtered out:\n%s", out)
|
|
}
|
|
|
|
func TestDoctorFailError_ExitCode(t *testing.T) {
|
|
e := &doctorFailError{}
|
|
assert.Equal(t, ExitCodeProbeFailure, e.ExitCode())
|
|
}
|