Skip to content

Commit 08e4e88

Browse files
committed
daemon: raise default minimum API version to v1.24
The daemon currently provides support for API versions all the way back to v1.12, which is the version of the API that shipped with docker 1.0. On Windows, the minimum supported version is v1.24. Such old versions of the client are rare, and supporting older API versions has accumulated significant amounts of code to remain backward-compatible (which is largely untested, and a "best-effort" at most). This patch updates the minimum API version to v1.24, which is the fallback API version used when API-version negotiation fails. The intent is to start deprecating older API versions, but no code is removed yet as part of this patch, and a DOCKER_MIN_API_VERSION environment variable is added, which allows overriding the minimum version (to allow restoring the behavior from before this patch). With this patch the daemon defaults to API v1.24 as minimum: docker version Client: Version: 24.0.2 API version: 1.43 Go version: go1.20.4 Git commit: cb74dfc Built: Thu May 25 21:50:49 2023 OS/Arch: linux/arm64 Context: default Server: Engine: Version: dev API version: 1.44 (minimum version 1.24) Go version: go1.21.3 Git commit: 0322a29 Built: Mon Dec 4 15:22:17 2023 OS/Arch: linux/arm64 Experimental: false containerd: Version: v1.7.9 GitCommit: 4f03e100cb967922bec7459a78d16ccbac9bb81d runc: Version: 1.1.10 GitCommit: v1.1.10-0-g18a0cb0 docker-init: Version: 0.19.0 GitCommit: de40ad0 Trying to use an older version of the API produces an error: DOCKER_API_VERSION=1.23 docker version Client: Version: 24.0.2 API version: 1.23 (downgraded from 1.43) Go version: go1.20.4 Git commit: cb74dfc Built: Thu May 25 21:50:49 2023 OS/Arch: linux/arm64 Context: default Error response from daemon: client version 1.23 is too old. Minimum supported API version is 1.24, please upgrade your client to a newer version To restore the previous minimum, users can start the daemon with the DOCKER_MIN_API_VERSION environment variable set: DOCKER_MIN_API_VERSION=1.12 dockerd API 1.12 is the oldest supported API version on Linux; docker version Client: Version: 24.0.2 API version: 1.43 Go version: go1.20.4 Git commit: cb74dfc Built: Thu May 25 21:50:49 2023 OS/Arch: linux/arm64 Context: default Server: Engine: Version: dev API version: 1.44 (minimum version 1.12) Go version: go1.21.3 Git commit: 0322a29 Built: Mon Dec 4 15:22:17 2023 OS/Arch: linux/arm64 Experimental: false containerd: Version: v1.7.9 GitCommit: 4f03e100cb967922bec7459a78d16ccbac9bb81d runc: Version: 1.1.10 GitCommit: v1.1.10-0-g18a0cb0 docker-init: Version: 0.19.0 GitCommit: de40ad0 When using the `DOCKER_MIN_API_VERSION` with a version of the API that is not supported, an error is produced when starting the daemon; DOCKER_MIN_API_VERSION=1.11 dockerd --validate invalid DOCKER_MIN_API_VERSION: minimum supported API version is 1.12: 1.11 DOCKER_MIN_API_VERSION=1.45 dockerd --validate invalid DOCKER_MIN_API_VERSION: maximum supported API version is 1.44: 1.45 Specifying a malformed API version also produces the same error; DOCKER_MIN_API_VERSION=hello dockerd --validate invalid DOCKER_MIN_API_VERSION: minimum supported API version is 1.12: hello Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent 4fa5a79 commit 08e4e88

11 files changed

Lines changed: 138 additions & 24 deletions

File tree

api/common_unix.go

Lines changed: 0 additions & 6 deletions
This file was deleted.

api/common_windows.go

Lines changed: 0 additions & 8 deletions
This file was deleted.

api/server/server_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import (
1515
func TestMiddlewares(t *testing.T) {
1616
srv := &Server{}
1717

18-
srv.UseMiddleware(middleware.NewVersionMiddleware("0.1omega2", api.DefaultVersion, api.MinVersion))
18+
const apiMinVersion = "1.12"
19+
srv.UseMiddleware(middleware.NewVersionMiddleware("0.1omega2", api.DefaultVersion, apiMinVersion))
1920

2021
req, _ := http.NewRequest(http.MethodGet, "/containers/json", nil)
2122
resp := httptest.NewRecorder()

client/client.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ import (
9090
// [Go stdlib]: https://github.com/golang/go/blob/6244b1946bc2101b01955468f1be502dbadd6807/src/net/http/transport.go#L558-L569
9191
const DummyHost = "api.moby.localhost"
9292

93+
// fallbackAPIVersion is the version to fallback to if API-version negotiation
94+
// fails. This version is the highest version of the API before API-version
95+
// negotiation was introduced. If negotiation fails (or no API version was
96+
// included in the API response), we assume the API server uses the most
97+
// recent version before negotiation was introduced.
98+
const fallbackAPIVersion = "1.24"
99+
93100
// Client is the API client that performs all operations
94101
// against a docker server.
95102
type Client struct {
@@ -329,7 +336,7 @@ func (cli *Client) NegotiateAPIVersionPing(pingResponse types.Ping) {
329336
func (cli *Client) negotiateAPIVersionPing(pingResponse types.Ping) {
330337
// default to the latest version before versioning headers existed
331338
if pingResponse.APIVersion == "" {
332-
pingResponse.APIVersion = "1.24"
339+
pingResponse.APIVersion = fallbackAPIVersion
333340
}
334341

335342
// if the client is not initialized with a version, start with the latest supported version

cmd/dockerd/daemon.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,20 @@ func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) {
520520
conf.LogLevel = opts.LogLevel
521521
conf.LogFormat = log.OutputFormat(opts.LogFormat)
522522

523+
// The DOCKER_MIN_API_VERSION env-var allows overriding the minimum API
524+
// version provided by the daemon within constraints of the minimum and
525+
// maximum (current) supported API versions.
526+
//
527+
// API versions older than [config.defaultMinAPIVersion] are deprecated and
528+
// to be removed in a future release. The "DOCKER_MIN_API_VERSION" env-var
529+
// should only be used for exceptional cases.
530+
if ver := os.Getenv("DOCKER_MIN_API_VERSION"); ver != "" {
531+
if err := config.ValidateMinAPIVersion(ver); err != nil {
532+
return nil, errors.Wrap(err, "invalid DOCKER_MIN_API_VERSION")
533+
}
534+
conf.MinAPIVersion = ver
535+
}
536+
523537
if flags.Changed(FlagTLS) {
524538
conf.TLS = &opts.TLS
525539
}
@@ -689,7 +703,7 @@ func initMiddlewares(s *apiserver.Server, cfg *config.Config, pluginStore plugin
689703
exp := middleware.NewExperimentalMiddleware(cfg.Experimental)
690704
s.UseMiddleware(exp)
691705

692-
vm := middleware.NewVersionMiddleware(v, api.DefaultVersion, api.MinVersion)
706+
vm := middleware.NewVersionMiddleware(v, api.DefaultVersion, cfg.MinAPIVersion)
693707
s.UseMiddleware(vm)
694708

695709
if cfg.CorsHeaders != "" {

daemon/config/config.go

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,17 @@ import (
1010
"os"
1111
"strings"
1212

13-
"golang.org/x/text/encoding"
14-
"golang.org/x/text/encoding/unicode"
15-
"golang.org/x/text/transform"
16-
1713
"github.com/containerd/log"
14+
"github.com/docker/docker/api"
15+
"github.com/docker/docker/api/types/versions"
1816
"github.com/docker/docker/opts"
1917
"github.com/docker/docker/registry"
2018
"github.com/imdario/mergo"
2119
"github.com/pkg/errors"
2220
"github.com/spf13/pflag"
21+
"golang.org/x/text/encoding"
22+
"golang.org/x/text/encoding/unicode"
23+
"golang.org/x/text/transform"
2324
)
2425

2526
const (
@@ -53,7 +54,11 @@ const (
5354
DefaultContainersNamespace = "moby"
5455
// DefaultPluginNamespace is the name of the default containerd namespace used for plugins.
5556
DefaultPluginNamespace = "plugins.moby"
56-
57+
// defaultMinAPIVersion is the minimum API version supported by the API.
58+
// This version can be overridden through the "DOCKER_MIN_API_VERSION"
59+
// environment variable. The minimum allowed version is determined
60+
// by [minAPIVersion].
61+
defaultMinAPIVersion = "1.24"
5762
// SeccompProfileDefault is the built-in default seccomp profile.
5863
SeccompProfileDefault = "builtin"
5964
// SeccompProfileUnconfined is a special profile name for seccomp to use an
@@ -247,6 +252,17 @@ type CommonConfig struct {
247252

248253
// CDISpecDirs is a list of directories in which CDI specifications can be found.
249254
CDISpecDirs []string `json:"cdi-spec-dirs,omitempty"`
255+
256+
// The minimum API version provided by the daemon. Defaults to [defaultMinAPIVersion].
257+
//
258+
// The DOCKER_MIN_API_VERSION allows overriding the minimum API version within
259+
// constraints of the minimum and maximum (current) supported API versions.
260+
//
261+
// API versions older than [defaultMinAPIVersion] are deprecated and
262+
// to be removed in a future release. The "DOCKER_MIN_API_VERSION" env
263+
// var should only be used for exceptional cases, and the MinAPIVersion
264+
// field is therefore not included in the JSON representation.
265+
MinAPIVersion string `json:"-"`
250266
}
251267

252268
// Proxies holds the proxies that are configured for the daemon.
@@ -290,6 +306,7 @@ func New() (*Config, error) {
290306
ContainerdNamespace: DefaultContainersNamespace,
291307
ContainerdPluginNamespace: DefaultPluginNamespace,
292308
DefaultRuntime: StockRuntimeName,
309+
MinAPIVersion: defaultMinAPIVersion,
293310
},
294311
}
295312

@@ -583,6 +600,25 @@ func findConfigurationConflicts(config map[string]interface{}, flags *pflag.Flag
583600
return nil
584601
}
585602

603+
// ValidateMinAPIVersion verifies if the given API version is within the
604+
// range supported by the daemon. It is used to validate a custom minimum
605+
// API version set through DOCKER_MIN_API_VERSION.
606+
func ValidateMinAPIVersion(ver string) error {
607+
if ver == "" {
608+
return errors.New(`value is empty`)
609+
}
610+
if strings.EqualFold(ver[0:1], "v") {
611+
return errors.New(`API version must be provided without "v" prefix`)
612+
}
613+
if versions.LessThan(ver, minAPIVersion) {
614+
return errors.Errorf(`minimum supported API version is %s: %s`, minAPIVersion, ver)
615+
}
616+
if versions.GreaterThan(ver, api.DefaultVersion) {
617+
return errors.Errorf(`maximum supported API version is %s: %s`, api.DefaultVersion, ver)
618+
}
619+
return nil
620+
}
621+
586622
// Validate validates some specific configs.
587623
// such as config.DNS, config.Labels, config.DNSSearch,
588624
// as well as config.MaxConcurrentDownloads, config.MaxConcurrentUploads and config.MaxDownloadAttempts.

daemon/config/config_linux.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ const (
2929
// StockRuntimeName is the reserved name/alias used to represent the
3030
// OCI runtime being shipped with the docker daemon package.
3131
StockRuntimeName = "runc"
32+
33+
// minAPIVersion represents Minimum REST API version supported
34+
minAPIVersion = "1.12"
3235
)
3336

3437
// BridgeConfig stores all the parameters for both the bridge driver and the default bridge network.

daemon/config/config_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99
"testing"
1010

11+
"github.com/docker/docker/api"
1112
"github.com/docker/docker/libnetwork/ipamutils"
1213
"github.com/docker/docker/opts"
1314
"github.com/google/go-cmp/cmp"
@@ -502,6 +503,62 @@ func TestValidateConfiguration(t *testing.T) {
502503
}
503504
}
504505

506+
func TestValidateMinAPIVersion(t *testing.T) {
507+
t.Parallel()
508+
tests := []struct {
509+
doc string
510+
input string
511+
expectedErr string
512+
}{
513+
{
514+
doc: "empty",
515+
expectedErr: "value is empty",
516+
},
517+
{
518+
doc: "with prefix",
519+
input: "v1.43",
520+
expectedErr: `API version must be provided without "v" prefix`,
521+
},
522+
{
523+
doc: "major only",
524+
input: "1",
525+
expectedErr: `minimum supported API version is`,
526+
},
527+
{
528+
doc: "too low",
529+
input: "1.0",
530+
expectedErr: `minimum supported API version is`,
531+
},
532+
{
533+
doc: "minor too high",
534+
input: "1.99",
535+
expectedErr: `maximum supported API version is`,
536+
},
537+
{
538+
doc: "major too high",
539+
input: "9.0",
540+
expectedErr: `maximum supported API version is`,
541+
},
542+
{
543+
doc: "current version",
544+
input: api.DefaultVersion,
545+
},
546+
}
547+
548+
for _, tc := range tests {
549+
tc := tc
550+
t.Run(tc.doc, func(t *testing.T) {
551+
err := ValidateMinAPIVersion(tc.input)
552+
if tc.expectedErr != "" {
553+
assert.Check(t, is.ErrorContains(err, tc.expectedErr))
554+
} else {
555+
assert.Check(t, err)
556+
}
557+
})
558+
}
559+
560+
}
561+
505562
func TestConfigInvalidDNS(t *testing.T) {
506563
tests := []struct {
507564
doc string

daemon/config/config_windows.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ const (
1313
// default value. On Windows keep this empty so the value is auto-detected
1414
// based on other options.
1515
StockRuntimeName = ""
16+
17+
// minAPIVersion represents Minimum REST API version supported
18+
// Technically the first daemon API version released on Windows is v1.25 in
19+
// engine version 1.13. However, some clients are explicitly using downlevel
20+
// APIs (e.g. docker-compose v2.1 file format) and that is just too restrictive.
21+
// Hence also allowing 1.24 on Windows.
22+
minAPIVersion string = "1.24"
1623
)
1724

1825
// BridgeConfig is meant to store all the parameters for both the bridge driver and the default bridge network. On

daemon/info.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func (daemon *Daemon) SystemVersion(ctx context.Context) (types.Version, error)
113113
Details: map[string]string{
114114
"GitCommit": dockerversion.GitCommit,
115115
"ApiVersion": api.DefaultVersion,
116-
"MinAPIVersion": api.MinVersion,
116+
"MinAPIVersion": cfg.MinAPIVersion,
117117
"GoVersion": runtime.Version(),
118118
"Os": runtime.GOOS,
119119
"Arch": runtime.GOARCH,
@@ -128,7 +128,7 @@ func (daemon *Daemon) SystemVersion(ctx context.Context) (types.Version, error)
128128
Version: dockerversion.Version,
129129
GitCommit: dockerversion.GitCommit,
130130
APIVersion: api.DefaultVersion,
131-
MinAPIVersion: api.MinVersion,
131+
MinAPIVersion: cfg.MinAPIVersion,
132132
GoVersion: runtime.Version(),
133133
Os: runtime.GOOS,
134134
Arch: runtime.GOARCH,

0 commit comments

Comments
 (0)