mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat: Add support for config templates
This commit is contained in:
@@ -73,6 +73,17 @@ type SandboxConfig struct {
|
||||
// Policies maps package manager names to their sandbox policy references.
|
||||
// Key is package manager name (e.g., "npm", "pip"), value is policy reference.
|
||||
Policies map[string]SandboxPolicyRef `mapstructure:"policies"`
|
||||
|
||||
// PolicyTemplates maps template names to their paths.
|
||||
PolicyTemplates map[string]SandboxPolicyTemplate `mapstructure:"policy_templates"`
|
||||
}
|
||||
|
||||
// SandboxPolicyTemplate defines a template for a sandbox policy, used to map
|
||||
// a profile name to a path.
|
||||
type SandboxPolicyTemplate struct {
|
||||
// Path is the path to the template file.
|
||||
// Relative path can be used to reference a template file in the config directory (example: ./npm-restrictive.yml)
|
||||
Path string `mapstructure:"path"`
|
||||
}
|
||||
|
||||
// SandboxPolicyRef references a sandbox policy for a specific package manager.
|
||||
@@ -130,6 +141,11 @@ func (r *RuntimeConfig) EventLogDir() string {
|
||||
return r.eventLogDir
|
||||
}
|
||||
|
||||
// ConfigDir returns the path to the config directory.
|
||||
func (r *RuntimeConfig) ConfigDir() string {
|
||||
return r.configDir
|
||||
}
|
||||
|
||||
// DefaultConfig is a fail safe contract for the runtime configuration.
|
||||
// The config package return an appropriate RuntimeConfig based on the environment and the configuration.
|
||||
func DefaultConfig() RuntimeConfig {
|
||||
|
||||
@@ -63,13 +63,22 @@ sandbox:
|
||||
# Enable sandbox mode (opt-in, default: false for backward compatibility)
|
||||
enabled: false
|
||||
|
||||
# Policy templates define policy profiles by name and path.
|
||||
# They can be used to override a built-in profile or create a custom profile.
|
||||
policy_templates:
|
||||
# Name for the template. Can be used to override a built-in profile or create a custom profile.
|
||||
# Path is the path to the template file.
|
||||
# Relative path can be used to reference a template file in the config directory (example: ./npm-restrictive.yml)
|
||||
npm-restrictive-override:
|
||||
path: ./profiles/npm-restrictive.yml
|
||||
|
||||
# Per-package-manager sandbox policies
|
||||
# Each package manager can have its own policy to account for unique security characteristics
|
||||
policies:
|
||||
# npm ecosystem
|
||||
# npm ecosystem. npm-restrictive is a built-in profile.
|
||||
npm:
|
||||
enabled: true
|
||||
profile: npm-restrictive # Built-in profile or path to custom YAML
|
||||
profile: npm-restrictive # Built-in profile, template name, or path to custom YAML
|
||||
|
||||
pnpm:
|
||||
enabled: true
|
||||
@@ -83,7 +92,7 @@ sandbox:
|
||||
enabled: true
|
||||
profile: npm-restrictive
|
||||
|
||||
# PyPI ecosystem
|
||||
# PyPI ecosystem. pypi-restrictive is a built-in profile.
|
||||
pip:
|
||||
enabled: true
|
||||
profile: pypi-restrictive
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/safedep/dry/log"
|
||||
"github.com/safedep/pmg/config"
|
||||
@@ -11,14 +12,33 @@ import (
|
||||
"github.com/safedep/pmg/sandbox/platform"
|
||||
)
|
||||
|
||||
type applySandboxConfig struct {
|
||||
sb sandbox.Sandbox
|
||||
}
|
||||
|
||||
type applySandboxOpt func(*applySandboxConfig)
|
||||
|
||||
// WithSandbox sets the sandbox to use for the command.
|
||||
// When not set, the sandbox will be determined by the platform.
|
||||
func WithSandbox(sb sandbox.Sandbox) applySandboxOpt {
|
||||
return func(c *applySandboxConfig) {
|
||||
c.sb = sb
|
||||
}
|
||||
}
|
||||
|
||||
// ApplySandbox applies sandbox isolation to the command if sandbox mode is enabled.
|
||||
// This is a helper function used by both guard and proxy flows to avoid code duplication.
|
||||
//
|
||||
// This is a security sensitive operation. If sandbox is enabled via. config but not available on the platform,
|
||||
// it will return an error to avoid running the command without sandbox protection.
|
||||
func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string) (*sandbox.ExecutionResult, error) {
|
||||
func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string, opts ...applySandboxOpt) (*sandbox.ExecutionResult, error) {
|
||||
cfg := config.Get()
|
||||
|
||||
applyConfig := &applySandboxConfig{}
|
||||
for _, opt := range opts {
|
||||
opt(applyConfig)
|
||||
}
|
||||
|
||||
if !cfg.Config.Sandbox.Enabled {
|
||||
return sandbox.NewExecutionResult(), nil
|
||||
}
|
||||
@@ -57,9 +77,27 @@ func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string) (*sandbox.E
|
||||
|
||||
log.Debugf("Loading sandbox policy %s", policyRef.Profile)
|
||||
|
||||
policy, err = registry.GetProfile(policyRef.Profile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load sandbox policy %s: %w", policyRef.Profile, err)
|
||||
// Check if there is a template for the policy and use it if it exists
|
||||
// This is a way to override a built-in profile or create a custom profile.
|
||||
if template, exists := cfg.Config.Sandbox.PolicyTemplates[policyRef.Profile]; exists {
|
||||
if filepath.IsAbs(template.Path) {
|
||||
policy, err = registry.GetProfile(template.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load sandbox policy %s: %w", template.Path, err)
|
||||
}
|
||||
} else {
|
||||
policyPath := filepath.Join(cfg.ConfigDir(), template.Path)
|
||||
policy, err = registry.GetProfile(policyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load sandbox policy %s: %w", policyPath, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Load the policy from the registry by name
|
||||
policy, err = registry.GetProfile(policyRef.Profile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load sandbox policy %s: %w", policyRef.Profile, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,9 +107,14 @@ func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string) (*sandbox.E
|
||||
return nil, fmt.Errorf("sandbox policy %s does not apply to %s", policy.Name, pmName)
|
||||
}
|
||||
|
||||
sb, err := platform.NewSandbox()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sandbox not available on this platform: %v", err)
|
||||
var sb sandbox.Sandbox
|
||||
if applyConfig.sb != nil {
|
||||
sb = applyConfig.sb
|
||||
} else {
|
||||
sb, err = platform.NewSandbox()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sandbox not available on this platform: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if !sb.IsAvailable() {
|
||||
|
||||
@@ -38,7 +38,12 @@ func (t *seatbeltPolicyTranslator) translate(policy *sandbox.SandboxPolicy) (str
|
||||
sb.WriteString("(allow mach-lookup)\n")
|
||||
sb.WriteString("(allow mach-register)\n")
|
||||
sb.WriteString("(allow ipc-posix-shm)\n")
|
||||
sb.WriteString("(allow signal)\n\n")
|
||||
sb.WriteString("(allow signal)\n")
|
||||
sb.WriteString(";; Allow reading file metadata for getcwd() and similar operations\n")
|
||||
sb.WriteString("(allow file-read-metadata)\n")
|
||||
sb.WriteString(";; Allow reading system configuration and libraries needed for process execution\n")
|
||||
sb.WriteString("(allow file-read* (subpath \"/dev\"))\n")
|
||||
sb.WriteString("(allow file-read* (subpath \"/etc\"))\n\n")
|
||||
|
||||
// Filesystem rules
|
||||
if err := t.translateFilesystem(policy, &sb); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user