diff --git a/cli/command/cli.go b/cli/command/cli.go index 815bc6855b30..b003f4f01d11 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -281,6 +281,17 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption) } filterResourceAttributesEnvvar() + // early return if GODEBUG is already set or the docker context is + // the default context, i.e. is a virtual context where we won't override + // any GODEBUG values. + if v := os.Getenv("GODEBUG"); cli.currentContext == DefaultContextName || v != "" { + return nil + } + meta, err := cli.contextStore.GetMetadata(cli.currentContext) + if err == nil { + setGoDebug(meta) + } + return nil } @@ -474,6 +485,57 @@ func (cli *DockerCli) getDockerEndPoint() (ep docker.Endpoint, err error) { return resolveDockerEndpoint(cli.contextStore, cn) } +// setGoDebug is an escape hatch that sets the GODEBUG environment +// variable value using docker context metadata. +// +// { +// "Name": "my-context", +// "Metadata": { "GODEBUG": "x509negativeserial=1" } +// } +// +// WARNING: Setting x509negativeserial=1 allows Go's x509 library to accept +// X.509 certificates with negative serial numbers. +// This behavior is deprecated and non-compliant with current security +// standards (RFC 5280). Accepting negative serial numbers can introduce +// serious security vulnerabilities, including the risk of certificate +// collision or bypass attacks. +// This option should only be used for legacy compatibility and never in +// production environments. +// Use at your own risk. +func setGoDebug(meta store.Metadata) { + fieldName := "GODEBUG" + godebugEnv := os.Getenv(fieldName) + // early return if GODEBUG is already set. We don't want to override what + // the user already sets. + if godebugEnv != "" { + return + } + + var cfg any + var ok bool + switch m := meta.Metadata.(type) { + case DockerContext: + cfg, ok = m.AdditionalFields[fieldName] + if !ok { + return + } + case map[string]any: + cfg, ok = m[fieldName] + if !ok { + return + } + default: + return + } + + v, ok := cfg.(string) + if !ok { + return + } + // set the GODEBUG environment variable with whatever was in the context + _ = os.Setenv(fieldName, v) +} + func (cli *DockerCli) initialize() error { cli.init.Do(func() { cli.dockerEndpoint, cli.initErr = cli.getDockerEndPoint() diff --git a/cli/command/cli_test.go b/cli/command/cli_test.go index 02e69044e631..e418a3c76df7 100644 --- a/cli/command/cli_test.go +++ b/cli/command/cli_test.go @@ -18,6 +18,7 @@ import ( "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/cli/context/store" "github.com/docker/cli/cli/flags" "github.com/moby/moby/api/types" "github.com/moby/moby/client" @@ -352,3 +353,23 @@ func TestHooksEnabled(t *testing.T) { assert.Check(t, !cli.HooksEnabled()) }) } + +func TestSetGoDebug(t *testing.T) { + t.Run("GODEBUG already set", func(t *testing.T) { + t.Setenv("GODEBUG", "val1,val2") + meta := store.Metadata{} + setGoDebug(meta) + assert.Equal(t, "val1,val2", os.Getenv("GODEBUG")) + }) + t.Run("GODEBUG in context metadata can set env", func(t *testing.T) { + meta := store.Metadata{ + Metadata: DockerContext{ + AdditionalFields: map[string]any{ + "GODEBUG": "val1,val2=1", + }, + }, + } + setGoDebug(meta) + assert.Equal(t, "val1,val2=1", os.Getenv("GODEBUG")) + }) +}