daemon: make sure proxy settings are sanitized when printing

The daemon can print the proxy configuration as part of error-messages,
and when reloading the daemon configuration (SIGHUP). Make sure that
the configuration is sanitized before printing.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2021-08-31 14:13:30 +02:00
parent bad4b30e65
commit 76016b846d
No known key found for this signature in database
GPG key ID: 76698F39D527CE8C
4 changed files with 68 additions and 14 deletions

View file

@ -801,8 +801,8 @@ func overrideProxyEnv(name, val string) {
if oldVal := os.Getenv(name); oldVal != "" && oldVal != val { if oldVal := os.Getenv(name); oldVal != "" && oldVal != val {
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"name": name, "name": name,
"old-value": oldVal, "old-value": config.MaskCredentials(oldVal),
"new-value": val, "new-value": config.MaskCredentials(val),
}).Warn("overriding existing proxy variable with value from configuration") }).Warn("overriding existing proxy variable with value from configuration")
} }
_ = os.Setenv(name, val) _ = os.Setenv(name, val)

View file

@ -534,6 +534,11 @@ func findConfigurationConflicts(config map[string]interface{}, flags *pflag.Flag
var conflicts []string var conflicts []string
printConflict := func(name string, flagValue, fileValue interface{}) string { printConflict := func(name string, flagValue, fileValue interface{}) string {
switch name {
case "http-proxy", "https-proxy":
flagValue = MaskCredentials(flagValue.(string))
fileValue = MaskCredentials(fileValue.(string))
}
return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue) return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue)
} }

View file

@ -28,14 +28,26 @@ func (daemon *Daemon) Reload(conf *config.Config) (err error) {
attributes := map[string]string{} attributes := map[string]string{}
defer func() { defer func() {
jsonString, _ := json.Marshal(daemon.configStore) if err == nil {
jsonString, _ := json.Marshal(&struct {
*config.Config
config.ProxyConfig
}{
Config: daemon.configStore,
ProxyConfig: config.ProxyConfig{
HTTPProxy: config.MaskCredentials(daemon.configStore.HTTPProxy),
HTTPSProxy: config.MaskCredentials(daemon.configStore.HTTPSProxy),
NoProxy: config.MaskCredentials(daemon.configStore.NoProxy),
},
})
logrus.Infof("Reloaded configuration: %s", jsonString)
}
// we're unlocking here, because // we're unlocking here, because
// LogDaemonEventWithAttributes() -> SystemInfo() -> GetAllRuntimes() // LogDaemonEventWithAttributes() -> SystemInfo() -> GetAllRuntimes()
// holds that lock too. // holds that lock too.
daemon.configStore.Unlock() daemon.configStore.Unlock()
if err == nil { if err == nil {
logrus.Infof("Reloaded configuration: %s", jsonString)
daemon.LogDaemonEventWithAttributes("reload", attributes) daemon.LogDaemonEventWithAttributes("reload", attributes)
} }
}() }()

View file

@ -9,6 +9,8 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
"syscall"
"testing" "testing"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -165,6 +167,8 @@ func TestDaemonProxy(t *testing.T) {
})) }))
defer proxyServer.Close() defer proxyServer.Close()
const userPass = "myuser:mypassword@"
// Configure proxy through env-vars // Configure proxy through env-vars
t.Run("environment variables", func(t *testing.T) { t.Run("environment variables", func(t *testing.T) {
defer env.Patch(t, "HTTP_PROXY", proxyServer.URL)() defer env.Patch(t, "HTTP_PROXY", proxyServer.URL)()
@ -195,10 +199,10 @@ func TestDaemonProxy(t *testing.T) {
// Configure proxy through command-line flags // Configure proxy through command-line flags
t.Run("command-line options", func(t *testing.T) { t.Run("command-line options", func(t *testing.T) {
defer env.Patch(t, "HTTP_PROXY", "http://from-env-http.invalid")() defer env.Patch(t, "HTTP_PROXY", "http://"+userPass+"from-env-http.invalid")()
defer env.Patch(t, "http_proxy", "http://from-env-http.invalid")() defer env.Patch(t, "http_proxy", "http://"+userPass+"from-env-http.invalid")()
defer env.Patch(t, "HTTPS_PROXY", "https://from-env-https.invalid")() defer env.Patch(t, "HTTPS_PROXY", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")()
defer env.Patch(t, "https_proxy", "https://from-env-http.invalid")() defer env.Patch(t, "https_proxy", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")()
defer env.Patch(t, "NO_PROXY", "ignore.invalid")() defer env.Patch(t, "NO_PROXY", "ignore.invalid")()
defer env.Patch(t, "no_proxy", "ignore.invalid")() defer env.Patch(t, "no_proxy", "ignore.invalid")()
@ -210,6 +214,7 @@ func TestDaemonProxy(t *testing.T) {
assert.Assert(t, is.Contains(string(logs), "overriding existing proxy variable with value from configuration")) assert.Assert(t, is.Contains(string(logs), "overriding existing proxy variable with value from configuration"))
for _, v := range []string{"http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "no_proxy", "NO_PROXY"} { for _, v := range []string{"http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "no_proxy", "NO_PROXY"} {
assert.Assert(t, is.Contains(string(logs), "name="+v)) assert.Assert(t, is.Contains(string(logs), "name="+v))
assert.Assert(t, !strings.Contains(string(logs), userPass), "logs should not contain the non-sanitized proxy URL: %s", string(logs))
} }
c := d.NewClientT(t) c := d.NewClientT(t)
@ -235,10 +240,10 @@ func TestDaemonProxy(t *testing.T) {
// Configure proxy through configuration file // Configure proxy through configuration file
t.Run("configuration file", func(t *testing.T) { t.Run("configuration file", func(t *testing.T) {
defer env.Patch(t, "HTTP_PROXY", "http://from-env-http.invalid")() defer env.Patch(t, "HTTP_PROXY", "http://"+userPass+"from-env-http.invalid")()
defer env.Patch(t, "http_proxy", "http://from-env-http.invalid")() defer env.Patch(t, "http_proxy", "http://"+userPass+"from-env-http.invalid")()
defer env.Patch(t, "HTTPS_PROXY", "https://from-env-https.invalid")() defer env.Patch(t, "HTTPS_PROXY", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")()
defer env.Patch(t, "https_proxy", "https://from-env-http.invalid")() defer env.Patch(t, "https_proxy", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")()
defer env.Patch(t, "NO_PROXY", "ignore.invalid")() defer env.Patch(t, "NO_PROXY", "ignore.invalid")()
defer env.Patch(t, "no_proxy", "ignore.invalid")() defer env.Patch(t, "no_proxy", "ignore.invalid")()
@ -258,6 +263,7 @@ func TestDaemonProxy(t *testing.T) {
assert.Assert(t, is.Contains(string(logs), "overriding existing proxy variable with value from configuration")) assert.Assert(t, is.Contains(string(logs), "overriding existing proxy variable with value from configuration"))
for _, v := range []string{"http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "no_proxy", "NO_PROXY"} { for _, v := range []string{"http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "no_proxy", "NO_PROXY"} {
assert.Assert(t, is.Contains(string(logs), "name="+v)) assert.Assert(t, is.Contains(string(logs), "name="+v))
assert.Assert(t, !strings.Contains(string(logs), userPass), "logs should not contain the non-sanitized proxy URL: %s", string(logs))
} }
_, err = c.ImagePull(ctx, "example.org:5002/some/image:latest", types.ImagePullOptions{}) _, err = c.ImagePull(ctx, "example.org:5002/some/image:latest", types.ImagePullOptions{})
@ -280,7 +286,8 @@ func TestDaemonProxy(t *testing.T) {
// Conflicting options (passed both through command-line options and config file) // Conflicting options (passed both through command-line options and config file)
t.Run("conflicting options", func(t *testing.T) { t.Run("conflicting options", func(t *testing.T) {
const ( const (
proxyRawURL = "https://myuser:mypassword@example.org" proxyRawURL = "https://" + userPass + "example.org"
proxyURL = "https://xxxxx:xxxxx@example.org"
) )
d := daemon.New(t) d := daemon.New(t)
@ -295,8 +302,38 @@ func TestDaemonProxy(t *testing.T) {
assert.NilError(t, err) assert.NilError(t, err)
expected := fmt.Sprintf( expected := fmt.Sprintf(
`the following directives are specified both as a flag and in the configuration file: http-proxy: (from flag: %[1]s, from file: %[1]s), https-proxy: (from flag: %[1]s, from file: %[1]s), no-proxy: (from flag: example.com, from file: example.com)`, `the following directives are specified both as a flag and in the configuration file: http-proxy: (from flag: %[1]s, from file: %[1]s), https-proxy: (from flag: %[1]s, from file: %[1]s), no-proxy: (from flag: example.com, from file: example.com)`,
proxyRawURL, proxyURL,
) )
assert.Assert(t, is.Contains(string(logs), expected)) assert.Assert(t, is.Contains(string(logs), expected))
}) })
// Make sure values are sanitized when reloading the daemon-config
t.Run("reload sanitized", func(t *testing.T) {
const (
proxyRawURL = "https://" + userPass + "example.org"
proxyURL = "https://xxxxx:xxxxx@example.org"
)
d := daemon.New(t)
d.Start(t, "--http-proxy", proxyRawURL, "--https-proxy", proxyRawURL, "--no-proxy", "example.com")
defer d.Stop(t)
err := d.Signal(syscall.SIGHUP)
assert.NilError(t, err)
logs, err := d.ReadLogFile()
assert.NilError(t, err)
// FIXME: there appears to ba a race condition, which causes ReadLogFile
// to not contain the full logs after signaling the daemon to reload,
// causing the test to fail here. As a workaround, check if we
// received the "reloaded" message after signaling, and only then
// check that it's sanitized properly. For more details on this
// issue, see https://github.com/moby/moby/pull/42835/files#r713120315
if !strings.Contains(string(logs), "Reloaded configuration:") {
t.Skip("Skipping test, because we did not find 'Reloaded configuration' in the logs")
}
assert.Assert(t, is.Contains(string(logs), proxyURL))
assert.Assert(t, !strings.Contains(string(logs), userPass), "logs should not contain the non-sanitized proxy URL: %s", string(logs))
})
} }