mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
140 lines
4.9 KiB
Go
140 lines
4.9 KiB
Go
package config
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"github.com/safedep/dry/log"
|
||
|
|
"github.com/safedep/dry/usefulerror"
|
||
|
|
"github.com/safedep/pmg/errcodes"
|
||
|
|
)
|
||
|
|
|
||
|
|
// RejectRemovedProxyOptOut fails loudly when a removed proxy opt-out is still
|
||
|
|
// in effect. Guard mode is removed and proxy interception can no longer be
|
||
|
|
// disabled, so a config or environment that explicitly disables it must not be
|
||
|
|
// silently ignored: the user opted out of proxy interception and switching
|
||
|
|
// them to it without notice would violate that expectation.
|
||
|
|
//
|
||
|
|
// This is a best-effort migration check covering the common opt-out spellings,
|
||
|
|
// not an exhaustive re-implementation of the old resolution; pathological
|
||
|
|
// configs are out of scope, and proxy interception runs regardless of its
|
||
|
|
// outcome.
|
||
|
|
//
|
||
|
|
// Resolution mirrors the old order:
|
||
|
|
// 1. PMG_PROXY_ENABLED (ignored under lockdown) wins over everything.
|
||
|
|
// Unrecognized values fall back to the default (proxy on), like the old
|
||
|
|
// loader which silently discarded bad config and ran on defaults.
|
||
|
|
// 2. The effective proxy.enabled file value, matched like viper resolved it
|
||
|
|
// (keys case-insensitive, literal dotted proxy.enabled key supported).
|
||
|
|
// Defaults to true.
|
||
|
|
// 3. The legacy fallback overrides it when the raw file has no exact "proxy"
|
||
|
|
// key (the old hasProxySectionInFile gate was case-sensitive):
|
||
|
|
// PMG_PROXY_MODE (ignored under lockdown) wins over the flat proxy_mode
|
||
|
|
// file key, both coerced cast.ToBool-style (unparseable = false).
|
||
|
|
func RejectRemovedProxyOptOut() error {
|
||
|
|
locked := globalConfig.IsLocked()
|
||
|
|
|
||
|
|
if !locked {
|
||
|
|
if raw := os.Getenv("PMG_PROXY_ENABLED"); raw != "" {
|
||
|
|
if enabled, ok := parseOptOutBool(raw); ok && !enabled {
|
||
|
|
return removedProxyOptOutError("Unset the PMG_PROXY_ENABLED environment variable")
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
rawKeys, err := readConfigFileKeys(globalConfig.configFilePath)
|
||
|
|
if err != nil {
|
||
|
|
if !os.IsNotExist(err) {
|
||
|
|
log.Warnf("skipping removed proxy opt-out check, could not read config file %s: %v", globalConfig.configFilePath, err)
|
||
|
|
}
|
||
|
|
rawKeys = nil
|
||
|
|
}
|
||
|
|
|
||
|
|
enabled, remedy := true, ""
|
||
|
|
if value, present := lookupProxyEnabled(rawKeys); present {
|
||
|
|
if parsed, ok := parseOptOutBool(value); ok {
|
||
|
|
enabled = parsed
|
||
|
|
remedy = fmt.Sprintf("Remove proxy.enabled from %s", globalConfig.configFilePath)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The legacy fallback only ran when the raw file had no exact "proxy" key,
|
||
|
|
// and within it the env var won over the flat file key with cast.ToBool
|
||
|
|
// coercion (any unparseable value meant false).
|
||
|
|
if _, hasRawProxyKey := rawKeys["proxy"]; !hasRawProxyKey {
|
||
|
|
if envRaw := os.Getenv("PMG_PROXY_MODE"); !locked && envRaw != "" {
|
||
|
|
enabled = legacyBoolValue(envRaw)
|
||
|
|
remedy = "Unset the PMG_PROXY_MODE environment variable"
|
||
|
|
} else if value, present := lookupKeyFold(rawKeys, "proxy_mode"); present {
|
||
|
|
enabled = legacyBoolValue(value)
|
||
|
|
remedy = fmt.Sprintf("Remove proxy_mode from %s", globalConfig.configFilePath)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if !enabled {
|
||
|
|
return removedProxyOptOutError(remedy)
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// lookupProxyEnabled returns the proxy.enabled value viper would have resolved
|
||
|
|
// from the raw file keys: an enabled key inside a proxy: section or a literal
|
||
|
|
// dotted proxy.enabled key, all matched case-insensitively like viper.
|
||
|
|
func lookupProxyEnabled(raw map[string]any) (any, bool) {
|
||
|
|
for key, value := range raw {
|
||
|
|
switch strings.ToLower(key) {
|
||
|
|
case "proxy":
|
||
|
|
if section, ok := value.(map[string]any); ok {
|
||
|
|
if v, present := lookupKeyFold(section, "enabled"); present {
|
||
|
|
return v, true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
case "proxy.enabled":
|
||
|
|
return value, true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil, false
|
||
|
|
}
|
||
|
|
|
||
|
|
// lookupKeyFold returns the value for key, matching case-insensitively like
|
||
|
|
// viper's key resolution.
|
||
|
|
func lookupKeyFold(m map[string]any, key string) (any, bool) {
|
||
|
|
for k, v := range m {
|
||
|
|
if strings.ToLower(k) == key {
|
||
|
|
return v, true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil, false
|
||
|
|
}
|
||
|
|
|
||
|
|
// parseOptOutBool reads a boolean out of whatever the YAML parser or the
|
||
|
|
// environment produced (bool, 0/1 numbers, ParseBool-compatible strings) by
|
||
|
|
// coercing the value through its string form. Anything else reports no opinion.
|
||
|
|
func parseOptOutBool(v any) (bool, bool) {
|
||
|
|
b, err := strconv.ParseBool(fmt.Sprintf("%v", v))
|
||
|
|
return b, err == nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// legacyBoolValue matches cast.ToBool, which the old fallback used via
|
||
|
|
// v.GetBool: unparseable values (including null) coerce to false instead of
|
||
|
|
// being ignored, so PMG_PROXY_MODE=off previously selected guard mode.
|
||
|
|
func legacyBoolValue(v any) bool {
|
||
|
|
enabled, ok := parseOptOutBool(v)
|
||
|
|
return ok && enabled
|
||
|
|
}
|
||
|
|
|
||
|
|
func removedProxyOptOutError(remedy string) error {
|
||
|
|
return usefulerror.NewUsefulError().
|
||
|
|
WithCode(errcodes.InvalidArgument).
|
||
|
|
WithHumanError("guard mode has been removed and proxy interception can no longer be disabled").
|
||
|
|
WithHelp(fmt.Sprintf("%s. If proxy interception does not work in your environment, report it at https://github.com/safedep/pmg/issues", remedy)).
|
||
|
|
WithMsg("removed proxy opt-out is still configured")
|
||
|
|
}
|