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.
|
// 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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user