feat: Add support for config templates

This commit is contained in:
Abhisek Datta
2026-01-09 18:46:08 +05:30
parent e72c1d6324
commit 9cb5345bd6
4 changed files with 84 additions and 11 deletions
+16
View File
@@ -73,6 +73,17 @@ type SandboxConfig struct {
// Policies maps package manager names to their sandbox policy references. // Policies maps package manager names to their sandbox policy references.
// Key is package manager name (e.g., "npm", "pip"), value is policy reference. // Key is package manager name (e.g., "npm", "pip"), value is policy reference.
Policies map[string]SandboxPolicyRef `mapstructure:"policies"` 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. // SandboxPolicyRef references a sandbox policy for a specific package manager.
@@ -130,6 +141,11 @@ func (r *RuntimeConfig) EventLogDir() string {
return r.eventLogDir 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. // DefaultConfig is a fail safe contract for the runtime configuration.
// The config package return an appropriate RuntimeConfig based on the environment and the configuration. // The config package return an appropriate RuntimeConfig based on the environment and the configuration.
func DefaultConfig() RuntimeConfig { func DefaultConfig() RuntimeConfig {
+12 -3
View File
@@ -63,13 +63,22 @@ sandbox:
# Enable sandbox mode (opt-in, default: false for backward compatibility) # Enable sandbox mode (opt-in, default: false for backward compatibility)
enabled: false 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 # Per-package-manager sandbox policies
# Each package manager can have its own policy to account for unique security characteristics # Each package manager can have its own policy to account for unique security characteristics
policies: policies:
# npm ecosystem # npm ecosystem. npm-restrictive is a built-in profile.
npm: npm:
enabled: true 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: pnpm:
enabled: true enabled: true
@@ -83,7 +92,7 @@ sandbox:
enabled: true enabled: true
profile: npm-restrictive profile: npm-restrictive
# PyPI ecosystem # PyPI ecosystem. pypi-restrictive is a built-in profile.
pip: pip:
enabled: true enabled: true
profile: pypi-restrictive profile: pypi-restrictive
+50 -7
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"os/exec" "os/exec"
"path/filepath"
"github.com/safedep/dry/log" "github.com/safedep/dry/log"
"github.com/safedep/pmg/config" "github.com/safedep/pmg/config"
@@ -11,14 +12,33 @@ import (
"github.com/safedep/pmg/sandbox/platform" "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. // 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 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, // 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. // 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() cfg := config.Get()
applyConfig := &applySandboxConfig{}
for _, opt := range opts {
opt(applyConfig)
}
if !cfg.Config.Sandbox.Enabled { if !cfg.Config.Sandbox.Enabled {
return sandbox.NewExecutionResult(), nil 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) log.Debugf("Loading sandbox policy %s", policyRef.Profile)
policy, err = registry.GetProfile(policyRef.Profile) // Check if there is a template for the policy and use it if it exists
if err != nil { // This is a way to override a built-in profile or create a custom profile.
return nil, fmt.Errorf("failed to load sandbox policy %s: %w", policyRef.Profile, err) 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) return nil, fmt.Errorf("sandbox policy %s does not apply to %s", policy.Name, pmName)
} }
sb, err := platform.NewSandbox() var sb sandbox.Sandbox
if err != nil { if applyConfig.sb != nil {
return nil, fmt.Errorf("sandbox not available on this platform: %v", err) 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() { 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-lookup)\n")
sb.WriteString("(allow mach-register)\n") sb.WriteString("(allow mach-register)\n")
sb.WriteString("(allow ipc-posix-shm)\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 // Filesystem rules
if err := t.translateFilesystem(policy, &sb); err != nil { if err := t.translateFilesystem(policy, &sb); err != nil {