Files
privatae-bastion/client-go/ops_view_test.go
T
Privatae e4c9b69b46
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m4s
ci / fuzz (push) Successful in 3m45s
bastion 0.53.2
2026-10-20 04:42:14 +00:00

119 lines
4.3 KiB
Go

//go:build !windows
package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestOpViewDecideKeepsTheBaseAndWhatIsDeclaredAndCoversTheRest(t *testing.T) {
m := &opManifest{Effects: opEffects{Files: []string{"/srv/app/"}}, Reads: []string{"/etc/nginx/*.conf"}}
segs := opViewSegments(opViewVisible(opViewDeclared(m)))
for p, want := range map[string]opViewAction{
"/usr/bin/python3": opViewKeep,
"/usr": opViewDescend, // /usr/local/sbin and friends are covered
"/usr/local/sbin": opViewHide,
"/etc": opViewDescend,
"/etc/passwd": opViewKeep,
"/etc/shadow": opViewHide,
"/etc/ssl": opViewDescend,
"/etc/ssl/certs/ca.pem": opViewKeep,
"/etc/ssl/private": opViewHide,
"/etc/nginx": opViewDescend,
"/etc/nginx/site.conf": opViewKeep,
"/etc/nginx/site.key": opViewHide,
"/srv": opViewDescend,
"/srv/app": opViewKeep,
"/srv/app/deep/file": opViewKeep,
"/srv/other": opViewHide,
"/root": opViewHide,
"/home": opViewHide,
"/var": opViewDescend, // for /var/tmp
"/var/tmp": opViewKeep,
"/var/lib": opViewHide,
"/opt": opViewDescend, // for the op's own directory
"/opt/bastion-op/run": opViewKeep,
"/opt/acme": opViewHide,
"/proc/1/environ": opViewKeep, // a fresh proc of the op's own pid namespace
"/boot": opViewHide,
"/etc/systemd/system/x.sh": opViewHide,
} {
if got := opViewDecide(p, segs); got != want {
t.Errorf("%s: got %d, want %d", p, got, want)
}
}
}
func TestOpSensitivePathsAreRefusedAtUnderOrAboveAndThroughLinks(t *testing.T) {
setupBox(t)
for _, p := range []string{
"/etc/shadow", "/etc/", "/etc/*", "/etc/ssh/sshd_config", "/etc/systemd/system/app.service",
"/root/.git-credentials", "/home/alice/.ssh/authorized_keys", "/home/*/.docker/config.json",
"/etc/wireguard/wg0.conf", "/var/lib/rancher/k3s/agent/client.key", "/etc/privatae/config.json",
"/var/lib/", "/home/",
} {
sens, err := opSensitiveMatch(p)
if err != nil {
t.Fatal(err)
}
if sens == "" {
t.Errorf("%s must reach a sensitive path", p)
}
// Refused either as sensitive or, above the record (/etc/, /var/lib/),
// by the record's own rule first — refused either way.
if err := effectOK(p); err == nil {
t.Errorf("%s: effectOK must refuse it", p)
}
}
for _, p := range []string{"/etc/nginx/", "/etc/*.conf", "/srv/app/", "/opt/acme/app/main.py", "/home/alice/site/"} {
sens, err := opSensitiveMatch(p)
if err != nil {
t.Fatal(err)
}
if sens != "" {
t.Errorf("%s reaches no credential store, got %s", p, sens)
}
}
// A plain path that is a link onto a store the person named is refused as
// what it resolves to.
store := t.TempDir()
cfg, _ := configDirPath()
_ = os.MkdirAll(cfg, 0o700)
if err := os.WriteFile(filepath.Join(cfg, opSensitiveFileName), []byte("# ours\n"+store+" # the vault\n"), 0o600); err != nil {
t.Fatal(err)
}
link := filepath.Join(t.TempDir(), "innocent")
if err := os.Symlink(store, link); err != nil {
t.Fatal(err)
}
if sens, _ := opSensitiveMatch(link + "/token"); sens == "" {
t.Fatal("a link onto a sensitive path is refused as what it names")
}
// A line that is not an absolute path refuses every op rather than drop
// the store the person meant to name.
_ = os.WriteFile(filepath.Join(cfg, opSensitiveFileName), []byte("relative/path\n"), 0o600)
if _, err := opSensitiveMatch("/srv/app/"); err == nil {
t.Fatal("a malformed sensitive_paths refuses every op")
}
}
func TestOpManifestReadsAreJudgedLikeEffects(t *testing.T) {
setupBox(t)
base := strings.Replace(opTestManifest, "modes:", "reads: [%s]\nmodes:", 1)
for read, ok := range map[string]bool{
`"/etc/nginx/site.conf"`: true,
`"/etc/shadow"`: false,
`"relative"`: false,
`"/proc/1/environ"`: false,
`"/"`: false,
} {
_, err := parseOpManifest([]byte(strings.Replace(strings.Replace(base, "%s/", "/srv/work/optest/x/", 1), "%s", read, 1)))
if (err == nil) != ok {
t.Errorf("reads %s: ok=%v err=%v", read, ok, err)
}
}
}