mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
fix: Opt-in lockdown for global config (#278)
* fix: Reject overriding managed flags * fix: Lockdown overrides when global config present * fix: Opt-in lock-down enforcement for global config * fix: Code review fixes * fix: Code review fixes * fix: Code review fixes
This commit is contained in:
+121
-28
@@ -2,8 +2,10 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var skipDependencyCooldown bool
|
||||
@@ -11,37 +13,128 @@ var skipDependencyCooldown bool
|
||||
// sandboxAllowRaw holds the raw --sandbox-allow flag values before parsing.
|
||||
var sandboxAllowRaw []string
|
||||
|
||||
// ApplyCobraFlags applies the cobra flags to the command.
|
||||
// These flags are local concern of the config package. This helper function is used
|
||||
// to bind them to the Cobra. The default values are taken from the global configuration,
|
||||
// allowing for overriding the configuration at runtime.
|
||||
// flagSpec declares a pmg flag once: how to bind it into cobra (bind) and the
|
||||
// metadata used to reason about it (managed). configFlagSpecs is the single
|
||||
// source of truth, so the cobra wiring and policy decisions cannot drift apart.
|
||||
type flagSpec struct {
|
||||
name string
|
||||
usage string
|
||||
|
||||
// true when the globally managed config governs this value
|
||||
managed bool
|
||||
|
||||
// bind registers the flag on fs. It owns the type, target field, and default
|
||||
// (read at bind time), keeping the flag tied to its config field with
|
||||
// compile-time safety rather than a stringly-typed key.
|
||||
bind func(fs *pflag.FlagSet, name, usage string)
|
||||
}
|
||||
|
||||
var configFlagSpecs = []flagSpec{
|
||||
{
|
||||
name: "transitive", usage: "Resolve transitive dependencies", managed: true,
|
||||
bind: func(fs *pflag.FlagSet, name, usage string) {
|
||||
fs.BoolVar(&globalConfig.Config.Transitive, name, globalConfig.Config.Transitive, usage)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "transitive-depth", usage: "Maximum depth of transitive dependencies to resolve", managed: true,
|
||||
bind: func(fs *pflag.FlagSet, name, usage string) {
|
||||
fs.IntVar(&globalConfig.Config.TransitiveDepth, name, globalConfig.Config.TransitiveDepth, usage)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "include-dev-dependencies", usage: "Include dev dependencies in the dependency graph (slows down resolution)", managed: true,
|
||||
bind: func(fs *pflag.FlagSet, name, usage string) {
|
||||
fs.BoolVar(&globalConfig.Config.IncludeDevDependencies, name, globalConfig.Config.IncludeDevDependencies, usage)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dry-run", usage: "Dry run skips execution of package manager", managed: false,
|
||||
bind: func(fs *pflag.FlagSet, name, usage string) {
|
||||
fs.BoolVar(&globalConfig.DryRun, name, globalConfig.DryRun, usage)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "paranoid", usage: "Enable high-security defaults (treat suspicious as malicious)", managed: true,
|
||||
bind: func(fs *pflag.FlagSet, name, usage string) {
|
||||
fs.BoolVar(&globalConfig.Config.Paranoid, name, globalConfig.Config.Paranoid, usage)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "skip-event-log", usage: "Skip event logging", managed: true,
|
||||
bind: func(fs *pflag.FlagSet, name, usage string) {
|
||||
fs.BoolVar(&globalConfig.Config.SkipEventLogging, name, globalConfig.Config.SkipEventLogging, usage)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "proxy-mode", usage: "Use proxy based interception", managed: true,
|
||||
bind: func(fs *pflag.FlagSet, name, usage string) {
|
||||
fs.BoolVar(&globalConfig.Config.Proxy.Enabled, name, globalConfig.Config.Proxy.Enabled, usage)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sandbox", usage: "Enable sandbox mode to isolate package manager processes (EXPERIMENTAL)", managed: true,
|
||||
bind: func(fs *pflag.FlagSet, name, usage string) {
|
||||
fs.BoolVar(&globalConfig.Config.Sandbox.Enabled, name, globalConfig.Config.Sandbox.Enabled, usage)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sandbox-enforce", usage: "Apply sandbox to all commands, not just install commands (requires --sandbox)", managed: true,
|
||||
bind: func(fs *pflag.FlagSet, name, usage string) {
|
||||
fs.BoolVar(&globalConfig.Config.Sandbox.EnforceAlways, name, globalConfig.Config.Sandbox.EnforceAlways, usage)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sandbox-profile", usage: "Override sandbox policy profile (built-in name or path to custom YAML)", managed: true,
|
||||
bind: func(fs *pflag.FlagSet, name, usage string) {
|
||||
fs.StringVar(&globalConfig.SandboxProfileOverride, name, globalConfig.SandboxProfileOverride, usage)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sandbox-allow", usage: "Add runtime sandbox allow rule (type=value). Types: read, write, exec, net-connect, net-bind", managed: true,
|
||||
bind: func(fs *pflag.FlagSet, name, usage string) {
|
||||
fs.StringArrayVar(&sandboxAllowRaw, name, nil, usage)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "skip-dependency-cooldown", usage: "Skip dependency cooldown enforcement", managed: true,
|
||||
bind: func(fs *pflag.FlagSet, name, usage string) {
|
||||
fs.BoolVar(&skipDependencyCooldown, name, false, usage)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// ApplyCobraFlags binds the config flags onto cmd as persistent flags. Defaults
|
||||
// are read from the current global configuration, allowing runtime overrides.
|
||||
func ApplyCobraFlags(cmd *cobra.Command) {
|
||||
cmd.PersistentFlags().BoolVar(&globalConfig.Config.Transitive, "transitive",
|
||||
globalConfig.Config.Transitive, "Resolve transitive dependencies")
|
||||
cmd.PersistentFlags().IntVar(&globalConfig.Config.TransitiveDepth, "transitive-depth",
|
||||
globalConfig.Config.TransitiveDepth, "Maximum depth of transitive dependencies to resolve")
|
||||
cmd.PersistentFlags().BoolVar(&globalConfig.Config.IncludeDevDependencies, "include-dev-dependencies",
|
||||
globalConfig.Config.IncludeDevDependencies, "Include dev dependencies in the dependency graph (slows down resolution)")
|
||||
cmd.PersistentFlags().BoolVar(&globalConfig.DryRun, "dry-run",
|
||||
globalConfig.DryRun, "Dry run skips execution of package manager")
|
||||
cmd.PersistentFlags().BoolVar(&globalConfig.Config.Paranoid, "paranoid",
|
||||
globalConfig.Config.Paranoid, "Enable high-security defaults (treat suspicious as malicious)")
|
||||
cmd.PersistentFlags().BoolVar(&globalConfig.Config.SkipEventLogging, "skip-event-log",
|
||||
globalConfig.Config.SkipEventLogging, "Skip event logging")
|
||||
cmd.PersistentFlags().BoolVar(&globalConfig.Config.Proxy.Enabled, "proxy-mode",
|
||||
globalConfig.Config.Proxy.Enabled, "Use proxy based interception")
|
||||
cmd.PersistentFlags().BoolVar(&globalConfig.Config.Sandbox.Enabled, "sandbox",
|
||||
globalConfig.Config.Sandbox.Enabled, "Enable sandbox mode to isolate package manager processes (EXPERIMENTAL)")
|
||||
cmd.PersistentFlags().BoolVar(&globalConfig.Config.Sandbox.EnforceAlways, "sandbox-enforce",
|
||||
globalConfig.Config.Sandbox.EnforceAlways, "Apply sandbox to all commands, not just install commands (requires --sandbox)")
|
||||
cmd.PersistentFlags().StringVar(&globalConfig.SandboxProfileOverride, "sandbox-profile",
|
||||
globalConfig.SandboxProfileOverride, "Override sandbox policy profile (built-in name or path to custom YAML)")
|
||||
cmd.PersistentFlags().StringArrayVar(&sandboxAllowRaw, "sandbox-allow",
|
||||
nil, "Add runtime sandbox allow rule (type=value). Types: read, write, exec, net-connect, net-bind")
|
||||
fs := cmd.PersistentFlags()
|
||||
for _, f := range configFlagSpecs {
|
||||
f.bind(fs, f.name, f.usage)
|
||||
}
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().BoolVar(&skipDependencyCooldown, "skip-dependency-cooldown",
|
||||
false, "Skip dependency cooldown enforcement")
|
||||
// RejectManagedFlagOverrides fails when the active config is a locked global
|
||||
// config and the user explicitly set a flag whose value that config governs.
|
||||
// Operational flags (managed == false) are unaffected, and an unlocked managed
|
||||
// config allows flag overrides. Call it after flag parsing.
|
||||
func RejectManagedFlagOverrides(cmd *cobra.Command) error {
|
||||
if !Get().IsLocked() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var offending []string
|
||||
for _, f := range configFlagSpecs {
|
||||
if f.managed && cmd.Flags().Changed(f.name) {
|
||||
offending = append(offending, "--"+f.name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(offending) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return managedError(fmt.Sprintf("these flags cannot override the globally managed configuration (%s): %s",
|
||||
globalConfig.configFilePath, strings.Join(offending, ", ")))
|
||||
}
|
||||
|
||||
// FinalizeDependencyCooldownOverride disables dependency cooldown in the global
|
||||
|
||||
Reference in New Issue
Block a user