mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
* feat: Add support for network bind * chore: Add comments for bwrap sandbox
660 lines
21 KiB
Go
660 lines
21 KiB
Go
//go:build darwin
|
|
// +build darwin
|
|
|
|
package platform
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/safedep/dry/utils"
|
|
"github.com/safedep/pmg/sandbox"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestSeatbeltTranslatorDarwinCommonTranslation(t *testing.T) {
|
|
policy := &sandbox.SandboxPolicy{
|
|
Name: "test",
|
|
Description: "test",
|
|
PackageManagers: []string{"npm"},
|
|
Filesystem: sandbox.FilesystemPolicy{
|
|
AllowRead: []string{"/tmp"},
|
|
AllowWrite: []string{"/tmp"},
|
|
DenyRead: []string{"/private/var"},
|
|
DenyWrite: []string{"/private/var"},
|
|
},
|
|
Network: sandbox.NetworkPolicy{
|
|
AllowOutbound: []string{"*:*"},
|
|
},
|
|
Process: sandbox.ProcessPolicy{
|
|
AllowExec: []string{"/bin/sh"},
|
|
DenyExec: []string{"/bin/bash"},
|
|
},
|
|
}
|
|
|
|
translator := newSeatbeltPolicyTranslator()
|
|
actual, err := translator.translate(policy)
|
|
assert.NoError(t, err)
|
|
|
|
// Test common translation
|
|
assert.Contains(t, actual, "(version 1)")
|
|
assert.Contains(t, actual, fmt.Sprintf(";; PMG Sandbox Policy: %s", policy.Name))
|
|
assert.Contains(t, actual, fmt.Sprintf(";; %s", policy.Description))
|
|
assert.Contains(t, actual, ";; Generated by PMG sandbox system")
|
|
|
|
// Test deny default - it should contain a message tag
|
|
assert.Contains(t, actual, "(deny default (with message")
|
|
|
|
// Allow reading /dev and /etc
|
|
assert.Contains(t, actual, "(allow file-read* (subpath \"/dev\"))")
|
|
assert.Contains(t, actual, "(allow file-read* (subpath \"/etc\"))")
|
|
|
|
// Allow process fork
|
|
assert.Contains(t, actual, "(allow process-fork)")
|
|
}
|
|
|
|
func TestSeatbeltTranslatorDarwinFilesystemTranslation(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
policy *sandbox.SandboxPolicy
|
|
assert func(t *testing.T, actual string, err error)
|
|
}{
|
|
{
|
|
name: "simple path",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Filesystem: sandbox.FilesystemPolicy{
|
|
AllowRead: []string{"/tmp"},
|
|
AllowWrite: []string{"/tmp"},
|
|
DenyRead: []string{"/private/var"},
|
|
DenyWrite: []string{"/private/var"},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, actual, "(allow file-read* (subpath \"/tmp\"))")
|
|
assert.Contains(t, actual, "(allow file-write* (subpath \"/tmp\"))")
|
|
assert.Contains(t, actual, "(deny file-read* (subpath \"/private/var\") (with message")
|
|
assert.Contains(t, actual, "(deny file-write* (subpath \"/private/var\") (with message")
|
|
},
|
|
},
|
|
{
|
|
name: "glob pattern with /**",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Filesystem: sandbox.FilesystemPolicy{
|
|
AllowRead: []string{"/path/to/dir/**"},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
// Should generate literal rule for parent directory
|
|
assert.Contains(t, actual, `(allow file-read* (literal "/path/to/dir"))`)
|
|
// Should use regex matching for glob patterns
|
|
assert.Contains(t, actual, "(allow file-read* (regex #")
|
|
assert.Contains(t, actual, "^/path/to/dir/.*$")
|
|
},
|
|
},
|
|
{
|
|
name: "glob pattern with *.txt",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Filesystem: sandbox.FilesystemPolicy{
|
|
AllowRead: []string{"/path/to/*.txt"},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
// Should use regex matching for glob patterns
|
|
assert.Contains(t, actual, "(allow file-read* (regex #")
|
|
assert.Contains(t, actual, `^/path/to/[^/]*\.txt$`)
|
|
},
|
|
},
|
|
{
|
|
name: "glob pattern with ? wildcard",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Filesystem: sandbox.FilesystemPolicy{
|
|
AllowWrite: []string{"/path/to/file?.log"},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
// Should use regex matching for glob patterns
|
|
assert.Contains(t, actual, "(allow file-write* (regex #")
|
|
assert.Contains(t, actual, `^/path/to/file[^/]\.log$`)
|
|
},
|
|
},
|
|
{
|
|
name: "glob pattern in middle of path",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Filesystem: sandbox.FilesystemPolicy{
|
|
DenyRead: []string{"/path/*/subdir"},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
// Should use regex matching for glob patterns
|
|
assert.Contains(t, actual, "(deny file-read* (regex #")
|
|
assert.Contains(t, actual, `^/path/[^/]*/subdir$`)
|
|
},
|
|
},
|
|
{
|
|
name: "glob pattern with bracket wildcard",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Filesystem: sandbox.FilesystemPolicy{
|
|
AllowRead: []string{"/tmp/test[123].txt"},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
// Should use regex matching for glob patterns
|
|
assert.Contains(t, actual, "(allow file-read* (regex #")
|
|
assert.Contains(t, actual, `^/tmp/test[123]\.txt$`)
|
|
},
|
|
},
|
|
{
|
|
name: "write glob pattern ending with /** auto-allows parent dir",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Filesystem: sandbox.FilesystemPolicy{
|
|
AllowWrite: []string{"/path/to/dir/**"},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
// Should generate literal rule for parent directory
|
|
assert.Contains(t, actual, `(allow file-write* (literal "/path/to/dir"))`)
|
|
// Should also generate regex rule for contents
|
|
assert.Contains(t, actual, `(allow file-write* (regex #"^/path/to/dir/.*$"))`)
|
|
},
|
|
},
|
|
{
|
|
name: "write glob pattern with ** in middle not auto-allowed",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Filesystem: sandbox.FilesystemPolicy{
|
|
AllowWrite: []string{"/path/**/subdir"},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
// Should only generate regex rule, no literal parent
|
|
assert.Contains(t, actual, `(allow file-write* (regex #`)
|
|
assert.NotContains(t, actual, `(allow file-write* (literal "/path")`)
|
|
},
|
|
},
|
|
{
|
|
name: "write glob pattern with variable expansion and /**",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Filesystem: sandbox.FilesystemPolicy{
|
|
AllowWrite: []string{"${HOME}/.npm/**"},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
homeDir := os.Getenv("HOME")
|
|
expectedLiteral := fmt.Sprintf(`(allow file-write* (literal "%s/.npm"))`, homeDir)
|
|
// Check for the literal parent directory rule
|
|
assert.Contains(t, actual, expectedLiteral)
|
|
// Check for the regex rule (escaped dot)
|
|
assert.Contains(t, actual, `\.npm/.*$`)
|
|
},
|
|
},
|
|
{
|
|
name: "multiple /** patterns all auto-allow parents",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Filesystem: sandbox.FilesystemPolicy{
|
|
AllowWrite: []string{
|
|
"/path/one/**",
|
|
"/path/two/**",
|
|
"/path/three/**",
|
|
},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, actual, `(allow file-write* (literal "/path/one"))`)
|
|
assert.Contains(t, actual, `(allow file-write* (literal "/path/two"))`)
|
|
assert.Contains(t, actual, `(allow file-write* (literal "/path/three"))`)
|
|
},
|
|
},
|
|
{
|
|
name: "edge case /** pattern at root",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Filesystem: sandbox.FilesystemPolicy{
|
|
AllowWrite: []string{"/**"},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
// Should allow writing to root (empty string after trimming becomes "/")
|
|
assert.Contains(t, actual, `(allow file-write* (literal "/"))`)
|
|
assert.Contains(t, actual, `(allow file-write* (regex #"^/.*$"))`)
|
|
},
|
|
},
|
|
{
|
|
name: "dangerous file paths are blocked by default",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Filesystem: sandbox.FilesystemPolicy{
|
|
DenyRead: []string{"/tmp/does-not-exist"},
|
|
DenyWrite: []string{"/tmp/does-not-exist"},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
|
|
homeDir, err := os.UserHomeDir()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Contains(t, actual, fmt.Sprintf("(deny file-read* (subpath \"%s/.ssh\") (with message", homeDir))
|
|
assert.Contains(t, actual, fmt.Sprintf("(deny file-write* (subpath \"%s/.ssh\") (with message", homeDir))
|
|
assert.Contains(t, actual, fmt.Sprintf("(deny file-read* (subpath \"%s/.kube\") (with message", homeDir))
|
|
assert.Contains(t, actual, fmt.Sprintf("(deny file-write* (subpath \"%s/.kube\") (with message", homeDir))
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
translator := newSeatbeltPolicyTranslator()
|
|
actual, err := translator.translate(tt.policy)
|
|
tt.assert(t, actual, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSeatbeltTranslatorDarwinProcessTranslation(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
policy *sandbox.SandboxPolicy
|
|
assert func(t *testing.T, actual string, err error)
|
|
}{
|
|
{
|
|
name: "literal exec path",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Process: sandbox.ProcessPolicy{
|
|
AllowExec: []string{"/bin/sh"},
|
|
DenyExec: []string{"/bin/bash"},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, actual, "(allow process-exec* (literal \"/bin/sh\"))")
|
|
assert.Contains(t, actual, "(deny process-exec* (literal \"/bin/bash\") (with message")
|
|
},
|
|
},
|
|
{
|
|
name: "glob pattern with /** for exec",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Process: sandbox.ProcessPolicy{
|
|
AllowExec: []string{"/usr/local/bin/**"},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
// Should use regex matching for glob patterns
|
|
assert.Contains(t, actual, "(allow process-exec* (regex #")
|
|
assert.Contains(t, actual, "^/usr/local/bin/.*$")
|
|
},
|
|
},
|
|
{
|
|
name: "glob pattern with * wildcard for exec",
|
|
policy: &sandbox.SandboxPolicy{
|
|
Process: sandbox.ProcessPolicy{
|
|
AllowExec: []string{"/usr/bin/python*"},
|
|
},
|
|
},
|
|
assert: func(t *testing.T, actual string, err error) {
|
|
assert.NoError(t, err)
|
|
// Should use regex matching for glob patterns
|
|
assert.Contains(t, actual, "(allow process-exec* (regex #")
|
|
assert.Contains(t, actual, `^/usr/bin/python[^/]*$`)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
translator := newSeatbeltPolicyTranslator()
|
|
actual, err := translator.translate(tt.policy)
|
|
tt.assert(t, actual, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtractBaseDir(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
pattern string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "no glob pattern",
|
|
pattern: "/path/to/file",
|
|
expected: "/path/to/file",
|
|
},
|
|
{
|
|
name: "glob pattern with /**",
|
|
pattern: "/path/to/**",
|
|
expected: "/path/to",
|
|
},
|
|
{
|
|
name: "glob pattern with *.txt",
|
|
pattern: "/path/to/*.txt",
|
|
expected: "/path/to",
|
|
},
|
|
{
|
|
name: "glob pattern with ? wildcard",
|
|
pattern: "/path/to/file?.log",
|
|
expected: "/path/to",
|
|
},
|
|
{
|
|
name: "glob pattern in middle of path",
|
|
pattern: "/path/*/subdir",
|
|
expected: "/path",
|
|
},
|
|
{
|
|
name: "glob pattern with bracket wildcard",
|
|
pattern: "/tmp/test[123].txt",
|
|
expected: "/tmp",
|
|
},
|
|
{
|
|
name: "glob pattern at root",
|
|
pattern: "/*.txt",
|
|
expected: "/",
|
|
},
|
|
{
|
|
name: "multiple glob patterns",
|
|
pattern: "/path/*/sub/*.txt",
|
|
expected: "/path",
|
|
},
|
|
{
|
|
name: "complex glob with multiple wildcards",
|
|
pattern: "/usr/bin/python*",
|
|
expected: "/usr/bin",
|
|
},
|
|
}
|
|
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
actual := extractBaseDir(tt.pattern)
|
|
assert.Equal(t, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetAncestorDirectories(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
path string
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "deep path",
|
|
path: "/private/tmp/test/file.txt",
|
|
expected: []string{"/private/tmp/test", "/private/tmp", "/private"},
|
|
},
|
|
{
|
|
name: "two level path",
|
|
path: "/tmp/file.txt",
|
|
expected: []string{"/tmp"},
|
|
},
|
|
{
|
|
name: "root level path",
|
|
path: "/file.txt",
|
|
expected: []string{},
|
|
},
|
|
{
|
|
name: "directory path",
|
|
path: "/usr/local/bin",
|
|
expected: []string{"/usr/local", "/usr"},
|
|
},
|
|
{
|
|
name: "path with trailing slash",
|
|
path: "/path/to/dir/",
|
|
expected: []string{"/path/to/dir", "/path/to", "/path"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
actual := getAncestorDirectories(tt.path)
|
|
assert.Equal(t, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateMoveBlockingRules(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
patterns []string
|
|
logTag string
|
|
assert func(t *testing.T, rules []string)
|
|
}{
|
|
{
|
|
name: "single literal path",
|
|
patterns: []string{"/sensitive/data"},
|
|
logTag: "test",
|
|
assert: func(t *testing.T, rules []string) {
|
|
// Should block moving the path itself
|
|
assert.Contains(t, rules, "(deny file-write-unlink (subpath \"/sensitive/data\") (with message \"test\"))")
|
|
// Should block moving the parent directory
|
|
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/sensitive\") (with message \"test\"))")
|
|
},
|
|
},
|
|
{
|
|
name: "glob pattern",
|
|
patterns: []string{"/path/to/*.txt"},
|
|
logTag: "test",
|
|
assert: func(t *testing.T, rules []string) {
|
|
// Should block moving the base directory
|
|
assert.Contains(t, rules, "(deny file-write-unlink (subpath \"/path/to\") (with message \"test\"))")
|
|
// Should block moving ancestor directories
|
|
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/path\") (with message \"test\"))")
|
|
},
|
|
},
|
|
{
|
|
name: "multiple paths",
|
|
patterns: []string{"/tmp/test", "/var/log/app"},
|
|
logTag: "test",
|
|
assert: func(t *testing.T, rules []string) {
|
|
assert.Contains(t, rules, "(deny file-write-unlink (subpath \"/tmp/test\") (with message \"test\"))")
|
|
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/tmp\") (with message \"test\"))")
|
|
assert.Contains(t, rules, "(deny file-write-unlink (subpath \"/var/log/app\") (with message \"test\"))")
|
|
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/var/log\") (with message \"test\"))")
|
|
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/var\") (with message \"test\"))")
|
|
},
|
|
},
|
|
{
|
|
name: "deep nested path",
|
|
patterns: []string{"/a/b/c/d/e/file.txt"},
|
|
logTag: "test",
|
|
assert: func(t *testing.T, rules []string) {
|
|
// Should have rules for all ancestors
|
|
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/a/b/c/d/e\") (with message \"test\"))")
|
|
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/a/b/c/d\") (with message \"test\"))")
|
|
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/a/b/c\") (with message \"test\"))")
|
|
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/a/b\") (with message \"test\"))")
|
|
assert.Contains(t, rules, "(deny file-write-unlink (literal \"/a\") (with message \"test\"))")
|
|
},
|
|
},
|
|
{
|
|
name: "root level path",
|
|
patterns: []string{"/file"},
|
|
logTag: "test",
|
|
assert: func(t *testing.T, rules []string) {
|
|
// Should only have the file itself, no ancestors
|
|
assert.Contains(t, rules, "(deny file-write-unlink (subpath \"/file\") (with message \"test\"))")
|
|
// Should not contain root as ancestor
|
|
for _, rule := range rules {
|
|
assert.NotContains(t, rule, "(deny file-write-unlink (literal \"/\"))")
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rules := generateMoveBlockingRules(tt.patterns, tt.logTag)
|
|
tt.assert(t, rules)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFilesystemTranslationWithMoveProtection(t *testing.T) {
|
|
policy := &sandbox.SandboxPolicy{
|
|
Name: "test",
|
|
Description: "test with move protection",
|
|
PackageManagers: []string{"npm"},
|
|
Filesystem: sandbox.FilesystemPolicy{
|
|
DenyRead: []string{"/private/sensitive"},
|
|
DenyWrite: []string{"/usr/local/bin"},
|
|
},
|
|
}
|
|
|
|
translator := newSeatbeltPolicyTranslator()
|
|
translator.enableMoveBlockingMitigation = true
|
|
|
|
actual, err := translator.translate(policy)
|
|
assert.NoError(t, err)
|
|
|
|
// Should contain deny read rule
|
|
assert.Contains(t, actual, "(deny file-read* (subpath \"/private/sensitive\") (with message")
|
|
|
|
// Should contain move protection for deny read
|
|
assert.Contains(t, actual, ";; Prevent bypassing read restrictions via file movement")
|
|
assert.Contains(t, actual, "(deny file-write-unlink (subpath \"/private/sensitive\") (with message")
|
|
assert.Contains(t, actual, "(deny file-write-unlink (literal \"/private\") (with message")
|
|
|
|
// Should contain deny write rule
|
|
assert.Contains(t, actual, "(deny file-write* (subpath \"/usr/local/bin\") (with message")
|
|
|
|
// Should contain move protection for deny write
|
|
assert.Contains(t, actual, ";; Prevent bypassing write restrictions via file movement")
|
|
assert.Contains(t, actual, "(deny file-write-unlink (subpath \"/usr/local/bin\") (with message")
|
|
assert.Contains(t, actual, "(deny file-write-unlink (literal \"/usr/local\") (with message")
|
|
assert.Contains(t, actual, "(deny file-write-unlink (literal \"/usr\") (with message")
|
|
}
|
|
|
|
func TestPTYSupport(t *testing.T) {
|
|
t.Run("PTY disabled by default", func(t *testing.T) {
|
|
policy := &sandbox.SandboxPolicy{
|
|
Name: "test",
|
|
Description: "test without PTY",
|
|
PackageManagers: []string{"npm"},
|
|
AllowPTY: utils.PtrTo(false),
|
|
}
|
|
|
|
translator := newSeatbeltPolicyTranslator()
|
|
actual, err := translator.translate(policy)
|
|
assert.NoError(t, err)
|
|
|
|
// Should NOT contain PTY rules
|
|
assert.NotContains(t, actual, "(allow pseudo-tty)")
|
|
assert.NotContains(t, actual, "/dev/ptmx")
|
|
})
|
|
|
|
t.Run("PTY enabled when requested", func(t *testing.T) {
|
|
policy := &sandbox.SandboxPolicy{
|
|
Name: "test",
|
|
Description: "test with PTY",
|
|
PackageManagers: []string{"npm"},
|
|
AllowPTY: utils.PtrTo(true),
|
|
}
|
|
|
|
translator := newSeatbeltPolicyTranslator()
|
|
actual, err := translator.translate(policy)
|
|
assert.NoError(t, err)
|
|
|
|
// Should contain PTY rules
|
|
assert.Contains(t, actual, ";; Pseudo-terminal (PTY) support")
|
|
assert.Contains(t, actual, "(allow pseudo-tty)")
|
|
assert.Contains(t, actual, "(allow file-ioctl")
|
|
assert.Contains(t, actual, "(literal \"/dev/ptmx\")")
|
|
assert.Contains(t, actual, "(regex #\"^/dev/ttys\")")
|
|
assert.Contains(t, actual, "(allow file-read* file-write*")
|
|
})
|
|
}
|
|
|
|
func TestNetworkBindSupport(t *testing.T) {
|
|
t.Run("AllowNetworkBind true with no AllowBind generates localhost-only bind rule", func(t *testing.T) {
|
|
policy := &sandbox.SandboxPolicy{
|
|
Name: "test",
|
|
Description: "test with network bind",
|
|
PackageManagers: []string{"npm"},
|
|
AllowNetworkBind: utils.PtrTo(true),
|
|
}
|
|
|
|
translator := newSeatbeltPolicyTranslator()
|
|
actual, err := translator.translate(policy)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Contains(t, actual, ";; Local network bind (localhost only)")
|
|
assert.Contains(t, actual, `(allow network-bind (local ip "localhost:*"))`)
|
|
assert.Contains(t, actual, `(allow network* (local ip "localhost:*"))`)
|
|
// Should not contain any explicit bind addresses
|
|
assert.NotContains(t, actual, `(allow network-bind (local ip "0.0.0.0:*"))`)
|
|
})
|
|
|
|
t.Run("AllowNetworkBind true with AllowBind generates both rules", func(t *testing.T) {
|
|
policy := &sandbox.SandboxPolicy{
|
|
Name: "test",
|
|
Description: "test with network bind and explicit addresses",
|
|
PackageManagers: []string{"npm"},
|
|
AllowNetworkBind: utils.PtrTo(true),
|
|
Network: sandbox.NetworkPolicy{
|
|
AllowBind: []string{"0.0.0.0:*"},
|
|
},
|
|
}
|
|
|
|
translator := newSeatbeltPolicyTranslator()
|
|
actual, err := translator.translate(policy)
|
|
assert.NoError(t, err)
|
|
|
|
// Localhost bind from AllowNetworkBind
|
|
assert.Contains(t, actual, `(allow network-bind (local ip "localhost:*"))`)
|
|
// Explicit bind address
|
|
assert.Contains(t, actual, `(allow network-bind (local ip "0.0.0.0:*"))`)
|
|
})
|
|
|
|
t.Run("AllowNetworkBind false with AllowBind generates only explicit bind rule", func(t *testing.T) {
|
|
policy := &sandbox.SandboxPolicy{
|
|
Name: "test",
|
|
Description: "test with explicit bind only",
|
|
PackageManagers: []string{"npm"},
|
|
AllowNetworkBind: utils.PtrTo(false),
|
|
Network: sandbox.NetworkPolicy{
|
|
AllowBind: []string{"0.0.0.0:*"},
|
|
},
|
|
}
|
|
|
|
translator := newSeatbeltPolicyTranslator()
|
|
actual, err := translator.translate(policy)
|
|
assert.NoError(t, err)
|
|
|
|
// Should NOT contain localhost bind
|
|
assert.NotContains(t, actual, ";; Local network bind (localhost only)")
|
|
assert.NotContains(t, actual, `(allow network-bind (local ip "localhost:*"))`)
|
|
// Should contain explicit bind address
|
|
assert.Contains(t, actual, `(allow network-bind (local ip "0.0.0.0:*"))`)
|
|
})
|
|
|
|
t.Run("AllowNetworkBind nil with no AllowBind generates no bind rules", func(t *testing.T) {
|
|
policy := &sandbox.SandboxPolicy{
|
|
Name: "test",
|
|
Description: "test without network bind",
|
|
PackageManagers: []string{"npm"},
|
|
}
|
|
|
|
translator := newSeatbeltPolicyTranslator()
|
|
actual, err := translator.translate(policy)
|
|
assert.NoError(t, err)
|
|
|
|
assert.NotContains(t, actual, "network-bind")
|
|
})
|
|
}
|
|
|
|
func TestSeatbeltTranslatorDarwinLogTag(t *testing.T) {
|
|
translator := newSeatbeltPolicyTranslator()
|
|
assert.NotEmpty(t, translator.LogTag())
|
|
|
|
translator = &seatbeltPolicyTranslator{logTag: "test"}
|
|
assert.Equal(t, "test", translator.LogTag())
|
|
}
|