mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
fix: Sandbox Allow network bind for npx (#151)
* feat: Add support for network bind * chore: Add comments for bwrap sandbox
This commit is contained in:
@@ -126,6 +126,11 @@ func (t *bubblewrapPolicyTranslator) addIsolationNamespaces(policy *sandbox.Sand
|
|||||||
log.Debugf("Network allowed (no --unshare-net)")
|
log.Debugf("Network allowed (no --unshare-net)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: AllowNetworkBind and Network.AllowBind are not handled here because
|
||||||
|
// bwrap's --unshare-net creates a namespace with loopback available, so
|
||||||
|
// localhost binding already works. Non-localhost binding requires full host
|
||||||
|
// network (no --unshare-net), which is controlled by AllowOutbound rules.
|
||||||
|
|
||||||
// PID namespace isolation
|
// PID namespace isolation
|
||||||
if t.config.unsharePID {
|
if t.config.unsharePID {
|
||||||
args = append(args, "--unshare-pid")
|
args = append(args, "--unshare-pid")
|
||||||
|
|||||||
@@ -555,6 +555,22 @@ func (t *seatbeltPolicyTranslator) translateNetwork(policy *sandbox.SandboxPolic
|
|||||||
|
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
|
|
||||||
|
// Network bind rules for local listening
|
||||||
|
if utils.SafelyGetValue(policy.AllowNetworkBind) {
|
||||||
|
sb.WriteString(";; Local network bind (localhost only)\n")
|
||||||
|
sb.WriteString("(allow network-bind (local ip \"localhost:*\"))\n")
|
||||||
|
sb.WriteString("(allow network* (local ip \"localhost:*\"))\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range policy.Network.AllowBind {
|
||||||
|
sb.WriteString(fmt.Sprintf("(allow network-bind (local ip \"%s\"))\n", addr))
|
||||||
|
sb.WriteString(fmt.Sprintf("(allow network* (local ip \"%s\"))\n", addr))
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.SafelyGetValue(policy.AllowNetworkBind) || len(policy.Network.AllowBind) > 0 {
|
||||||
|
sb.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -572,6 +572,84 @@ func TestPTYSupport(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func TestSeatbeltTranslatorDarwinLogTag(t *testing.T) {
|
||||||
translator := newSeatbeltPolicyTranslator()
|
translator := newSeatbeltPolicyTranslator()
|
||||||
assert.NotEmpty(t, translator.LogTag())
|
assert.NotEmpty(t, translator.LogTag())
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ type SandboxPolicy struct {
|
|||||||
|
|
||||||
// AllowPTY allows pseudo-terminal (PTY) operations.
|
// AllowPTY allows pseudo-terminal (PTY) operations.
|
||||||
AllowPTY *bool `yaml:"allow_pty" json:"allow_pty"`
|
AllowPTY *bool `yaml:"allow_pty" json:"allow_pty"`
|
||||||
|
|
||||||
|
// AllowNetworkBind allows binding to localhost (127.0.0.1 / ::1) for listening.
|
||||||
|
AllowNetworkBind *bool `yaml:"allow_network_bind" json:"allow_network_bind"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilesystemPolicy defines allowed and denied filesystem access patterns.
|
// FilesystemPolicy defines allowed and denied filesystem access patterns.
|
||||||
@@ -50,6 +53,7 @@ type FilesystemPolicy struct {
|
|||||||
type NetworkPolicy struct {
|
type NetworkPolicy struct {
|
||||||
AllowOutbound []string `yaml:"allow_outbound" json:"allow_outbound"`
|
AllowOutbound []string `yaml:"allow_outbound" json:"allow_outbound"`
|
||||||
DenyOutbound []string `yaml:"deny_outbound" json:"deny_outbound"`
|
DenyOutbound []string `yaml:"deny_outbound" json:"deny_outbound"`
|
||||||
|
AllowBind []string `yaml:"allow_bind" json:"allow_bind"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessPolicy defines allowed and denied process execution patterns.
|
// ProcessPolicy defines allowed and denied process execution patterns.
|
||||||
@@ -124,6 +128,7 @@ func (child *SandboxPolicy) MergeWithParent(parent *SandboxPolicy) {
|
|||||||
// Union network lists
|
// Union network lists
|
||||||
child.Network.AllowOutbound = unionStringSlices(parent.Network.AllowOutbound, child.Network.AllowOutbound)
|
child.Network.AllowOutbound = unionStringSlices(parent.Network.AllowOutbound, child.Network.AllowOutbound)
|
||||||
child.Network.DenyOutbound = unionStringSlices(parent.Network.DenyOutbound, child.Network.DenyOutbound)
|
child.Network.DenyOutbound = unionStringSlices(parent.Network.DenyOutbound, child.Network.DenyOutbound)
|
||||||
|
child.Network.AllowBind = unionStringSlices(parent.Network.AllowBind, child.Network.AllowBind)
|
||||||
|
|
||||||
// Union process lists
|
// Union process lists
|
||||||
child.Process.AllowExec = unionStringSlices(parent.Process.AllowExec, child.Process.AllowExec)
|
child.Process.AllowExec = unionStringSlices(parent.Process.AllowExec, child.Process.AllowExec)
|
||||||
@@ -137,6 +142,10 @@ func (child *SandboxPolicy) MergeWithParent(parent *SandboxPolicy) {
|
|||||||
if child.AllowGitConfig == nil {
|
if child.AllowGitConfig == nil {
|
||||||
child.AllowGitConfig = utils.PtrTo(utils.SafelyGetValue(parent.AllowGitConfig))
|
child.AllowGitConfig = utils.PtrTo(utils.SafelyGetValue(parent.AllowGitConfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if child.AllowNetworkBind == nil {
|
||||||
|
child.AllowNetworkBind = utils.PtrTo(utils.SafelyGetValue(parent.AllowNetworkBind))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// unionStringSlices returns a new slice containing all unique elements from both slices.
|
// unionStringSlices returns a new slice containing all unique elements from both slices.
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ package_managers:
|
|||||||
# in parent profiles in the future.
|
# in parent profiles in the future.
|
||||||
allow_pty: true
|
allow_pty: true
|
||||||
|
|
||||||
|
# npx generators and dev servers frequently need to bind to localhost ports
|
||||||
|
allow_network_bind: true
|
||||||
|
|
||||||
filesystem:
|
filesystem:
|
||||||
# Add write permissions for common generator outputs
|
# Add write permissions for common generator outputs
|
||||||
allow_write:
|
allow_write:
|
||||||
|
|||||||
Reference in New Issue
Block a user