moby/api/server/middleware/version.go

87 lines
3.7 KiB
Go
Raw Normal View History

package middleware // import "github.com/docker/docker/api/server/middleware"
import (
"context"
"fmt"
"net/http"
"runtime"
"github.com/docker/docker/api"
"github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/types/versions"
)
// VersionMiddleware is a middleware that
// validates the client and server versions.
type VersionMiddleware struct {
serverVersion string
// defaultAPIVersion is the default API version provided by the API server,
// specified as "major.minor". It is usually configured to the latest API
// version [github.com/docker/docker/api.DefaultVersion].
//
// API requests for API versions greater than this version are rejected by
// the server and produce a [versionUnsupportedError].
defaultAPIVersion string
// minAPIVersion is the minimum API version provided by the API server,
// specified as "major.minor".
//
// API requests for API versions lower than this version are rejected by
// the server and produce a [versionUnsupportedError].
minAPIVersion string
}
// NewVersionMiddleware creates a VersionMiddleware with the given versions.
func NewVersionMiddleware(serverVersion, defaultAPIVersion, minAPIVersion string) (*VersionMiddleware, error) {
if versions.LessThan(defaultAPIVersion, api.MinSupportedAPIVersion) || versions.GreaterThan(defaultAPIVersion, api.DefaultVersion) {
return nil, fmt.Errorf("invalid default API version (%s): must be between %s and %s", defaultAPIVersion, api.MinSupportedAPIVersion, api.DefaultVersion)
}
if versions.LessThan(minAPIVersion, api.MinSupportedAPIVersion) || versions.GreaterThan(minAPIVersion, api.DefaultVersion) {
return nil, fmt.Errorf("invalid minimum API version (%s): must be between %s and %s", minAPIVersion, api.MinSupportedAPIVersion, api.DefaultVersion)
}
if versions.GreaterThan(minAPIVersion, defaultAPIVersion) {
return nil, fmt.Errorf("invalid API version: the minimum API version (%s) is higher than the default version (%s)", minAPIVersion, defaultAPIVersion)
}
return &VersionMiddleware{
serverVersion: serverVersion,
defaultAPIVersion: defaultAPIVersion,
minAPIVersion: minAPIVersion,
}, nil
}
type versionUnsupportedError struct {
Return 400 error if API client is too new Commit e98e4a71110fd33852bb755a9b8b4ebc9df904db implemented API version negotiation using the `/_ping` endpoint. In that change, URL validation for the maximum supported API version was removed from the API server (validation for the _minimum_ version was kept in place). With this feature, clients that support version negotiation would negotiate the maximum version supported by the daemon, and downgrade to an older API version if the client's default API version is not supported. However, clients that do _not_ support version negotiation can call API versions that are higher than the maximum supported version. Due to the missing version check, this is silently ignored, and the daemon's default API version is used. This is a problem, because the actual API version in use is non-deterministic; for example, calling `/v9999.9999/version` on a daemon that runs API v1.34 will use API v1.34, but calling the same URL on an older daemon may use API version v1.24. This patch reverts the removal of the API check for maximum supported versions. The documentation has been updated accordingly Before this patch is applied, the daemon returns a 200 (success): $ curl -v --unix-socket /var/run/docker.sock http://localhost/v9999.9999/version * Trying /var/run/docker.sock... * Connected to localhost (/Users/sebastiaan/Library/Containers/com.dock) port 80 (#0) > GET /v9999.9999/version HTTP/1.1 > Host: localhost > User-Agent: curl/7.54.0 > Accept: */* > < HTTP/1.1 200 OK < Api-Version: 1.32 < Content-Length: 240 < Content-Type: application/json < Date: Tue, 10 Oct 2017 09:11:29 GMT < Docker-Experimental: true < Ostype: linux < Server: Docker/17.09.0-ce (linux) < {"Version":"17.09.0-ce","ApiVersion":"1.32","MinAPIVersion":"1.12","GitCommit":"afdb6d4","GoVersion":"go1.8.3","Os":"linux","Arch":"amd64","KernelVersion":"4.9.49-moby","Experimental":true,"BuildTime":"2017-09-26T22:45:38.000000000+00:00"} * Connection #0 to host localhost left intact After this patch is applied, a 400 (Bad Request) is returned: $ curl -v --unix-socket /var/run/docker.sock http://localhost/v9999.9999/version * Trying /var/run/docker.sock... * Connected to localhost (/var/run/docker.sock) port 80 (#0) > GET /v9999.9999/info HTTP/1.1 > Host: localhost > User-Agent: curl/7.52.1 > Accept: */* > < HTTP/1.1 400 Bad Request < Content-Type: application/json < Date: Tue, 10 Oct 2017 08:08:34 GMT < Content-Length: 89 < {"message":"client version 9999.9999 is too new. Maximim supported API version is 1.34"} * Curl_http_done: called premature == 0 * Connection #0 to host localhost left intact Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-10-10 08:28:46 +00:00
version, minVersion, maxVersion string
}
func (e versionUnsupportedError) Error() string {
Return 400 error if API client is too new Commit e98e4a71110fd33852bb755a9b8b4ebc9df904db implemented API version negotiation using the `/_ping` endpoint. In that change, URL validation for the maximum supported API version was removed from the API server (validation for the _minimum_ version was kept in place). With this feature, clients that support version negotiation would negotiate the maximum version supported by the daemon, and downgrade to an older API version if the client's default API version is not supported. However, clients that do _not_ support version negotiation can call API versions that are higher than the maximum supported version. Due to the missing version check, this is silently ignored, and the daemon's default API version is used. This is a problem, because the actual API version in use is non-deterministic; for example, calling `/v9999.9999/version` on a daemon that runs API v1.34 will use API v1.34, but calling the same URL on an older daemon may use API version v1.24. This patch reverts the removal of the API check for maximum supported versions. The documentation has been updated accordingly Before this patch is applied, the daemon returns a 200 (success): $ curl -v --unix-socket /var/run/docker.sock http://localhost/v9999.9999/version * Trying /var/run/docker.sock... * Connected to localhost (/Users/sebastiaan/Library/Containers/com.dock) port 80 (#0) > GET /v9999.9999/version HTTP/1.1 > Host: localhost > User-Agent: curl/7.54.0 > Accept: */* > < HTTP/1.1 200 OK < Api-Version: 1.32 < Content-Length: 240 < Content-Type: application/json < Date: Tue, 10 Oct 2017 09:11:29 GMT < Docker-Experimental: true < Ostype: linux < Server: Docker/17.09.0-ce (linux) < {"Version":"17.09.0-ce","ApiVersion":"1.32","MinAPIVersion":"1.12","GitCommit":"afdb6d4","GoVersion":"go1.8.3","Os":"linux","Arch":"amd64","KernelVersion":"4.9.49-moby","Experimental":true,"BuildTime":"2017-09-26T22:45:38.000000000+00:00"} * Connection #0 to host localhost left intact After this patch is applied, a 400 (Bad Request) is returned: $ curl -v --unix-socket /var/run/docker.sock http://localhost/v9999.9999/version * Trying /var/run/docker.sock... * Connected to localhost (/var/run/docker.sock) port 80 (#0) > GET /v9999.9999/info HTTP/1.1 > Host: localhost > User-Agent: curl/7.52.1 > Accept: */* > < HTTP/1.1 400 Bad Request < Content-Type: application/json < Date: Tue, 10 Oct 2017 08:08:34 GMT < Content-Length: 89 < {"message":"client version 9999.9999 is too new. Maximim supported API version is 1.34"} * Curl_http_done: called premature == 0 * Connection #0 to host localhost left intact Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-10-10 08:28:46 +00:00
if e.minVersion != "" {
return fmt.Sprintf("client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", e.version, e.minVersion)
}
return fmt.Sprintf("client version %s is too new. Maximum supported API version is %s", e.version, e.maxVersion)
}
func (e versionUnsupportedError) InvalidParameter() {}
// WrapHandler returns a new handler function wrapping the previous one in the request chain.
func (v VersionMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
w.Header().Set("Server", fmt.Sprintf("Docker/%s (%s)", v.serverVersion, runtime.GOOS))
w.Header().Set("API-Version", v.defaultAPIVersion)
w.Header().Set("OSType", runtime.GOOS)
apiVersion := vars["version"]
if apiVersion == "" {
apiVersion = v.defaultAPIVersion
}
if versions.LessThan(apiVersion, v.minAPIVersion) {
return versionUnsupportedError{version: apiVersion, minVersion: v.minAPIVersion}
Return 400 error if API client is too new Commit e98e4a71110fd33852bb755a9b8b4ebc9df904db implemented API version negotiation using the `/_ping` endpoint. In that change, URL validation for the maximum supported API version was removed from the API server (validation for the _minimum_ version was kept in place). With this feature, clients that support version negotiation would negotiate the maximum version supported by the daemon, and downgrade to an older API version if the client's default API version is not supported. However, clients that do _not_ support version negotiation can call API versions that are higher than the maximum supported version. Due to the missing version check, this is silently ignored, and the daemon's default API version is used. This is a problem, because the actual API version in use is non-deterministic; for example, calling `/v9999.9999/version` on a daemon that runs API v1.34 will use API v1.34, but calling the same URL on an older daemon may use API version v1.24. This patch reverts the removal of the API check for maximum supported versions. The documentation has been updated accordingly Before this patch is applied, the daemon returns a 200 (success): $ curl -v --unix-socket /var/run/docker.sock http://localhost/v9999.9999/version * Trying /var/run/docker.sock... * Connected to localhost (/Users/sebastiaan/Library/Containers/com.dock) port 80 (#0) > GET /v9999.9999/version HTTP/1.1 > Host: localhost > User-Agent: curl/7.54.0 > Accept: */* > < HTTP/1.1 200 OK < Api-Version: 1.32 < Content-Length: 240 < Content-Type: application/json < Date: Tue, 10 Oct 2017 09:11:29 GMT < Docker-Experimental: true < Ostype: linux < Server: Docker/17.09.0-ce (linux) < {"Version":"17.09.0-ce","ApiVersion":"1.32","MinAPIVersion":"1.12","GitCommit":"afdb6d4","GoVersion":"go1.8.3","Os":"linux","Arch":"amd64","KernelVersion":"4.9.49-moby","Experimental":true,"BuildTime":"2017-09-26T22:45:38.000000000+00:00"} * Connection #0 to host localhost left intact After this patch is applied, a 400 (Bad Request) is returned: $ curl -v --unix-socket /var/run/docker.sock http://localhost/v9999.9999/version * Trying /var/run/docker.sock... * Connected to localhost (/var/run/docker.sock) port 80 (#0) > GET /v9999.9999/info HTTP/1.1 > Host: localhost > User-Agent: curl/7.52.1 > Accept: */* > < HTTP/1.1 400 Bad Request < Content-Type: application/json < Date: Tue, 10 Oct 2017 08:08:34 GMT < Content-Length: 89 < {"message":"client version 9999.9999 is too new. Maximim supported API version is 1.34"} * Curl_http_done: called premature == 0 * Connection #0 to host localhost left intact Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-10-10 08:28:46 +00:00
}
if versions.GreaterThan(apiVersion, v.defaultAPIVersion) {
return versionUnsupportedError{version: apiVersion, maxVersion: v.defaultAPIVersion}
}
ctx = context.WithValue(ctx, httputils.APIVersionKey{}, apiVersion)
return handler(ctx, w, r, vars)
}
}