Browse Source

Merge pull request #33151 from nwt/push-foreign-layers

Add daemon option to push foreign layers
Sebastiaan van Stijn 8 năm trước cách đây
mục cha
commit
a30ef99e8d

+ 5 - 3
api/types/registry/registry.go

@@ -10,9 +10,11 @@ import (
 
 // ServiceConfig stores daemon registry services configuration.
 type ServiceConfig struct {
-	InsecureRegistryCIDRs []*NetIPNet           `json:"InsecureRegistryCIDRs"`
-	IndexConfigs          map[string]*IndexInfo `json:"IndexConfigs"`
-	Mirrors               []string
+	AllowNondistributableArtifactsCIDRs     []*NetIPNet
+	AllowNondistributableArtifactsHostnames []string
+	InsecureRegistryCIDRs                   []*NetIPNet           `json:"InsecureRegistryCIDRs"`
+	IndexConfigs                            map[string]*IndexInfo `json:"IndexConfigs"`
+	Mirrors                                 []string
 }
 
 // NetIPNet is the net.IPNet type, which can be marshalled and

+ 2 - 0
cmd/dockerd/daemon_test.go

@@ -131,6 +131,7 @@ func TestLoadDaemonConfigWithEmbeddedOptions(t *testing.T) {
 
 func TestLoadDaemonConfigWithRegistryOptions(t *testing.T) {
 	content := `{
+		"allow-nondistributable-artifacts": ["allow-nondistributable-artifacts.com"],
 		"registry-mirrors": ["https://mirrors.docker.com"],
 		"insecure-registries": ["https://insecure.docker.com"]
 	}`
@@ -142,6 +143,7 @@ func TestLoadDaemonConfigWithRegistryOptions(t *testing.T) {
 	require.NoError(t, err)
 	require.NotNil(t, loadedConfig)
 
+	assert.Len(t, loadedConfig.AllowNondistributableArtifacts, 1)
 	assert.Len(t, loadedConfig.Mirrors, 1)
 	assert.Len(t, loadedConfig.InsecureRegistries, 1)
 }

+ 1 - 0
contrib/completion/bash/docker

@@ -1969,6 +1969,7 @@ _docker_daemon() {
 	local options_with_args="
 		$global_options_with_args
 		--add-runtime
+		--allow-nondistributable-artifacts
 		--api-cors-header
 		--authorization-plugin
 		--bip

+ 1 - 0
contrib/completion/zsh/_docker

@@ -2603,6 +2603,7 @@ __docker_subcommand() {
             _arguments $(__docker_arguments) \
                 $opts_help \
                 "($help)*--add-runtime=[Register an additional OCI compatible runtime]:runtime:__docker_complete_runtimes" \
+                "($help)*--allow-nondistributable-artifacts=[Push nondistributable artifacts to specified registries]:registry: " \
                 "($help)--api-cors-header=[CORS headers in the Engine API]:CORS headers: " \
                 "($help)*--authorization-plugin=[Authorization plugins to load]" \
                 "($help -b --bridge)"{-b=,--bridge=}"[Attach containers to a network bridge]:bridge:_net_interfaces" \

+ 28 - 0
daemon/reload.go

@@ -48,6 +48,9 @@ func (daemon *Daemon) Reload(conf *config.Config) (err error) {
 	if err := daemon.reloadLabels(conf, attributes); err != nil {
 		return err
 	}
+	if err := daemon.reloadAllowNondistributableArtifacts(conf, attributes); err != nil {
+		return err
+	}
 	if err := daemon.reloadInsecureRegistries(conf, attributes); err != nil {
 		return err
 	}
@@ -217,6 +220,31 @@ func (daemon *Daemon) reloadLabels(conf *config.Config, attributes map[string]st
 	return nil
 }
 
+// reloadAllowNondistributableArtifacts updates the configuration with allow-nondistributable-artifacts options
+// and updates the passed attributes.
+func (daemon *Daemon) reloadAllowNondistributableArtifacts(conf *config.Config, attributes map[string]string) error {
+	// Update corresponding configuration.
+	if conf.IsValueSet("allow-nondistributable-artifacts") {
+		daemon.configStore.AllowNondistributableArtifacts = conf.AllowNondistributableArtifacts
+		if err := daemon.RegistryService.LoadAllowNondistributableArtifacts(conf.AllowNondistributableArtifacts); err != nil {
+			return err
+		}
+	}
+
+	// Prepare reload event attributes with updatable configurations.
+	if daemon.configStore.AllowNondistributableArtifacts != nil {
+		v, err := json.Marshal(daemon.configStore.AllowNondistributableArtifacts)
+		if err != nil {
+			return err
+		}
+		attributes["allow-nondistributable-artifacts"] = string(v)
+	} else {
+		attributes["allow-nondistributable-artifacts"] = "[]"
+	}
+
+	return nil
+}
+
 // reloadInsecureRegistries updates configuration with insecure registry option
 // and updates the passed attributes
 func (daemon *Daemon) reloadInsecureRegistries(conf *config.Config, attributes map[string]string) error {

+ 56 - 0
daemon/reload_test.go

@@ -4,6 +4,7 @@ package daemon
 
 import (
 	"reflect"
+	"sort"
 	"testing"
 	"time"
 
@@ -40,6 +41,61 @@ func TestDaemonReloadLabels(t *testing.T) {
 	}
 }
 
+func TestDaemonReloadAllowNondistributableArtifacts(t *testing.T) {
+	daemon := &Daemon{
+		configStore: &config.Config{},
+	}
+
+	// Initialize daemon with some registries.
+	daemon.RegistryService = registry.NewService(registry.ServiceOptions{
+		AllowNondistributableArtifacts: []string{
+			"127.0.0.0/8",
+			"10.10.1.11:5000",
+			"10.10.1.22:5000", // This will be removed during reload.
+			"docker1.com",
+			"docker2.com", // This will be removed during reload.
+		},
+	})
+
+	registries := []string{
+		"127.0.0.0/8",
+		"10.10.1.11:5000",
+		"10.10.1.33:5000", // This will be added during reload.
+		"docker1.com",
+		"docker3.com", // This will be added during reload.
+	}
+
+	newConfig := &config.Config{
+		CommonConfig: config.CommonConfig{
+			ServiceOptions: registry.ServiceOptions{
+				AllowNondistributableArtifacts: registries,
+			},
+			ValuesSet: map[string]interface{}{
+				"allow-nondistributable-artifacts": registries,
+			},
+		},
+	}
+
+	if err := daemon.Reload(newConfig); err != nil {
+		t.Fatal(err)
+	}
+
+	actual := []string{}
+	serviceConfig := daemon.RegistryService.ServiceConfig()
+	for _, value := range serviceConfig.AllowNondistributableArtifactsCIDRs {
+		actual = append(actual, value.String())
+	}
+	for _, value := range serviceConfig.AllowNondistributableArtifactsHostnames {
+		actual = append(actual, value)
+	}
+
+	sort.Strings(registries)
+	sort.Strings(actual)
+	if !reflect.DeepEqual(registries, actual) {
+		t.Fatalf("expected %v, got %v\n", registries, actual)
+	}
+}
+
 func TestDaemonReloadMirrors(t *testing.T) {
 	daemon := &Daemon{}
 	daemon.RegistryService = registry.NewService(registry.ServiceOptions{

+ 14 - 6
distribution/pull_v2_windows.go

@@ -23,20 +23,28 @@ func (ld *v2LayerDescriptor) Descriptor() distribution.Descriptor {
 }
 
 func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekCloser, error) {
+	blobs := ld.repo.Blobs(ctx)
+	rsc, err := blobs.Open(ctx, ld.digest)
+
 	if len(ld.src.URLs) == 0 {
-		blobs := ld.repo.Blobs(ctx)
-		return blobs.Open(ctx, ld.digest)
+		return rsc, err
 	}
 
-	var (
-		err error
-		rsc distribution.ReadSeekCloser
-	)
+	// We're done if the registry has this blob.
+	if err == nil {
+		// Seek does an HTTP GET.  If it succeeds, the blob really is accessible.
+		if _, err = rsc.Seek(0, os.SEEK_SET); err == nil {
+			return rsc, nil
+		}
+		rsc.Close()
+	}
 
 	// Find the first URL that results in a 200 result code.
 	for _, url := range ld.src.URLs {
 		logrus.Debugf("Pulling %v from foreign URL %v", ld.digest, url)
 		rsc = transport.NewHTTPReadSeeker(http.DefaultClient, url, nil)
+
+		// Seek does an HTTP GET.  If it succeeds, the blob really is accessible.
 		_, err = rsc.Seek(0, os.SEEK_SET)
 		if err == nil {
 			break

+ 9 - 4
distribution/push_v2.go

@@ -141,6 +141,7 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, id
 		hmacKey:           hmacKey,
 		repoInfo:          p.repoInfo.Name,
 		ref:               p.ref,
+		endpoint:          p.endpoint,
 		repo:              p.repo,
 		pushState:         &p.pushState,
 	}
@@ -239,6 +240,7 @@ type v2PushDescriptor struct {
 	hmacKey           []byte
 	repoInfo          reference.Named
 	ref               reference.Named
+	endpoint          registry.APIEndpoint
 	repo              distribution.Repository
 	pushState         *pushState
 	remoteDescriptor  distribution.Descriptor
@@ -259,10 +261,13 @@ func (pd *v2PushDescriptor) DiffID() layer.DiffID {
 }
 
 func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.Output) (distribution.Descriptor, error) {
-	if fs, ok := pd.layer.(distribution.Describable); ok {
-		if d := fs.Descriptor(); len(d.URLs) > 0 {
-			progress.Update(progressOutput, pd.ID(), "Skipped foreign layer")
-			return d, nil
+	// Skip foreign layers unless this registry allows nondistributable artifacts.
+	if !pd.endpoint.AllowNondistributableArtifacts {
+		if fs, ok := pd.layer.(distribution.Describable); ok {
+			if d := fs.Descriptor(); len(d.URLs) > 0 {
+				progress.Update(progressOutput, pd.ID(), "Skipped foreign layer")
+				return d, nil
+			}
 		}
 	}
 

+ 30 - 0
docs/reference/commandline/dockerd.md

@@ -23,6 +23,7 @@ A self-sufficient runtime for containers.
 
 Options:
       --add-runtime runtime                   Register an additional OCI compatible runtime (default [])
+      --allow-nondistributable-artifacts list Push nondistributable artifacts to specified registries (default [])
       --api-cors-header string                Set CORS headers in the Engine API
       --authorization-plugin list             Authorization plugins to load (default [])
       --bip string                            Specify network bridge IP
@@ -828,6 +829,32 @@ To set the DNS search domain for all Docker containers, use:
 $ sudo dockerd --dns-search example.com
 ```
 
+#### Allow push of nondistributable artifacts
+
+Some images (e.g., Windows base images) contain artifacts whose distribution is
+restricted by license. When these images are pushed to a registry, restricted
+artifacts are not included.
+
+To override this behavior for specific registries, use the
+`--allow-nondistributable-artifacts` option in one of the following forms:
+
+* `--allow-nondistributable-artifacts myregistry:5000` tells the Docker daemon
+  to push nondistributable artifacts to myregistry:5000.
+* `--allow-nondistributable-artifacts 10.1.0.0/16` tells the Docker daemon to
+  push nondistributable artifacts to all registries whose resolved IP address
+  is within the subnet described by the CIDR syntax.
+
+This option can be used multiple times.
+
+This option is useful when pushing images containing nondistributable artifacts
+to a registry on an air-gapped network so hosts on that network can pull the
+images without connecting to another server.
+
+> **Warning**: Nondistributable artifacts typically have restrictions on how
+> and where they can be distributed and shared. Only use this feature to push
+> artifacts to private registries and ensure that you are in compliance with
+> any terms that cover redistributing nondistributable artifacts.
+
 #### Insecure registries
 
 Docker considers a private registry either secure or insecure. In the rest of
@@ -1261,6 +1288,7 @@ This is a full example of the allowed configuration options on Linux:
 	"default-gateway-v6": "",
 	"icc": false,
 	"raw-logs": false,
+	"allow-nondistributable-artifacts": [],
 	"registry-mirrors": [],
 	"seccomp-profile": "",
 	"insecure-registries": [],
@@ -1330,6 +1358,7 @@ This is a full example of the allowed configuration options on Windows:
     "bridge": "",
     "fixed-cidr": "",
     "raw-logs": false,
+    "allow-nondistributable-artifacts": [],
     "registry-mirrors": [],
     "insecure-registries": [],
     "disable-legacy-registry": false
@@ -1361,6 +1390,7 @@ The list of currently supported options that can be reconfigured is this:
 - `runtimes`: it updates the list of available OCI runtimes that can
   be used to run containers
 - `authorization-plugin`: specifies the authorization plugins to use.
+- `allow-nondistributable-artifacts`: Replaces the set of registries to which the daemon will push nondistributable artifacts with a new set of registries.
 - `insecure-registries`: it replaces the daemon insecure registries with a new set of insecure registries. If some existing insecure registries in daemon's configuration are not in newly reloaded insecure resgitries, these existing ones will be removed from daemon's config.
 - `registry-mirrors`: it replaces the daemon registry mirrors with a new set of registry mirrors. If some existing registry mirrors in daemon's configuration are not in newly reloaded registry mirrors, these existing ones will be removed from daemon's config.
 

+ 1 - 1
integration-cli/docker_cli_events_unix_test.go

@@ -428,7 +428,7 @@ func (s *DockerDaemonSuite) TestDaemonEvents(c *check.C) {
 	out, err = s.d.Cmd("events", "--since=0", "--until", daemonUnixTime(c))
 	c.Assert(err, checker.IsNil)
 
-	c.Assert(out, checker.Contains, fmt.Sprintf("daemon reload %s (cluster-advertise=, cluster-store=, cluster-store-opts={}, debug=true, default-runtime=runc, default-shm-size=67108864, insecure-registries=[], labels=[\"bar=foo\"], live-restore=false, max-concurrent-downloads=1, max-concurrent-uploads=5, name=%s, registry-mirrors=[], runtimes=runc:{docker-runc []}, shutdown-timeout=10)", daemonID, daemonName))
+	c.Assert(out, checker.Contains, fmt.Sprintf("daemon reload %s (allow-nondistributable-artifacts=[], cluster-advertise=, cluster-store=, cluster-store-opts={}, debug=true, default-runtime=runc, default-shm-size=67108864, insecure-registries=[], labels=[\"bar=foo\"], live-restore=false, max-concurrent-downloads=1, max-concurrent-uploads=5, name=%s, registry-mirrors=[], runtimes=runc:{docker-runc []}, shutdown-timeout=10)", daemonID, daemonName))
 }
 
 func (s *DockerDaemonSuite) TestDaemonEventsWithFilters(c *check.C) {

+ 15 - 0
man/dockerd.8.md

@@ -7,6 +7,7 @@ dockerd - Enable daemon mode
 # SYNOPSIS
 **dockerd**
 [**--add-runtime**[=*[]*]]
+[**--allow-nondistributable-artifacts**[=*[]*]]
 [**--api-cors-header**=[=*API-CORS-HEADER*]]
 [**--authorization-plugin**[=*[]*]]
 [**-b**|**--bridge**[=*BRIDGE*]]
@@ -116,6 +117,20 @@ $ sudo dockerd --add-runtime runc=runc --add-runtime custom=/usr/local/bin/my-ru
 
   **Note**: defining runtime arguments via the command line is not supported.
 
+**--allow-nondistributable-artifacts**=[]
+  Push nondistributable artifacts to the specified registries.
+
+  List can contain elements with CIDR notation to specify a whole subnet.
+
+  This option is useful when pushing images containing nondistributable
+  artifacts to a registry on an air-gapped network so hosts on that network can
+  pull the images without connecting to another server.
+
+  **Warning**: Nondistributable artifacts typically have restrictions on how
+  and where they can be distributed and shared. Only use this feature to push
+  artifacts to private registries and ensure that you are in compliance with
+  any terms that cover redistributing nondistributable artifacts.
+
 **--api-cors-header**=""
   Set CORS headers in the Engine API. Default is cors disabled. Give urls like
   "http://foo, http://bar, ...". Give "*" to allow all.

+ 75 - 8
registry/config.go

@@ -18,8 +18,9 @@ import (
 
 // ServiceOptions holds command line options.
 type ServiceOptions struct {
-	Mirrors            []string `json:"registry-mirrors,omitempty"`
-	InsecureRegistries []string `json:"insecure-registries,omitempty"`
+	AllowNondistributableArtifacts []string `json:"allow-nondistributable-artifacts,omitempty"`
+	Mirrors                        []string `json:"registry-mirrors,omitempty"`
+	InsecureRegistries             []string `json:"insecure-registries,omitempty"`
 
 	// V2Only controls access to legacy registries.  If it is set to true via the
 	// command line flag the daemon will not attempt to contact v1 legacy registries
@@ -74,9 +75,11 @@ var lookupIP = net.LookupIP
 // InstallCliFlags adds command-line options to the top-level flag parser for
 // the current process.
 func (options *ServiceOptions) InstallCliFlags(flags *pflag.FlagSet) {
+	ana := opts.NewNamedListOptsRef("allow-nondistributable-artifacts", &options.AllowNondistributableArtifacts, ValidateIndexName)
 	mirrors := opts.NewNamedListOptsRef("registry-mirrors", &options.Mirrors, ValidateMirror)
 	insecureRegistries := opts.NewNamedListOptsRef("insecure-registries", &options.InsecureRegistries, ValidateIndexName)
 
+	flags.Var(ana, "allow-nondistributable-artifacts", "Allow push of nondistributable artifacts to registry")
 	flags.Var(mirrors, "registry-mirror", "Preferred Docker registry mirror")
 	flags.Var(insecureRegistries, "insecure-registry", "Enable insecure registry communication")
 
@@ -95,12 +98,50 @@ func newServiceConfig(options ServiceOptions) *serviceConfig {
 		V2Only: options.V2Only,
 	}
 
+	config.LoadAllowNondistributableArtifacts(options.AllowNondistributableArtifacts)
 	config.LoadMirrors(options.Mirrors)
 	config.LoadInsecureRegistries(options.InsecureRegistries)
 
 	return config
 }
 
+// LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries into config.
+func (config *serviceConfig) LoadAllowNondistributableArtifacts(registries []string) error {
+	cidrs := map[string]*registrytypes.NetIPNet{}
+	hostnames := map[string]bool{}
+
+	for _, r := range registries {
+		if _, err := ValidateIndexName(r); err != nil {
+			return err
+		}
+		if validateNoScheme(r) != nil {
+			return fmt.Errorf("allow-nondistributable-artifacts registry %s should not contain '://'", r)
+		}
+
+		if _, ipnet, err := net.ParseCIDR(r); err == nil {
+			// Valid CIDR.
+			cidrs[ipnet.String()] = (*registrytypes.NetIPNet)(ipnet)
+		} else if err := validateHostPort(r); err == nil {
+			// Must be `host:port` if not CIDR.
+			hostnames[r] = true
+		} else {
+			return fmt.Errorf("allow-nondistributable-artifacts registry %s is not valid: %v", r, err)
+		}
+	}
+
+	config.AllowNondistributableArtifactsCIDRs = make([]*(registrytypes.NetIPNet), 0)
+	for _, c := range cidrs {
+		config.AllowNondistributableArtifactsCIDRs = append(config.AllowNondistributableArtifactsCIDRs, c)
+	}
+
+	config.AllowNondistributableArtifactsHostnames = make([]string, 0)
+	for h := range hostnames {
+		config.AllowNondistributableArtifactsHostnames = append(config.AllowNondistributableArtifactsHostnames, h)
+	}
+
+	return nil
+}
+
 // LoadMirrors loads mirrors to config, after removing duplicates.
 // Returns an error if mirrors contains an invalid mirror.
 func (config *serviceConfig) LoadMirrors(mirrors []string) error {
@@ -211,6 +252,25 @@ skip:
 	return nil
 }
 
+// allowNondistributableArtifacts returns true if the provided hostname is part of the list of regsitries
+// that allow push of nondistributable artifacts.
+//
+// The list can contain elements with CIDR notation to specify a whole subnet. If the subnet contains an IP
+// of the registry specified by hostname, true is returned.
+//
+// hostname should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
+// or an IP address. If it is a domain name, then it will be resolved to IP addresses for matching. If
+// resolution fails, CIDR matching is not performed.
+func allowNondistributableArtifacts(config *serviceConfig, hostname string) bool {
+	for _, h := range config.AllowNondistributableArtifactsHostnames {
+		if h == hostname {
+			return true
+		}
+	}
+
+	return isCIDRMatch(config.AllowNondistributableArtifactsCIDRs, hostname)
+}
+
 // isSecureIndex returns false if the provided indexName is part of the list of insecure registries
 // Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
 //
@@ -229,10 +289,17 @@ func isSecureIndex(config *serviceConfig, indexName string) bool {
 		return index.Secure
 	}
 
-	host, _, err := net.SplitHostPort(indexName)
+	return !isCIDRMatch(config.InsecureRegistryCIDRs, indexName)
+}
+
+// isCIDRMatch returns true if URLHost matches an element of cidrs. URLHost is a URL.Host (`host:port` or `host`)
+// where the `host` part can be either a domain name or an IP address. If it is a domain name, then it will be
+// resolved to IP addresses for matching. If resolution fails, false is returned.
+func isCIDRMatch(cidrs []*registrytypes.NetIPNet, URLHost string) bool {
+	host, _, err := net.SplitHostPort(URLHost)
 	if err != nil {
-		// assume indexName is of the form `host` without the port and go on.
-		host = indexName
+		// Assume URLHost is of the form `host` without the port and go on.
+		host = URLHost
 	}
 
 	addrs, err := lookupIP(host)
@@ -249,15 +316,15 @@ func isSecureIndex(config *serviceConfig, indexName string) bool {
 
 	// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
 	for _, addr := range addrs {
-		for _, ipnet := range config.InsecureRegistryCIDRs {
+		for _, ipnet := range cidrs {
 			// check if the addr falls in the subnet
 			if (*net.IPNet)(ipnet).Contains(addr) {
-				return false
+				return true
 			}
 		}
 	}
 
-	return true
+	return false
 }
 
 // ValidateMirror validates an HTTP(S) registry mirror

+ 119 - 0
registry/config_test.go

@@ -1,10 +1,129 @@
 package registry
 
 import (
+	"reflect"
+	"sort"
 	"strings"
 	"testing"
 )
 
+func TestLoadAllowNondistributableArtifacts(t *testing.T) {
+	testCases := []struct {
+		registries []string
+		cidrStrs   []string
+		hostnames  []string
+		err        string
+	}{
+		{
+			registries: []string{"1.2.3.0/24"},
+			cidrStrs:   []string{"1.2.3.0/24"},
+		},
+		{
+			registries: []string{"2001:db8::/120"},
+			cidrStrs:   []string{"2001:db8::/120"},
+		},
+		{
+			registries: []string{"127.0.0.1"},
+			hostnames:  []string{"127.0.0.1"},
+		},
+		{
+			registries: []string{"127.0.0.1:8080"},
+			hostnames:  []string{"127.0.0.1:8080"},
+		},
+		{
+			registries: []string{"2001:db8::1"},
+			hostnames:  []string{"2001:db8::1"},
+		},
+		{
+			registries: []string{"[2001:db8::1]:80"},
+			hostnames:  []string{"[2001:db8::1]:80"},
+		},
+		{
+			registries: []string{"[2001:db8::1]:80"},
+			hostnames:  []string{"[2001:db8::1]:80"},
+		},
+		{
+			registries: []string{"1.2.3.0/24", "2001:db8::/120", "127.0.0.1", "127.0.0.1:8080"},
+			cidrStrs:   []string{"1.2.3.0/24", "2001:db8::/120"},
+			hostnames:  []string{"127.0.0.1", "127.0.0.1:8080"},
+		},
+
+		{
+			registries: []string{"http://mytest.com"},
+			err:        "allow-nondistributable-artifacts registry http://mytest.com should not contain '://'",
+		},
+		{
+			registries: []string{"https://mytest.com"},
+			err:        "allow-nondistributable-artifacts registry https://mytest.com should not contain '://'",
+		},
+		{
+			registries: []string{"HTTP://mytest.com"},
+			err:        "allow-nondistributable-artifacts registry HTTP://mytest.com should not contain '://'",
+		},
+		{
+			registries: []string{"svn://mytest.com"},
+			err:        "allow-nondistributable-artifacts registry svn://mytest.com should not contain '://'",
+		},
+		{
+			registries: []string{"-invalid-registry"},
+			err:        "Cannot begin or end with a hyphen",
+		},
+		{
+			registries: []string{`mytest-.com`},
+			err:        `allow-nondistributable-artifacts registry mytest-.com is not valid: invalid host "mytest-.com"`,
+		},
+		{
+			registries: []string{`1200:0000:AB00:1234:0000:2552:7777:1313:8080`},
+			err:        `allow-nondistributable-artifacts registry 1200:0000:AB00:1234:0000:2552:7777:1313:8080 is not valid: invalid host "1200:0000:AB00:1234:0000:2552:7777:1313:8080"`,
+		},
+		{
+			registries: []string{`mytest.com:500000`},
+			err:        `allow-nondistributable-artifacts registry mytest.com:500000 is not valid: invalid port "500000"`,
+		},
+		{
+			registries: []string{`"mytest.com"`},
+			err:        `allow-nondistributable-artifacts registry "mytest.com" is not valid: invalid host "\"mytest.com\""`,
+		},
+		{
+			registries: []string{`"mytest.com:5000"`},
+			err:        `allow-nondistributable-artifacts registry "mytest.com:5000" is not valid: invalid host "\"mytest.com"`,
+		},
+	}
+	for _, testCase := range testCases {
+		config := newServiceConfig(ServiceOptions{})
+		err := config.LoadAllowNondistributableArtifacts(testCase.registries)
+		if testCase.err == "" {
+			if err != nil {
+				t.Fatalf("expect no error, got '%s'", err)
+			}
+
+			cidrStrs := []string{}
+			for _, c := range config.AllowNondistributableArtifactsCIDRs {
+				cidrStrs = append(cidrStrs, c.String())
+			}
+
+			sort.Strings(testCase.cidrStrs)
+			sort.Strings(cidrStrs)
+			if (len(testCase.cidrStrs) > 0 || len(cidrStrs) > 0) && !reflect.DeepEqual(testCase.cidrStrs, cidrStrs) {
+				t.Fatalf("expect AllowNondistributableArtifactsCIDRs to be '%+v', got '%+v'", testCase.cidrStrs, cidrStrs)
+			}
+
+			sort.Strings(testCase.hostnames)
+			sort.Strings(config.AllowNondistributableArtifactsHostnames)
+			if (len(testCase.hostnames) > 0 || len(config.AllowNondistributableArtifactsHostnames) > 0) && !reflect.DeepEqual(testCase.hostnames, config.AllowNondistributableArtifactsHostnames) {
+				t.Fatalf("expect AllowNondistributableArtifactsHostnames to be '%+v', got '%+v'", testCase.hostnames, config.AllowNondistributableArtifactsHostnames)
+			}
+		} else {
+			if err == nil {
+				t.Fatalf("expect error '%s', got no error", testCase.err)
+			}
+			if !strings.Contains(err.Error(), testCase.err) {
+				t.Fatalf("expect error '%s', got '%s'", testCase.err, err)
+			}
+		}
+	}
+}
+
 func TestValidateMirror(t *testing.T) {
 	valid := []string{
 		"http://mirror-1.com",

+ 42 - 0
registry/registry_test.go

@@ -811,6 +811,48 @@ func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
 	}
 }
 
+func TestAllowNondistributableArtifacts(t *testing.T) {
+	tests := []struct {
+		addr       string
+		registries []string
+		expected   bool
+	}{
+		{IndexName, nil, false},
+		{"example.com", []string{}, false},
+		{"example.com", []string{"example.com"}, true},
+		{"localhost", []string{"localhost:5000"}, false},
+		{"localhost:5000", []string{"localhost:5000"}, true},
+		{"localhost", []string{"example.com"}, false},
+		{"127.0.0.1:5000", []string{"127.0.0.1:5000"}, true},
+		{"localhost", nil, false},
+		{"localhost:5000", nil, false},
+		{"127.0.0.1", nil, false},
+		{"localhost", []string{"example.com"}, false},
+		{"127.0.0.1", []string{"example.com"}, false},
+		{"example.com", nil, false},
+		{"example.com", []string{"example.com"}, true},
+		{"127.0.0.1", []string{"example.com"}, false},
+		{"127.0.0.1:5000", []string{"example.com"}, false},
+		{"example.com:5000", []string{"42.42.0.0/16"}, true},
+		{"example.com", []string{"42.42.0.0/16"}, true},
+		{"example.com:5000", []string{"42.42.42.42/8"}, true},
+		{"127.0.0.1:5000", []string{"127.0.0.0/8"}, true},
+		{"42.42.42.42:5000", []string{"42.1.1.1/8"}, true},
+		{"invalid.domain.com", []string{"42.42.0.0/16"}, false},
+		{"invalid.domain.com", []string{"invalid.domain.com"}, true},
+		{"invalid.domain.com:5000", []string{"invalid.domain.com"}, false},
+		{"invalid.domain.com:5000", []string{"invalid.domain.com:5000"}, true},
+	}
+	for _, tt := range tests {
+		config := newServiceConfig(ServiceOptions{
+			AllowNondistributableArtifacts: tt.registries,
+		})
+		if v := allowNondistributableArtifacts(config, tt.addr); v != tt.expected {
+			t.Errorf("allowNondistributableArtifacts failed for %q %v, expected %v got %v", tt.addr, tt.registries, tt.expected, v)
+		}
+	}
+}
+
 func TestIsSecureIndex(t *testing.T) {
 	tests := []struct {
 		addr               string

+ 23 - 9
registry/service.go

@@ -31,6 +31,7 @@ type Service interface {
 	Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error)
 	ServiceConfig() *registrytypes.ServiceConfig
 	TLSConfig(hostname string) (*tls.Config, error)
+	LoadAllowNondistributableArtifacts([]string) error
 	LoadMirrors([]string) error
 	LoadInsecureRegistries([]string) error
 }
@@ -56,13 +57,17 @@ func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
 	defer s.mu.Unlock()
 
 	servConfig := registrytypes.ServiceConfig{
-		InsecureRegistryCIDRs: make([]*(registrytypes.NetIPNet), 0),
-		IndexConfigs:          make(map[string]*(registrytypes.IndexInfo)),
-		Mirrors:               make([]string, 0),
+		AllowNondistributableArtifactsCIDRs:     make([]*(registrytypes.NetIPNet), 0),
+		AllowNondistributableArtifactsHostnames: make([]string, 0),
+		InsecureRegistryCIDRs:                   make([]*(registrytypes.NetIPNet), 0),
+		IndexConfigs:                            make(map[string]*(registrytypes.IndexInfo)),
+		Mirrors:                                 make([]string, 0),
 	}
 
 	// construct a new ServiceConfig which will not retrieve s.Config directly,
 	// and look up items in s.config with mu locked
+	servConfig.AllowNondistributableArtifactsCIDRs = append(servConfig.AllowNondistributableArtifactsCIDRs, s.config.ServiceConfig.AllowNondistributableArtifactsCIDRs...)
+	servConfig.AllowNondistributableArtifactsHostnames = append(servConfig.AllowNondistributableArtifactsHostnames, s.config.ServiceConfig.AllowNondistributableArtifactsHostnames...)
 	servConfig.InsecureRegistryCIDRs = append(servConfig.InsecureRegistryCIDRs, s.config.ServiceConfig.InsecureRegistryCIDRs...)
 
 	for key, value := range s.config.ServiceConfig.IndexConfigs {
@@ -74,6 +79,14 @@ func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
 	return &servConfig
 }
 
+// LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries for Service.
+func (s *DefaultService) LoadAllowNondistributableArtifacts(registries []string) error {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	return s.config.LoadAllowNondistributableArtifacts(registries)
+}
+
 // LoadMirrors loads registry mirrors for Service
 func (s *DefaultService) LoadMirrors(mirrors []string) error {
 	s.mu.Lock()
@@ -235,12 +248,13 @@ func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInf
 
 // APIEndpoint represents a remote API endpoint
 type APIEndpoint struct {
-	Mirror       bool
-	URL          *url.URL
-	Version      APIVersion
-	Official     bool
-	TrimHostname bool
-	TLSConfig    *tls.Config
+	Mirror                         bool
+	URL                            *url.URL
+	Version                        APIVersion
+	AllowNondistributableArtifacts bool
+	Official                       bool
+	TrimHostname                   bool
+	TLSConfig                      *tls.Config
 }
 
 // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint

+ 9 - 5
registry/service_v2.go

@@ -44,6 +44,8 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
 		return endpoints, nil
 	}
 
+	ana := allowNondistributableArtifacts(s.config, hostname)
+
 	tlsConfig, err = s.tlsConfig(hostname)
 	if err != nil {
 		return nil, err
@@ -55,9 +57,10 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
 				Scheme: "https",
 				Host:   hostname,
 			},
-			Version:      APIVersion2,
-			TrimHostname: true,
-			TLSConfig:    tlsConfig,
+			Version: APIVersion2,
+			AllowNondistributableArtifacts: ana,
+			TrimHostname:                   true,
+			TLSConfig:                      tlsConfig,
 		},
 	}
 
@@ -67,8 +70,9 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
 				Scheme: "http",
 				Host:   hostname,
 			},
-			Version:      APIVersion2,
-			TrimHostname: true,
+			Version: APIVersion2,
+			AllowNondistributableArtifacts: ana,
+			TrimHostname:                   true,
 			// used to check if supposed to be secure via InsecureSkipVerify
 			TLSConfig: tlsConfig,
 		})