Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions cli/command/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Comment on lines +535 to +536
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder if that would override what we currently set in our go.mod on buildx repo: https://github.com/docker/buildx/blob/67218bef58f934f3f19f66346891ea4fbdc1ad4e/go.mod#L187-L188

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it merges the key-value pairs that are specified by the GODEBUG environment variable with the go.mod ones.

When a GODEBUG setting is not listed in the environment variable, its value is derived from three sources: the defaults for the Go toolchain used to build the program, amended to match the Go version listed in go.mod, and then overridden by explicit //go:debug lines in the program.

https://go.dev/doc/godebug

e.g.

GODEBUG=http2client=0

---
go.mod
--
godebug (
  http2server=0
) 

will result in

godebug (
  http2server=0
  http2client=0
)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we specify GODEBUG=winsymlink=1 then it will override the value set inside buildx's go.mod. (but that's already possible without this PR)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok looks good thx!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks for checking! I was curious indeed how those options would interact with a go.mod having it set.

Curious; does that go.mod option take effect when building in GOPATH mode? Or does that ignore go.mod entirely?

}

func (cli *DockerCli) initialize() error {
cli.init.Do(func() {
cli.dockerEndpoint, cli.initErr = cli.getDockerEndPoint()
Expand Down
21 changes: 21 additions & 0 deletions cli/command/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"))
})
}
Loading