Browse code

Add daemon option to push foreign layers

The --allow-nondistributable-artifacts daemon option specifies
registries to which foreign layers should be pushed. (By default,
foreign layers are not pushed to registries.)

Additionally, to make this option effective, foreign layers are now
pulled from the registry if possible, falling back to the URLs in the
image manifest otherwise.

This option is useful when pushing images containing foreign layers to a
registry on an air-gapped network so hosts on that network can pull the
images without connecting to another server.

Signed-off-by: Noah Treuhaft <noah.treuhaft@docker.com>

Noah Treuhaft authored on 2017/05/10 06:00:31
Showing 16 changed files
... ...
@@ -7,9 +7,11 @@ import (
7 7
 
8 8
 // ServiceConfig stores daemon registry services configuration.
9 9
 type ServiceConfig struct {
10
-	InsecureRegistryCIDRs []*NetIPNet           `json:"InsecureRegistryCIDRs"`
11
-	IndexConfigs          map[string]*IndexInfo `json:"IndexConfigs"`
12
-	Mirrors               []string
10
+	AllowNondistributableArtifactsCIDRs     []*NetIPNet
11
+	AllowNondistributableArtifactsHostnames []string
12
+	InsecureRegistryCIDRs                   []*NetIPNet           `json:"InsecureRegistryCIDRs"`
13
+	IndexConfigs                            map[string]*IndexInfo `json:"IndexConfigs"`
14
+	Mirrors                                 []string
13 15
 }
14 16
 
15 17
 // NetIPNet is the net.IPNet type, which can be marshalled and
... ...
@@ -131,6 +131,7 @@ func TestLoadDaemonConfigWithEmbeddedOptions(t *testing.T) {
131 131
 
132 132
 func TestLoadDaemonConfigWithRegistryOptions(t *testing.T) {
133 133
 	content := `{
134
+		"allow-nondistributable-artifacts": ["allow-nondistributable-artifacts.com"],
134 135
 		"registry-mirrors": ["https://mirrors.docker.com"],
135 136
 		"insecure-registries": ["https://insecure.docker.com"]
136 137
 	}`
... ...
@@ -142,6 +143,7 @@ func TestLoadDaemonConfigWithRegistryOptions(t *testing.T) {
142 142
 	require.NoError(t, err)
143 143
 	require.NotNil(t, loadedConfig)
144 144
 
145
+	assert.Len(t, loadedConfig.AllowNondistributableArtifacts, 1)
145 146
 	assert.Len(t, loadedConfig.Mirrors, 1)
146 147
 	assert.Len(t, loadedConfig.InsecureRegistries, 1)
147 148
 }
... ...
@@ -1962,6 +1962,7 @@ _docker_daemon() {
1962 1962
 	local options_with_args="
1963 1963
 		$global_options_with_args
1964 1964
 		--add-runtime
1965
+		--allow-nondistributable-artifacts
1965 1966
 		--api-cors-header
1966 1967
 		--authorization-plugin
1967 1968
 		--bip
... ...
@@ -2603,6 +2603,7 @@ __docker_subcommand() {
2603 2603
             _arguments $(__docker_arguments) \
2604 2604
                 $opts_help \
2605 2605
                 "($help)*--add-runtime=[Register an additional OCI compatible runtime]:runtime:__docker_complete_runtimes" \
2606
+                "($help)*--allow-nondistributable-artifacts=[Push nondistributable artifacts to specified registries]:registry: " \
2606 2607
                 "($help)--api-cors-header=[CORS headers in the Engine API]:CORS headers: " \
2607 2608
                 "($help)*--authorization-plugin=[Authorization plugins to load]" \
2608 2609
                 "($help -b --bridge)"{-b=,--bridge=}"[Attach containers to a network bridge]:bridge:_net_interfaces" \
... ...
@@ -48,6 +48,9 @@ func (daemon *Daemon) Reload(conf *config.Config) (err error) {
48 48
 	if err := daemon.reloadLabels(conf, attributes); err != nil {
49 49
 		return err
50 50
 	}
51
+	if err := daemon.reloadAllowNondistributableArtifacts(conf, attributes); err != nil {
52
+		return err
53
+	}
51 54
 	if err := daemon.reloadInsecureRegistries(conf, attributes); err != nil {
52 55
 		return err
53 56
 	}
... ...
@@ -217,6 +220,31 @@ func (daemon *Daemon) reloadLabels(conf *config.Config, attributes map[string]st
217 217
 	return nil
218 218
 }
219 219
 
220
+// reloadAllowNondistributableArtifacts updates the configuration with allow-nondistributable-artifacts options
221
+// and updates the passed attributes.
222
+func (daemon *Daemon) reloadAllowNondistributableArtifacts(conf *config.Config, attributes map[string]string) error {
223
+	// Update corresponding configuration.
224
+	if conf.IsValueSet("allow-nondistributable-artifacts") {
225
+		daemon.configStore.AllowNondistributableArtifacts = conf.AllowNondistributableArtifacts
226
+		if err := daemon.RegistryService.LoadAllowNondistributableArtifacts(conf.AllowNondistributableArtifacts); err != nil {
227
+			return err
228
+		}
229
+	}
230
+
231
+	// Prepare reload event attributes with updatable configurations.
232
+	if daemon.configStore.AllowNondistributableArtifacts != nil {
233
+		v, err := json.Marshal(daemon.configStore.AllowNondistributableArtifacts)
234
+		if err != nil {
235
+			return err
236
+		}
237
+		attributes["allow-nondistributable-artifacts"] = string(v)
238
+	} else {
239
+		attributes["allow-nondistributable-artifacts"] = "[]"
240
+	}
241
+
242
+	return nil
243
+}
244
+
220 245
 // reloadInsecureRegistries updates configuration with insecure registry option
221 246
 // and updates the passed attributes
222 247
 func (daemon *Daemon) reloadInsecureRegistries(conf *config.Config, attributes map[string]string) error {
... ...
@@ -4,6 +4,7 @@ package daemon
4 4
 
5 5
 import (
6 6
 	"reflect"
7
+	"sort"
7 8
 	"testing"
8 9
 	"time"
9 10
 
... ...
@@ -40,6 +41,61 @@ func TestDaemonReloadLabels(t *testing.T) {
40 40
 	}
41 41
 }
42 42
 
43
+func TestDaemonReloadAllowNondistributableArtifacts(t *testing.T) {
44
+	daemon := &Daemon{
45
+		configStore: &config.Config{},
46
+	}
47
+
48
+	// Initialize daemon with some registries.
49
+	daemon.RegistryService = registry.NewService(registry.ServiceOptions{
50
+		AllowNondistributableArtifacts: []string{
51
+			"127.0.0.0/8",
52
+			"10.10.1.11:5000",
53
+			"10.10.1.22:5000", // This will be removed during reload.
54
+			"docker1.com",
55
+			"docker2.com", // This will be removed during reload.
56
+		},
57
+	})
58
+
59
+	registries := []string{
60
+		"127.0.0.0/8",
61
+		"10.10.1.11:5000",
62
+		"10.10.1.33:5000", // This will be added during reload.
63
+		"docker1.com",
64
+		"docker3.com", // This will be added during reload.
65
+	}
66
+
67
+	newConfig := &config.Config{
68
+		CommonConfig: config.CommonConfig{
69
+			ServiceOptions: registry.ServiceOptions{
70
+				AllowNondistributableArtifacts: registries,
71
+			},
72
+			ValuesSet: map[string]interface{}{
73
+				"allow-nondistributable-artifacts": registries,
74
+			},
75
+		},
76
+	}
77
+
78
+	if err := daemon.Reload(newConfig); err != nil {
79
+		t.Fatal(err)
80
+	}
81
+
82
+	actual := []string{}
83
+	serviceConfig := daemon.RegistryService.ServiceConfig()
84
+	for _, value := range serviceConfig.AllowNondistributableArtifactsCIDRs {
85
+		actual = append(actual, value.String())
86
+	}
87
+	for _, value := range serviceConfig.AllowNondistributableArtifactsHostnames {
88
+		actual = append(actual, value)
89
+	}
90
+
91
+	sort.Strings(registries)
92
+	sort.Strings(actual)
93
+	if !reflect.DeepEqual(registries, actual) {
94
+		t.Fatalf("expected %v, got %v\n", registries, actual)
95
+	}
96
+}
97
+
43 98
 func TestDaemonReloadMirrors(t *testing.T) {
44 99
 	daemon := &Daemon{}
45 100
 	daemon.RegistryService = registry.NewService(registry.ServiceOptions{
... ...
@@ -23,20 +23,28 @@ func (ld *v2LayerDescriptor) Descriptor() distribution.Descriptor {
23 23
 }
24 24
 
25 25
 func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekCloser, error) {
26
+	blobs := ld.repo.Blobs(ctx)
27
+	rsc, err := blobs.Open(ctx, ld.digest)
28
+
26 29
 	if len(ld.src.URLs) == 0 {
27
-		blobs := ld.repo.Blobs(ctx)
28
-		return blobs.Open(ctx, ld.digest)
30
+		return rsc, err
29 31
 	}
30 32
 
31
-	var (
32
-		err error
33
-		rsc distribution.ReadSeekCloser
34
-	)
33
+	// We're done if the registry has this blob.
34
+	if err == nil {
35
+		// Seek does an HTTP GET.  If it succeeds, the blob really is accessible.
36
+		if _, err = rsc.Seek(0, os.SEEK_SET); err == nil {
37
+			return rsc, nil
38
+		}
39
+		rsc.Close()
40
+	}
35 41
 
36 42
 	// Find the first URL that results in a 200 result code.
37 43
 	for _, url := range ld.src.URLs {
38 44
 		logrus.Debugf("Pulling %v from foreign URL %v", ld.digest, url)
39 45
 		rsc = transport.NewHTTPReadSeeker(http.DefaultClient, url, nil)
46
+
47
+		// Seek does an HTTP GET.  If it succeeds, the blob really is accessible.
40 48
 		_, err = rsc.Seek(0, os.SEEK_SET)
41 49
 		if err == nil {
42 50
 			break
... ...
@@ -141,6 +141,7 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, id
141 141
 		hmacKey:           hmacKey,
142 142
 		repoInfo:          p.repoInfo.Name,
143 143
 		ref:               p.ref,
144
+		endpoint:          p.endpoint,
144 145
 		repo:              p.repo,
145 146
 		pushState:         &p.pushState,
146 147
 	}
... ...
@@ -239,6 +240,7 @@ type v2PushDescriptor struct {
239 239
 	hmacKey           []byte
240 240
 	repoInfo          reference.Named
241 241
 	ref               reference.Named
242
+	endpoint          registry.APIEndpoint
242 243
 	repo              distribution.Repository
243 244
 	pushState         *pushState
244 245
 	remoteDescriptor  distribution.Descriptor
... ...
@@ -259,10 +261,13 @@ func (pd *v2PushDescriptor) DiffID() layer.DiffID {
259 259
 }
260 260
 
261 261
 func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.Output) (distribution.Descriptor, error) {
262
-	if fs, ok := pd.layer.(distribution.Describable); ok {
263
-		if d := fs.Descriptor(); len(d.URLs) > 0 {
264
-			progress.Update(progressOutput, pd.ID(), "Skipped foreign layer")
265
-			return d, nil
262
+	// Skip foreign layers unless this registry allows nondistributable artifacts.
263
+	if !pd.endpoint.AllowNondistributableArtifacts {
264
+		if fs, ok := pd.layer.(distribution.Describable); ok {
265
+			if d := fs.Descriptor(); len(d.URLs) > 0 {
266
+				progress.Update(progressOutput, pd.ID(), "Skipped foreign layer")
267
+				return d, nil
268
+			}
266 269
 		}
267 270
 	}
268 271
 
... ...
@@ -23,6 +23,7 @@ A self-sufficient runtime for containers.
23 23
 
24 24
 Options:
25 25
       --add-runtime runtime                   Register an additional OCI compatible runtime (default [])
26
+      --allow-nondistributable-artifacts list Push nondistributable artifacts to specified registries (default [])
26 27
       --api-cors-header string                Set CORS headers in the Engine API
27 28
       --authorization-plugin list             Authorization plugins to load (default [])
28 29
       --bip string                            Specify network bridge IP
... ...
@@ -828,6 +829,32 @@ To set the DNS search domain for all Docker containers, use:
828 828
 $ sudo dockerd --dns-search example.com
829 829
 ```
830 830
 
831
+#### Allow push of nondistributable artifacts
832
+
833
+Some images (e.g., Windows base images) contain artifacts whose distribution is
834
+restricted by license. When these images are pushed to a registry, restricted
835
+artifacts are not included.
836
+
837
+To override this behavior for specific registries, use the
838
+`--allow-nondistributable-artifacts` option in one of the following forms:
839
+
840
+* `--allow-nondistributable-artifacts myregistry:5000` tells the Docker daemon
841
+  to push nondistributable artifacts to myregistry:5000.
842
+* `--allow-nondistributable-artifacts 10.1.0.0/16` tells the Docker daemon to
843
+  push nondistributable artifacts to all registries whose resolved IP address
844
+  is within the subnet described by the CIDR syntax.
845
+
846
+This option can be used multiple times.
847
+
848
+This option is useful when pushing images containing nondistributable artifacts
849
+to a registry on an air-gapped network so hosts on that network can pull the
850
+images without connecting to another server.
851
+
852
+> **Warning**: Nondistributable artifacts typically have restrictions on how
853
+> and where they can be distributed and shared. Only use this feature to push
854
+> artifacts to private registries and ensure that you are in compliance with
855
+> any terms that cover redistributing nondistributable artifacts.
856
+
831 857
 #### Insecure registries
832 858
 
833 859
 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:
1261 1261
 	"default-gateway-v6": "",
1262 1262
 	"icc": false,
1263 1263
 	"raw-logs": false,
1264
+	"allow-nondistributable-artifacts": [],
1264 1265
 	"registry-mirrors": [],
1265 1266
 	"seccomp-profile": "",
1266 1267
 	"insecure-registries": [],
... ...
@@ -1330,6 +1358,7 @@ This is a full example of the allowed configuration options on Windows:
1330 1330
     "bridge": "",
1331 1331
     "fixed-cidr": "",
1332 1332
     "raw-logs": false,
1333
+    "allow-nondistributable-artifacts": [],
1333 1334
     "registry-mirrors": [],
1334 1335
     "insecure-registries": [],
1335 1336
     "disable-legacy-registry": false
... ...
@@ -1361,6 +1390,7 @@ The list of currently supported options that can be reconfigured is this:
1361 1361
 - `runtimes`: it updates the list of available OCI runtimes that can
1362 1362
   be used to run containers
1363 1363
 - `authorization-plugin`: specifies the authorization plugins to use.
1364
+- `allow-nondistributable-artifacts`: Replaces the set of registries to which the daemon will push nondistributable artifacts with a new set of registries.
1364 1365
 - `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.
1365 1366
 - `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.
1366 1367
 
... ...
@@ -428,7 +428,7 @@ func (s *DockerDaemonSuite) TestDaemonEvents(c *check.C) {
428 428
 	out, err = s.d.Cmd("events", "--since=0", "--until", daemonUnixTime(c))
429 429
 	c.Assert(err, checker.IsNil)
430 430
 
431
-	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))
431
+	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))
432 432
 }
433 433
 
434 434
 func (s *DockerDaemonSuite) TestDaemonEventsWithFilters(c *check.C) {
... ...
@@ -7,6 +7,7 @@ dockerd - Enable daemon mode
7 7
 # SYNOPSIS
8 8
 **dockerd**
9 9
 [**--add-runtime**[=*[]*]]
10
+[**--allow-nondistributable-artifacts**[=*[]*]]
10 11
 [**--api-cors-header**=[=*API-CORS-HEADER*]]
11 12
 [**--authorization-plugin**[=*[]*]]
12 13
 [**-b**|**--bridge**[=*BRIDGE*]]
... ...
@@ -116,6 +117,20 @@ $ sudo dockerd --add-runtime runc=runc --add-runtime custom=/usr/local/bin/my-ru
116 116
 
117 117
   **Note**: defining runtime arguments via the command line is not supported.
118 118
 
119
+**--allow-nondistributable-artifacts**=[]
120
+  Push nondistributable artifacts to the specified registries.
121
+
122
+  List can contain elements with CIDR notation to specify a whole subnet.
123
+
124
+  This option is useful when pushing images containing nondistributable
125
+  artifacts to a registry on an air-gapped network so hosts on that network can
126
+  pull the images without connecting to another server.
127
+
128
+  **Warning**: Nondistributable artifacts typically have restrictions on how
129
+  and where they can be distributed and shared. Only use this feature to push
130
+  artifacts to private registries and ensure that you are in compliance with
131
+  any terms that cover redistributing nondistributable artifacts.
132
+
119 133
 **--api-cors-header**=""
120 134
   Set CORS headers in the Engine API. Default is cors disabled. Give urls like
121 135
   "http://foo, http://bar, ...". Give "*" to allow all.
... ...
@@ -18,8 +18,9 @@ import (
18 18
 
19 19
 // ServiceOptions holds command line options.
20 20
 type ServiceOptions struct {
21
-	Mirrors            []string `json:"registry-mirrors,omitempty"`
22
-	InsecureRegistries []string `json:"insecure-registries,omitempty"`
21
+	AllowNondistributableArtifacts []string `json:"allow-nondistributable-artifacts,omitempty"`
22
+	Mirrors                        []string `json:"registry-mirrors,omitempty"`
23
+	InsecureRegistries             []string `json:"insecure-registries,omitempty"`
23 24
 
24 25
 	// V2Only controls access to legacy registries.  If it is set to true via the
25 26
 	// command line flag the daemon will not attempt to contact v1 legacy registries
... ...
@@ -74,9 +75,11 @@ var lookupIP = net.LookupIP
74 74
 // InstallCliFlags adds command-line options to the top-level flag parser for
75 75
 // the current process.
76 76
 func (options *ServiceOptions) InstallCliFlags(flags *pflag.FlagSet) {
77
+	ana := opts.NewNamedListOptsRef("allow-nondistributable-artifacts", &options.AllowNondistributableArtifacts, ValidateIndexName)
77 78
 	mirrors := opts.NewNamedListOptsRef("registry-mirrors", &options.Mirrors, ValidateMirror)
78 79
 	insecureRegistries := opts.NewNamedListOptsRef("insecure-registries", &options.InsecureRegistries, ValidateIndexName)
79 80
 
81
+	flags.Var(ana, "allow-nondistributable-artifacts", "Allow push of nondistributable artifacts to registry")
80 82
 	flags.Var(mirrors, "registry-mirror", "Preferred Docker registry mirror")
81 83
 	flags.Var(insecureRegistries, "insecure-registry", "Enable insecure registry communication")
82 84
 
... ...
@@ -95,12 +98,50 @@ func newServiceConfig(options ServiceOptions) *serviceConfig {
95 95
 		V2Only: options.V2Only,
96 96
 	}
97 97
 
98
+	config.LoadAllowNondistributableArtifacts(options.AllowNondistributableArtifacts)
98 99
 	config.LoadMirrors(options.Mirrors)
99 100
 	config.LoadInsecureRegistries(options.InsecureRegistries)
100 101
 
101 102
 	return config
102 103
 }
103 104
 
105
+// LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries into config.
106
+func (config *serviceConfig) LoadAllowNondistributableArtifacts(registries []string) error {
107
+	cidrs := map[string]*registrytypes.NetIPNet{}
108
+	hostnames := map[string]bool{}
109
+
110
+	for _, r := range registries {
111
+		if _, err := ValidateIndexName(r); err != nil {
112
+			return err
113
+		}
114
+		if validateNoScheme(r) != nil {
115
+			return fmt.Errorf("allow-nondistributable-artifacts registry %s should not contain '://'", r)
116
+		}
117
+
118
+		if _, ipnet, err := net.ParseCIDR(r); err == nil {
119
+			// Valid CIDR.
120
+			cidrs[ipnet.String()] = (*registrytypes.NetIPNet)(ipnet)
121
+		} else if err := validateHostPort(r); err == nil {
122
+			// Must be `host:port` if not CIDR.
123
+			hostnames[r] = true
124
+		} else {
125
+			return fmt.Errorf("allow-nondistributable-artifacts registry %s is not valid: %v", r, err)
126
+		}
127
+	}
128
+
129
+	config.AllowNondistributableArtifactsCIDRs = make([]*(registrytypes.NetIPNet), 0)
130
+	for _, c := range cidrs {
131
+		config.AllowNondistributableArtifactsCIDRs = append(config.AllowNondistributableArtifactsCIDRs, c)
132
+	}
133
+
134
+	config.AllowNondistributableArtifactsHostnames = make([]string, 0)
135
+	for h := range hostnames {
136
+		config.AllowNondistributableArtifactsHostnames = append(config.AllowNondistributableArtifactsHostnames, h)
137
+	}
138
+
139
+	return nil
140
+}
141
+
104 142
 // LoadMirrors loads mirrors to config, after removing duplicates.
105 143
 // Returns an error if mirrors contains an invalid mirror.
106 144
 func (config *serviceConfig) LoadMirrors(mirrors []string) error {
... ...
@@ -211,6 +252,25 @@ skip:
211 211
 	return nil
212 212
 }
213 213
 
214
+// allowNondistributableArtifacts returns true if the provided hostname is part of the list of regsitries
215
+// that allow push of nondistributable artifacts.
216
+//
217
+// The list can contain elements with CIDR notation to specify a whole subnet. If the subnet contains an IP
218
+// of the registry specified by hostname, true is returned.
219
+//
220
+// hostname should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
221
+// or an IP address. If it is a domain name, then it will be resolved to IP addresses for matching. If
222
+// resolution fails, CIDR matching is not performed.
223
+func allowNondistributableArtifacts(config *serviceConfig, hostname string) bool {
224
+	for _, h := range config.AllowNondistributableArtifactsHostnames {
225
+		if h == hostname {
226
+			return true
227
+		}
228
+	}
229
+
230
+	return isCIDRMatch(config.AllowNondistributableArtifactsCIDRs, hostname)
231
+}
232
+
214 233
 // isSecureIndex returns false if the provided indexName is part of the list of insecure registries
215 234
 // Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
216 235
 //
... ...
@@ -229,10 +289,17 @@ func isSecureIndex(config *serviceConfig, indexName string) bool {
229 229
 		return index.Secure
230 230
 	}
231 231
 
232
-	host, _, err := net.SplitHostPort(indexName)
232
+	return !isCIDRMatch(config.InsecureRegistryCIDRs, indexName)
233
+}
234
+
235
+// isCIDRMatch returns true if URLHost matches an element of cidrs. URLHost is a URL.Host (`host:port` or `host`)
236
+// where the `host` part can be either a domain name or an IP address. If it is a domain name, then it will be
237
+// resolved to IP addresses for matching. If resolution fails, false is returned.
238
+func isCIDRMatch(cidrs []*registrytypes.NetIPNet, URLHost string) bool {
239
+	host, _, err := net.SplitHostPort(URLHost)
233 240
 	if err != nil {
234
-		// assume indexName is of the form `host` without the port and go on.
235
-		host = indexName
241
+		// Assume URLHost is of the form `host` without the port and go on.
242
+		host = URLHost
236 243
 	}
237 244
 
238 245
 	addrs, err := lookupIP(host)
... ...
@@ -249,15 +316,15 @@ func isSecureIndex(config *serviceConfig, indexName string) bool {
249 249
 
250 250
 	// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
251 251
 	for _, addr := range addrs {
252
-		for _, ipnet := range config.InsecureRegistryCIDRs {
252
+		for _, ipnet := range cidrs {
253 253
 			// check if the addr falls in the subnet
254 254
 			if (*net.IPNet)(ipnet).Contains(addr) {
255
-				return false
255
+				return true
256 256
 			}
257 257
 		}
258 258
 	}
259 259
 
260
-	return true
260
+	return false
261 261
 }
262 262
 
263 263
 // ValidateMirror validates an HTTP(S) registry mirror
... ...
@@ -1,10 +1,129 @@
1 1
 package registry
2 2
 
3 3
 import (
4
+	"reflect"
5
+	"sort"
4 6
 	"strings"
5 7
 	"testing"
6 8
 )
7 9
 
10
+func TestLoadAllowNondistributableArtifacts(t *testing.T) {
11
+	testCases := []struct {
12
+		registries []string
13
+		cidrStrs   []string
14
+		hostnames  []string
15
+		err        string
16
+	}{
17
+		{
18
+			registries: []string{"1.2.3.0/24"},
19
+			cidrStrs:   []string{"1.2.3.0/24"},
20
+		},
21
+		{
22
+			registries: []string{"2001:db8::/120"},
23
+			cidrStrs:   []string{"2001:db8::/120"},
24
+		},
25
+		{
26
+			registries: []string{"127.0.0.1"},
27
+			hostnames:  []string{"127.0.0.1"},
28
+		},
29
+		{
30
+			registries: []string{"127.0.0.1:8080"},
31
+			hostnames:  []string{"127.0.0.1:8080"},
32
+		},
33
+		{
34
+			registries: []string{"2001:db8::1"},
35
+			hostnames:  []string{"2001:db8::1"},
36
+		},
37
+		{
38
+			registries: []string{"[2001:db8::1]:80"},
39
+			hostnames:  []string{"[2001:db8::1]:80"},
40
+		},
41
+		{
42
+			registries: []string{"[2001:db8::1]:80"},
43
+			hostnames:  []string{"[2001:db8::1]:80"},
44
+		},
45
+		{
46
+			registries: []string{"1.2.3.0/24", "2001:db8::/120", "127.0.0.1", "127.0.0.1:8080"},
47
+			cidrStrs:   []string{"1.2.3.0/24", "2001:db8::/120"},
48
+			hostnames:  []string{"127.0.0.1", "127.0.0.1:8080"},
49
+		},
50
+
51
+		{
52
+			registries: []string{"http://mytest.com"},
53
+			err:        "allow-nondistributable-artifacts registry http://mytest.com should not contain '://'",
54
+		},
55
+		{
56
+			registries: []string{"https://mytest.com"},
57
+			err:        "allow-nondistributable-artifacts registry https://mytest.com should not contain '://'",
58
+		},
59
+		{
60
+			registries: []string{"HTTP://mytest.com"},
61
+			err:        "allow-nondistributable-artifacts registry HTTP://mytest.com should not contain '://'",
62
+		},
63
+		{
64
+			registries: []string{"svn://mytest.com"},
65
+			err:        "allow-nondistributable-artifacts registry svn://mytest.com should not contain '://'",
66
+		},
67
+		{
68
+			registries: []string{"-invalid-registry"},
69
+			err:        "Cannot begin or end with a hyphen",
70
+		},
71
+		{
72
+			registries: []string{`mytest-.com`},
73
+			err:        `allow-nondistributable-artifacts registry mytest-.com is not valid: invalid host "mytest-.com"`,
74
+		},
75
+		{
76
+			registries: []string{`1200:0000:AB00:1234:0000:2552:7777:1313:8080`},
77
+			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"`,
78
+		},
79
+		{
80
+			registries: []string{`mytest.com:500000`},
81
+			err:        `allow-nondistributable-artifacts registry mytest.com:500000 is not valid: invalid port "500000"`,
82
+		},
83
+		{
84
+			registries: []string{`"mytest.com"`},
85
+			err:        `allow-nondistributable-artifacts registry "mytest.com" is not valid: invalid host "\"mytest.com\""`,
86
+		},
87
+		{
88
+			registries: []string{`"mytest.com:5000"`},
89
+			err:        `allow-nondistributable-artifacts registry "mytest.com:5000" is not valid: invalid host "\"mytest.com"`,
90
+		},
91
+	}
92
+	for _, testCase := range testCases {
93
+		config := newServiceConfig(ServiceOptions{})
94
+		err := config.LoadAllowNondistributableArtifacts(testCase.registries)
95
+		if testCase.err == "" {
96
+			if err != nil {
97
+				t.Fatalf("expect no error, got '%s'", err)
98
+			}
99
+
100
+			cidrStrs := []string{}
101
+			for _, c := range config.AllowNondistributableArtifactsCIDRs {
102
+				cidrStrs = append(cidrStrs, c.String())
103
+			}
104
+
105
+			sort.Strings(testCase.cidrStrs)
106
+			sort.Strings(cidrStrs)
107
+			if (len(testCase.cidrStrs) > 0 || len(cidrStrs) > 0) && !reflect.DeepEqual(testCase.cidrStrs, cidrStrs) {
108
+				t.Fatalf("expect AllowNondistributableArtifactsCIDRs to be '%+v', got '%+v'", testCase.cidrStrs, cidrStrs)
109
+			}
110
+
111
+			sort.Strings(testCase.hostnames)
112
+			sort.Strings(config.AllowNondistributableArtifactsHostnames)
113
+			if (len(testCase.hostnames) > 0 || len(config.AllowNondistributableArtifactsHostnames) > 0) && !reflect.DeepEqual(testCase.hostnames, config.AllowNondistributableArtifactsHostnames) {
114
+				t.Fatalf("expect AllowNondistributableArtifactsHostnames to be '%+v', got '%+v'", testCase.hostnames, config.AllowNondistributableArtifactsHostnames)
115
+			}
116
+		} else {
117
+			if err == nil {
118
+				t.Fatalf("expect error '%s', got no error", testCase.err)
119
+			}
120
+			if !strings.Contains(err.Error(), testCase.err) {
121
+				t.Fatalf("expect error '%s', got '%s'", testCase.err, err)
122
+			}
123
+		}
124
+	}
125
+}
126
+
8 127
 func TestValidateMirror(t *testing.T) {
9 128
 	valid := []string{
10 129
 		"http://mirror-1.com",
... ...
@@ -811,6 +811,48 @@ func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
811 811
 	}
812 812
 }
813 813
 
814
+func TestAllowNondistributableArtifacts(t *testing.T) {
815
+	tests := []struct {
816
+		addr       string
817
+		registries []string
818
+		expected   bool
819
+	}{
820
+		{IndexName, nil, false},
821
+		{"example.com", []string{}, false},
822
+		{"example.com", []string{"example.com"}, true},
823
+		{"localhost", []string{"localhost:5000"}, false},
824
+		{"localhost:5000", []string{"localhost:5000"}, true},
825
+		{"localhost", []string{"example.com"}, false},
826
+		{"127.0.0.1:5000", []string{"127.0.0.1:5000"}, true},
827
+		{"localhost", nil, false},
828
+		{"localhost:5000", nil, false},
829
+		{"127.0.0.1", nil, false},
830
+		{"localhost", []string{"example.com"}, false},
831
+		{"127.0.0.1", []string{"example.com"}, false},
832
+		{"example.com", nil, false},
833
+		{"example.com", []string{"example.com"}, true},
834
+		{"127.0.0.1", []string{"example.com"}, false},
835
+		{"127.0.0.1:5000", []string{"example.com"}, false},
836
+		{"example.com:5000", []string{"42.42.0.0/16"}, true},
837
+		{"example.com", []string{"42.42.0.0/16"}, true},
838
+		{"example.com:5000", []string{"42.42.42.42/8"}, true},
839
+		{"127.0.0.1:5000", []string{"127.0.0.0/8"}, true},
840
+		{"42.42.42.42:5000", []string{"42.1.1.1/8"}, true},
841
+		{"invalid.domain.com", []string{"42.42.0.0/16"}, false},
842
+		{"invalid.domain.com", []string{"invalid.domain.com"}, true},
843
+		{"invalid.domain.com:5000", []string{"invalid.domain.com"}, false},
844
+		{"invalid.domain.com:5000", []string{"invalid.domain.com:5000"}, true},
845
+	}
846
+	for _, tt := range tests {
847
+		config := newServiceConfig(ServiceOptions{
848
+			AllowNondistributableArtifacts: tt.registries,
849
+		})
850
+		if v := allowNondistributableArtifacts(config, tt.addr); v != tt.expected {
851
+			t.Errorf("allowNondistributableArtifacts failed for %q %v, expected %v got %v", tt.addr, tt.registries, tt.expected, v)
852
+		}
853
+	}
854
+}
855
+
814 856
 func TestIsSecureIndex(t *testing.T) {
815 857
 	tests := []struct {
816 858
 		addr               string
... ...
@@ -31,6 +31,7 @@ type Service interface {
31 31
 	Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error)
32 32
 	ServiceConfig() *registrytypes.ServiceConfig
33 33
 	TLSConfig(hostname string) (*tls.Config, error)
34
+	LoadAllowNondistributableArtifacts([]string) error
34 35
 	LoadMirrors([]string) error
35 36
 	LoadInsecureRegistries([]string) error
36 37
 }
... ...
@@ -56,13 +57,17 @@ func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
56 56
 	defer s.mu.Unlock()
57 57
 
58 58
 	servConfig := registrytypes.ServiceConfig{
59
-		InsecureRegistryCIDRs: make([]*(registrytypes.NetIPNet), 0),
60
-		IndexConfigs:          make(map[string]*(registrytypes.IndexInfo)),
61
-		Mirrors:               make([]string, 0),
59
+		AllowNondistributableArtifactsCIDRs:     make([]*(registrytypes.NetIPNet), 0),
60
+		AllowNondistributableArtifactsHostnames: make([]string, 0),
61
+		InsecureRegistryCIDRs:                   make([]*(registrytypes.NetIPNet), 0),
62
+		IndexConfigs:                            make(map[string]*(registrytypes.IndexInfo)),
63
+		Mirrors:                                 make([]string, 0),
62 64
 	}
63 65
 
64 66
 	// construct a new ServiceConfig which will not retrieve s.Config directly,
65 67
 	// and look up items in s.config with mu locked
68
+	servConfig.AllowNondistributableArtifactsCIDRs = append(servConfig.AllowNondistributableArtifactsCIDRs, s.config.ServiceConfig.AllowNondistributableArtifactsCIDRs...)
69
+	servConfig.AllowNondistributableArtifactsHostnames = append(servConfig.AllowNondistributableArtifactsHostnames, s.config.ServiceConfig.AllowNondistributableArtifactsHostnames...)
66 70
 	servConfig.InsecureRegistryCIDRs = append(servConfig.InsecureRegistryCIDRs, s.config.ServiceConfig.InsecureRegistryCIDRs...)
67 71
 
68 72
 	for key, value := range s.config.ServiceConfig.IndexConfigs {
... ...
@@ -74,6 +79,14 @@ func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
74 74
 	return &servConfig
75 75
 }
76 76
 
77
+// LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries for Service.
78
+func (s *DefaultService) LoadAllowNondistributableArtifacts(registries []string) error {
79
+	s.mu.Lock()
80
+	defer s.mu.Unlock()
81
+
82
+	return s.config.LoadAllowNondistributableArtifacts(registries)
83
+}
84
+
77 85
 // LoadMirrors loads registry mirrors for Service
78 86
 func (s *DefaultService) LoadMirrors(mirrors []string) error {
79 87
 	s.mu.Lock()
... ...
@@ -235,12 +248,13 @@ func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInf
235 235
 
236 236
 // APIEndpoint represents a remote API endpoint
237 237
 type APIEndpoint struct {
238
-	Mirror       bool
239
-	URL          *url.URL
240
-	Version      APIVersion
241
-	Official     bool
242
-	TrimHostname bool
243
-	TLSConfig    *tls.Config
238
+	Mirror                         bool
239
+	URL                            *url.URL
240
+	Version                        APIVersion
241
+	AllowNondistributableArtifacts bool
242
+	Official                       bool
243
+	TrimHostname                   bool
244
+	TLSConfig                      *tls.Config
244 245
 }
245 246
 
246 247
 // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
... ...
@@ -44,6 +44,8 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
44 44
 		return endpoints, nil
45 45
 	}
46 46
 
47
+	ana := allowNondistributableArtifacts(s.config, hostname)
48
+
47 49
 	tlsConfig, err = s.tlsConfig(hostname)
48 50
 	if err != nil {
49 51
 		return nil, err
... ...
@@ -55,9 +57,10 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
55 55
 				Scheme: "https",
56 56
 				Host:   hostname,
57 57
 			},
58
-			Version:      APIVersion2,
59
-			TrimHostname: true,
60
-			TLSConfig:    tlsConfig,
58
+			Version: APIVersion2,
59
+			AllowNondistributableArtifacts: ana,
60
+			TrimHostname:                   true,
61
+			TLSConfig:                      tlsConfig,
61 62
 		},
62 63
 	}
63 64
 
... ...
@@ -67,8 +70,9 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
67 67
 				Scheme: "http",
68 68
 				Host:   hostname,
69 69
 			},
70
-			Version:      APIVersion2,
71
-			TrimHostname: true,
70
+			Version: APIVersion2,
71
+			AllowNondistributableArtifacts: ana,
72
+			TrimHostname:                   true,
72 73
 			// used to check if supposed to be secure via InsecureSkipVerify
73 74
 			TLSConfig: tlsConfig,
74 75
 		})