mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat: Add support for sandbox allow override (#165)
* feat: Add support for sandbox allow override * fix: Main should fail on arg processing error * fix: Remove redundant policy conflict check
This commit is contained in:
@@ -7,7 +7,9 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/safedep/dry/log"
|
||||
"github.com/safedep/dry/utils"
|
||||
"github.com/safedep/pmg/config"
|
||||
"github.com/safedep/pmg/internal/eventlog"
|
||||
"github.com/safedep/pmg/sandbox"
|
||||
"github.com/safedep/pmg/sandbox/platform"
|
||||
"github.com/safedep/pmg/usefulerror"
|
||||
@@ -114,6 +116,12 @@ func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string, opts ...app
|
||||
|
||||
log.Debugf("Loaded sandbox policy %s", policy.Name)
|
||||
|
||||
// Apply runtime --sandbox-allow overrides to the policy before execution
|
||||
if len(cfg.SandboxAllowOverrides) > 0 {
|
||||
applyRuntimeOverrides(policy, cfg.SandboxAllowOverrides)
|
||||
logSandboxOverridesToEventLog(policy.Name, cfg.SandboxAllowOverrides)
|
||||
}
|
||||
|
||||
if !policy.AppliesToPackageManager(pmName) {
|
||||
return nil, fmt.Errorf("sandbox policy %s does not apply to %s", policy.Name, pmName)
|
||||
}
|
||||
@@ -146,3 +154,49 @@ func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string, opts ...app
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// applyRuntimeOverrides applies --sandbox-allow overrides to the policy.
|
||||
// Overrides are additive — they only append to allow lists, never modify deny lists.
|
||||
// Warnings are logged for conflicts with deny rules and mandatory deny patterns.
|
||||
func applyRuntimeOverrides(policy *sandbox.SandboxPolicy, overrides []config.SandboxAllowOverride) {
|
||||
for _, override := range overrides {
|
||||
switch override.Type {
|
||||
case config.SandboxAllowRead:
|
||||
log.Infof("Sandbox override: allowing read access to %s", override.Value)
|
||||
policy.Filesystem.AllowRead = append(policy.Filesystem.AllowRead, override.Value)
|
||||
|
||||
case config.SandboxAllowWrite:
|
||||
log.Infof("Sandbox override: allowing write access to %s", override.Value)
|
||||
policy.Filesystem.AllowWrite = append(policy.Filesystem.AllowWrite, override.Value)
|
||||
|
||||
case config.SandboxAllowExec:
|
||||
log.Infof("Sandbox override: allowing execution of %s", override.Value)
|
||||
policy.Process.AllowExec = append(policy.Process.AllowExec, override.Value)
|
||||
|
||||
case config.SandboxAllowNetConnect:
|
||||
log.Infof("Sandbox override: allowing outbound connection to %s", override.Value)
|
||||
policy.Network.AllowOutbound = append(policy.Network.AllowOutbound, override.Value)
|
||||
|
||||
case config.SandboxAllowNetBind:
|
||||
log.Infof("Sandbox override: allowing network bind on %s", override.Value)
|
||||
policy.Network.AllowBind = append(policy.Network.AllowBind, override.Value)
|
||||
|
||||
// Enable AllowNetworkBind so the translator emits bind rules.
|
||||
// Without this, AllowBind entries would be ignored on some platforms.
|
||||
policy.AllowNetworkBind = utils.PtrTo(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// logSandboxOverridesToEventLog records sandbox allow overrides in the audit event log.
|
||||
func logSandboxOverridesToEventLog(profileName string, overrides []config.SandboxAllowOverride) {
|
||||
entries := make([]map[string]string, 0, len(overrides))
|
||||
for _, o := range overrides {
|
||||
entries = append(entries, map[string]string{
|
||||
"type": string(o.Type),
|
||||
"value": o.Value,
|
||||
})
|
||||
}
|
||||
|
||||
eventlog.LogSandboxOverrides(profileName, entries)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/safedep/dry/utils"
|
||||
"github.com/safedep/pmg/config"
|
||||
"github.com/safedep/pmg/sandbox"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestApplyRuntimeOverrides_Read(t *testing.T) {
|
||||
policy := &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowRead: []string{"/existing"},
|
||||
},
|
||||
}
|
||||
|
||||
applyRuntimeOverrides(policy, []config.SandboxAllowOverride{
|
||||
{Type: config.SandboxAllowRead, Value: "/new/path", Raw: "read=/new/path"},
|
||||
})
|
||||
|
||||
assert.Contains(t, policy.Filesystem.AllowRead, "/existing")
|
||||
assert.Contains(t, policy.Filesystem.AllowRead, "/new/path")
|
||||
}
|
||||
|
||||
func TestApplyRuntimeOverrides_Write(t *testing.T) {
|
||||
policy := &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowWrite: []string{"/existing"},
|
||||
},
|
||||
}
|
||||
|
||||
applyRuntimeOverrides(policy, []config.SandboxAllowOverride{
|
||||
{Type: config.SandboxAllowWrite, Value: "/new/file", Raw: "write=/new/file"},
|
||||
})
|
||||
|
||||
assert.Contains(t, policy.Filesystem.AllowWrite, "/existing")
|
||||
assert.Contains(t, policy.Filesystem.AllowWrite, "/new/file")
|
||||
}
|
||||
|
||||
func TestApplyRuntimeOverrides_Exec(t *testing.T) {
|
||||
policy := &sandbox.SandboxPolicy{
|
||||
Process: sandbox.ProcessPolicy{
|
||||
AllowExec: []string{"/usr/bin/node"},
|
||||
},
|
||||
}
|
||||
|
||||
applyRuntimeOverrides(policy, []config.SandboxAllowOverride{
|
||||
{Type: config.SandboxAllowExec, Value: "/usr/bin/curl", Raw: "exec=/usr/bin/curl"},
|
||||
})
|
||||
|
||||
assert.Contains(t, policy.Process.AllowExec, "/usr/bin/node")
|
||||
assert.Contains(t, policy.Process.AllowExec, "/usr/bin/curl")
|
||||
}
|
||||
|
||||
func TestApplyRuntimeOverrides_NetConnect(t *testing.T) {
|
||||
policy := &sandbox.SandboxPolicy{
|
||||
Network: sandbox.NetworkPolicy{
|
||||
AllowOutbound: []string{"registry.npmjs.org:443"},
|
||||
},
|
||||
}
|
||||
|
||||
applyRuntimeOverrides(policy, []config.SandboxAllowOverride{
|
||||
{Type: config.SandboxAllowNetConnect, Value: "example.com:443", Raw: "net-connect=example.com:443"},
|
||||
})
|
||||
|
||||
assert.Contains(t, policy.Network.AllowOutbound, "registry.npmjs.org:443")
|
||||
assert.Contains(t, policy.Network.AllowOutbound, "example.com:443")
|
||||
}
|
||||
|
||||
func TestApplyRuntimeOverrides_NetBind(t *testing.T) {
|
||||
policy := &sandbox.SandboxPolicy{
|
||||
Network: sandbox.NetworkPolicy{
|
||||
AllowBind: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
applyRuntimeOverrides(policy, []config.SandboxAllowOverride{
|
||||
{Type: config.SandboxAllowNetBind, Value: "127.0.0.1:3000", Raw: "net-bind=127.0.0.1:3000"},
|
||||
})
|
||||
|
||||
assert.Contains(t, policy.Network.AllowBind, "127.0.0.1:3000")
|
||||
assert.NotNil(t, policy.AllowNetworkBind)
|
||||
assert.True(t, *policy.AllowNetworkBind)
|
||||
}
|
||||
|
||||
func TestApplyRuntimeOverrides_NetBindPreservesExistingTrue(t *testing.T) {
|
||||
policy := &sandbox.SandboxPolicy{
|
||||
AllowNetworkBind: utils.PtrTo(true),
|
||||
Network: sandbox.NetworkPolicy{
|
||||
AllowBind: []string{"localhost:8080"},
|
||||
},
|
||||
}
|
||||
|
||||
applyRuntimeOverrides(policy, []config.SandboxAllowOverride{
|
||||
{Type: config.SandboxAllowNetBind, Value: "127.0.0.1:3000", Raw: "net-bind=127.0.0.1:3000"},
|
||||
})
|
||||
|
||||
assert.Contains(t, policy.Network.AllowBind, "localhost:8080")
|
||||
assert.Contains(t, policy.Network.AllowBind, "127.0.0.1:3000")
|
||||
assert.True(t, *policy.AllowNetworkBind)
|
||||
}
|
||||
|
||||
func TestApplyRuntimeOverrides_MultipleOverrides(t *testing.T) {
|
||||
policy := &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{},
|
||||
Process: sandbox.ProcessPolicy{},
|
||||
Network: sandbox.NetworkPolicy{},
|
||||
}
|
||||
|
||||
overrides := []config.SandboxAllowOverride{
|
||||
{Type: config.SandboxAllowWrite, Value: "/path/a", Raw: "write=/path/a"},
|
||||
{Type: config.SandboxAllowWrite, Value: "/path/b", Raw: "write=/path/b"},
|
||||
{Type: config.SandboxAllowExec, Value: "/usr/bin/curl", Raw: "exec=/usr/bin/curl"},
|
||||
{Type: config.SandboxAllowNetConnect, Value: "example.com:443", Raw: "net-connect=example.com:443"},
|
||||
}
|
||||
|
||||
applyRuntimeOverrides(policy, overrides)
|
||||
|
||||
assert.Len(t, policy.Filesystem.AllowWrite, 2)
|
||||
assert.Len(t, policy.Process.AllowExec, 1)
|
||||
assert.Len(t, policy.Network.AllowOutbound, 1)
|
||||
}
|
||||
|
||||
func TestApplyRuntimeOverrides_EmptyOverrides(t *testing.T) {
|
||||
policy := &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
AllowWrite: []string{"/existing"},
|
||||
},
|
||||
}
|
||||
|
||||
applyRuntimeOverrides(policy, []config.SandboxAllowOverride{})
|
||||
|
||||
// Policy should be unchanged
|
||||
assert.Equal(t, []string{"/existing"}, policy.Filesystem.AllowWrite)
|
||||
}
|
||||
|
||||
func TestApplyRuntimeOverrides_DenyListsUnmodified(t *testing.T) {
|
||||
policy := &sandbox.SandboxPolicy{
|
||||
Filesystem: sandbox.FilesystemPolicy{
|
||||
DenyWrite: []string{"/protected"},
|
||||
},
|
||||
Process: sandbox.ProcessPolicy{
|
||||
DenyExec: []string{"/usr/bin/curl"},
|
||||
},
|
||||
Network: sandbox.NetworkPolicy{
|
||||
DenyOutbound: []string{"*:*"},
|
||||
},
|
||||
}
|
||||
|
||||
overrides := []config.SandboxAllowOverride{
|
||||
{Type: config.SandboxAllowWrite, Value: "/something", Raw: "write=/something"},
|
||||
{Type: config.SandboxAllowExec, Value: "/usr/bin/wget", Raw: "exec=/usr/bin/wget"},
|
||||
{Type: config.SandboxAllowNetConnect, Value: "example.com:443", Raw: "net-connect=example.com:443"},
|
||||
}
|
||||
|
||||
applyRuntimeOverrides(policy, overrides)
|
||||
|
||||
// Deny lists should never be modified by overrides
|
||||
assert.Equal(t, []string{"/protected"}, policy.Filesystem.DenyWrite)
|
||||
assert.Equal(t, []string{"/usr/bin/curl"}, policy.Process.DenyExec)
|
||||
assert.Equal(t, []string{"*:*"}, policy.Network.DenyOutbound)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user