mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Windows: Block pulling uplevel images
Signed-off-by: John Howard <jhoward@microsoft.com>
This commit is contained in:
parent
68c3201626
commit
83908836d3
9 changed files with 107 additions and 36 deletions
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/libtrust"
|
||||
"github.com/opencontainers/go-digest"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
|
@ -86,7 +87,8 @@ type ImagePushConfig struct {
|
|||
type ImageConfigStore interface {
|
||||
Put([]byte) (digest.Digest, error)
|
||||
Get(digest.Digest) ([]byte, error)
|
||||
RootFSAndOSFromConfig([]byte) (*image.RootFS, string, error)
|
||||
RootFSFromConfig([]byte) (*image.RootFS, error)
|
||||
PlatformFromConfig([]byte) (*specs.Platform, error)
|
||||
}
|
||||
|
||||
// PushLayerProvider provides layers to be pushed by ChainID.
|
||||
|
@ -140,18 +142,26 @@ func (s *imageConfigStore) Get(d digest.Digest) ([]byte, error) {
|
|||
return img.RawJSON(), nil
|
||||
}
|
||||
|
||||
func (s *imageConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, string, error) {
|
||||
func (s *imageConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) {
|
||||
var unmarshalledConfig image.Image
|
||||
if err := json.Unmarshal(c, &unmarshalledConfig); err != nil {
|
||||
return nil, "", err
|
||||
return nil, err
|
||||
}
|
||||
return unmarshalledConfig.RootFS, nil
|
||||
}
|
||||
|
||||
func (s *imageConfigStore) PlatformFromConfig(c []byte) (*specs.Platform, error) {
|
||||
var unmarshalledConfig image.Image
|
||||
if err := json.Unmarshal(c, &unmarshalledConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// fail immediately on Windows when downloading a non-Windows image
|
||||
// and vice versa. Exception on Windows if Linux Containers are enabled.
|
||||
if runtime.GOOS == "windows" && unmarshalledConfig.OS == "linux" && !system.LCOWSupported() {
|
||||
return nil, "", fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
|
||||
return nil, fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
|
||||
} else if runtime.GOOS != "windows" && unmarshalledConfig.OS == "windows" {
|
||||
return nil, "", fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
|
||||
return nil, fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
|
||||
}
|
||||
|
||||
os := unmarshalledConfig.OS
|
||||
|
@ -159,9 +169,9 @@ func (s *imageConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, strin
|
|||
os = runtime.GOOS
|
||||
}
|
||||
if !system.IsOSSupported(os) {
|
||||
return nil, "", system.ErrNotSupportedOperatingSystem
|
||||
return nil, system.ErrNotSupportedOperatingSystem
|
||||
}
|
||||
return unmarshalledConfig.RootFS, os, nil
|
||||
return &specs.Platform{OS: os, OSVersion: unmarshalledConfig.OSVersion}, nil
|
||||
}
|
||||
|
||||
type storeLayerProvider struct {
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
refstore "github.com/docker/docker/reference"
|
||||
"github.com/docker/docker/registry"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
|
@ -584,11 +585,11 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
|
|||
}()
|
||||
|
||||
var (
|
||||
configJSON []byte // raw serialized image config
|
||||
downloadedRootFS *image.RootFS // rootFS from registered layers
|
||||
configRootFS *image.RootFS // rootFS from configuration
|
||||
release func() // release resources from rootFS download
|
||||
configOS string // for LCOW when registering downloaded layers
|
||||
configJSON []byte // raw serialized image config
|
||||
downloadedRootFS *image.RootFS // rootFS from registered layers
|
||||
configRootFS *image.RootFS // rootFS from configuration
|
||||
release func() // release resources from rootFS download
|
||||
configPlatform *specs.Platform // for LCOW when registering downloaded layers
|
||||
)
|
||||
|
||||
// https://github.com/docker/docker/issues/24766 - Err on the side of caution,
|
||||
|
@ -600,14 +601,16 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
|
|||
// check to block Windows images being pulled on Linux is implemented, it
|
||||
// may be necessary to perform the same type of serialisation.
|
||||
if runtime.GOOS == "windows" {
|
||||
configJSON, configRootFS, configOS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
|
||||
configJSON, configRootFS, configPlatform, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if configRootFS == nil {
|
||||
return "", "", errRootFSInvalid
|
||||
}
|
||||
if err := checkImageCompatibility(configPlatform.OS, configPlatform.OSVersion); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if len(descriptors) != len(configRootFS.DiffIDs) {
|
||||
return "", "", errRootFSMismatch
|
||||
|
@ -615,8 +618,8 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
|
|||
|
||||
// Early bath if the requested OS doesn't match that of the configuration.
|
||||
// This avoids doing the download, only to potentially fail later.
|
||||
if !strings.EqualFold(configOS, requestedOS) {
|
||||
return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configOS, requestedOS)
|
||||
if !strings.EqualFold(configPlatform.OS, requestedOS) {
|
||||
return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configPlatform.OS, requestedOS)
|
||||
}
|
||||
|
||||
// Populate diff ids in descriptors to avoid downloading foreign layers
|
||||
|
@ -698,16 +701,20 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
|
|||
return imageID, manifestDigest, nil
|
||||
}
|
||||
|
||||
func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, string, error) {
|
||||
func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, *specs.Platform, error) {
|
||||
select {
|
||||
case configJSON := <-configChan:
|
||||
rootfs, os, err := s.RootFSAndOSFromConfig(configJSON)
|
||||
rootfs, err := s.RootFSFromConfig(configJSON)
|
||||
if err != nil {
|
||||
return nil, nil, "", err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return configJSON, rootfs, os, nil
|
||||
platform, err := s.PlatformFromConfig(configJSON)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return configJSON, rootfs, platform, nil
|
||||
case err := <-errChan:
|
||||
return nil, nil, "", err
|
||||
return nil, nil, nil, err
|
||||
// Don't need a case for ctx.Done in the select because cancellation
|
||||
// will trigger an error in p.pullSchema2ImageConfig.
|
||||
}
|
||||
|
@ -736,6 +743,10 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf
|
|||
}
|
||||
manifestDigest := manifestMatches[0].Digest
|
||||
|
||||
if err := checkImageCompatibility(manifestMatches[0].Platform.OS, manifestMatches[0].Platform.OSVersion); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
manSvc, err := p.repo.Manifests(ctx)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
|
|
|
@ -27,3 +27,8 @@ func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []m
|
|||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
// checkImageCompatibility is a Windows-specific function. No-op on Linux
|
||||
func checkImageCompatibility(imageOS, imageOSVersion string) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package distribution // import "github.com/docker/docker/distribution"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
|
@ -63,7 +65,6 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
|
|||
func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []manifestlist.ManifestDescriptor {
|
||||
osVersion := ""
|
||||
if os == "windows" {
|
||||
// TODO: Add UBR (Update Build Release) component after build
|
||||
version := system.GetOSVersion()
|
||||
osVersion = fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
|
||||
logrus.Debugf("will prefer entries with version %s", osVersion)
|
||||
|
@ -71,10 +72,11 @@ func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []m
|
|||
|
||||
var matches []manifestlist.ManifestDescriptor
|
||||
for _, manifestDescriptor := range manifests {
|
||||
// TODO: Consider filtering out greater versions, including only greater UBR
|
||||
if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == os {
|
||||
matches = append(matches, manifestDescriptor)
|
||||
logrus.Debugf("found match for %s/%s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
|
||||
logrus.Debugf("found match for %s/%s %s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.Platform.OSVersion, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
|
||||
} else {
|
||||
logrus.Debugf("ignoring %s/%s %s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.Platform.OSVersion, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
|
||||
}
|
||||
}
|
||||
if os == "windows" {
|
||||
|
@ -107,3 +109,22 @@ func (mbv manifestsByVersion) Len() int {
|
|||
func (mbv manifestsByVersion) Swap(i, j int) {
|
||||
mbv.list[i], mbv.list[j] = mbv.list[j], mbv.list[i]
|
||||
}
|
||||
|
||||
// checkImageCompatibility blocks pulling incompatible images based on a later OS build
|
||||
// Fixes https://github.com/moby/moby/issues/36184.
|
||||
func checkImageCompatibility(imageOS, imageOSVersion string) error {
|
||||
if imageOS == "windows" {
|
||||
hostOSV := system.GetOSVersion()
|
||||
splitImageOSVersion := strings.Split(imageOSVersion, ".") // eg 10.0.16299.nnnn
|
||||
if len(splitImageOSVersion) >= 3 {
|
||||
if imageOSBuild, err := strconv.Atoi(splitImageOSVersion[2]); err == nil {
|
||||
if imageOSBuild > int(hostOSV.Build) {
|
||||
errMsg := fmt.Sprintf("a Windows version %s.%s.%s-based image is incompatible with a %s host", splitImageOSVersion[0], splitImageOSVersion[1], splitImageOSVersion[2], hostOSV.ToString())
|
||||
logrus.Debugf(errMsg)
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -118,12 +118,17 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, id
|
|||
return fmt.Errorf("could not find image from tag %s: %v", reference.FamiliarString(ref), err)
|
||||
}
|
||||
|
||||
rootfs, os, err := p.config.ImageStore.RootFSAndOSFromConfig(imgConfig)
|
||||
rootfs, err := p.config.ImageStore.RootFSFromConfig(imgConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get rootfs for image %s: %s", reference.FamiliarString(ref), err)
|
||||
}
|
||||
|
||||
l, err := p.config.LayerStores[os].Get(rootfs.ChainID())
|
||||
platform, err := p.config.ImageStore.PlatformFromConfig(imgConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get platform for image %s: %s", reference.FamiliarString(ref), err)
|
||||
}
|
||||
|
||||
l, err := p.config.LayerStores[platform.OS].Get(rootfs.ChainID())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get top layer from image: %v", err)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -53,6 +54,10 @@ func GetOSVersion() OSVersion {
|
|||
return osv
|
||||
}
|
||||
|
||||
func (osv OSVersion) ToString() string {
|
||||
return fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.Build)
|
||||
}
|
||||
|
||||
// IsWindowsClient returns true if the SKU is client
|
||||
// @engine maintainers - this function should not be removed or modified as it
|
||||
// is used to enforce licensing restrictions on Windows.
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
"github.com/docker/docker/plugin/v2"
|
||||
refstore "github.com/docker/docker/reference"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
|
@ -146,10 +147,15 @@ func (s *tempConfigStore) Get(d digest.Digest) ([]byte, error) {
|
|||
return s.config, nil
|
||||
}
|
||||
|
||||
func (s *tempConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, string, error) {
|
||||
func (s *tempConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) {
|
||||
return configToRootFS(c)
|
||||
}
|
||||
|
||||
func (s *tempConfigStore) PlatformFromConfig(c []byte) (*specs.Platform, error) {
|
||||
// TODO: LCOW/Plugins. This will need revisiting. For now use the runtime OS
|
||||
return &specs.Platform{OS: runtime.GOOS}, nil
|
||||
}
|
||||
|
||||
func computePrivileges(c types.PluginConfig) types.PluginPrivileges {
|
||||
var privileges types.PluginPrivileges
|
||||
if c.Network.Type != "null" && c.Network.Type != "bridge" && c.Network.Type != "" {
|
||||
|
@ -534,10 +540,15 @@ func (s *pluginConfigStore) Get(d digest.Digest) ([]byte, error) {
|
|||
return ioutil.ReadAll(rwc)
|
||||
}
|
||||
|
||||
func (s *pluginConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, string, error) {
|
||||
func (s *pluginConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) {
|
||||
return configToRootFS(c)
|
||||
}
|
||||
|
||||
func (s *pluginConfigStore) PlatformFromConfig(c []byte) (*specs.Platform, error) {
|
||||
// TODO: LCOW/Plugins. This will need revisiting. For now use the runtime OS
|
||||
return &specs.Platform{OS: runtime.GOOS}, nil
|
||||
}
|
||||
|
||||
type pluginLayerProvider struct {
|
||||
pm *Manager
|
||||
plugin *v2.Plugin
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/distribution/xfer"
|
||||
"github.com/docker/docker/image"
|
||||
|
@ -14,6 +15,7 @@ import (
|
|||
"github.com/docker/docker/pkg/chrootarchive"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/opencontainers/go-digest"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
|
@ -178,6 +180,10 @@ func (dm *downloadManager) Put(dt []byte) (digest.Digest, error) {
|
|||
func (dm *downloadManager) Get(d digest.Digest) ([]byte, error) {
|
||||
return nil, fmt.Errorf("digest not found")
|
||||
}
|
||||
func (dm *downloadManager) RootFSAndOSFromConfig(c []byte) (*image.RootFS, string, error) {
|
||||
func (dm *downloadManager) RootFSFromConfig(c []byte) (*image.RootFS, error) {
|
||||
return configToRootFS(c)
|
||||
}
|
||||
func (dm *downloadManager) PlatformFromConfig(c []byte) (*specs.Platform, error) {
|
||||
// TODO: LCOW/Plugins. This will need revisiting. For now use the runtime OS
|
||||
return &specs.Platform{OS: runtime.GOOS}, nil
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -375,19 +374,17 @@ func isEqualPrivilege(a, b types.PluginPrivilege) bool {
|
|||
return reflect.DeepEqual(a.Value, b.Value)
|
||||
}
|
||||
|
||||
func configToRootFS(c []byte) (*image.RootFS, string, error) {
|
||||
// TODO @jhowardmsft LCOW - Will need to revisit this.
|
||||
os := runtime.GOOS
|
||||
func configToRootFS(c []byte) (*image.RootFS, error) {
|
||||
var pluginConfig types.PluginConfig
|
||||
if err := json.Unmarshal(c, &pluginConfig); err != nil {
|
||||
return nil, "", err
|
||||
return nil, err
|
||||
}
|
||||
// validation for empty rootfs is in distribution code
|
||||
if pluginConfig.Rootfs == nil {
|
||||
return nil, os, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return rootFSFromPlugin(pluginConfig.Rootfs), os, nil
|
||||
return rootFSFromPlugin(pluginConfig.Rootfs), nil
|
||||
}
|
||||
|
||||
func rootFSFromPlugin(pluginfs *types.PluginConfigRootfs) *image.RootFS {
|
||||
|
|
Loading…
Reference in a new issue