1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

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>
This commit is contained in:
Noah Treuhaft 2017-05-09 14:00:31 -07:00
parent e8c2a33b74
commit 67fdf574d5
16 changed files with 430 additions and 36 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -48,6 +48,9 @@ func (daemon *Daemon) Reload(conf *config.Config) (err error) {
if err := daemon.reloadLabels(conf, attributes); err != nil { if err := daemon.reloadLabels(conf, attributes); err != nil {
return err return err
} }
if err := daemon.reloadAllowNondistributableArtifacts(conf, attributes); err != nil {
return err
}
if err := daemon.reloadInsecureRegistries(conf, attributes); err != nil { if err := daemon.reloadInsecureRegistries(conf, attributes); err != nil {
return err return err
} }
@ -217,6 +220,31 @@ func (daemon *Daemon) reloadLabels(conf *config.Config, attributes map[string]st
return nil 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 // reloadInsecureRegistries updates configuration with insecure registry option
// and updates the passed attributes // and updates the passed attributes
func (daemon *Daemon) reloadInsecureRegistries(conf *config.Config, attributes map[string]string) error { func (daemon *Daemon) reloadInsecureRegistries(conf *config.Config, attributes map[string]string) error {

View file

@ -4,6 +4,7 @@ package daemon
import ( import (
"reflect" "reflect"
"sort"
"testing" "testing"
"time" "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) { func TestDaemonReloadMirrors(t *testing.T) {
daemon := &Daemon{} daemon := &Daemon{}
daemon.RegistryService = registry.NewService(registry.ServiceOptions{ daemon.RegistryService = registry.NewService(registry.ServiceOptions{

View file

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

View file

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

View file

@ -23,6 +23,7 @@ A self-sufficient runtime for containers.
Options: Options:
--add-runtime runtime Register an additional OCI compatible runtime (default []) --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 --api-cors-header string Set CORS headers in the Engine API
--authorization-plugin list Authorization plugins to load (default []) --authorization-plugin list Authorization plugins to load (default [])
--bip string Specify network bridge IP --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 $ 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 #### Insecure registries
Docker considers a private registry either secure or insecure. In the rest of 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": "", "default-gateway-v6": "",
"icc": false, "icc": false,
"raw-logs": false, "raw-logs": false,
"allow-nondistributable-artifacts": [],
"registry-mirrors": [], "registry-mirrors": [],
"seccomp-profile": "", "seccomp-profile": "",
"insecure-registries": [], "insecure-registries": [],
@ -1330,6 +1358,7 @@ This is a full example of the allowed configuration options on Windows:
"bridge": "", "bridge": "",
"fixed-cidr": "", "fixed-cidr": "",
"raw-logs": false, "raw-logs": false,
"allow-nondistributable-artifacts": [],
"registry-mirrors": [], "registry-mirrors": [],
"insecure-registries": [], "insecure-registries": [],
"disable-legacy-registry": false "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 - `runtimes`: it updates the list of available OCI runtimes that can
be used to run containers be used to run containers
- `authorization-plugin`: specifies the authorization plugins to use. - `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. - `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. - `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.

View file

@ -428,7 +428,7 @@ func (s *DockerDaemonSuite) TestDaemonEvents(c *check.C) {
out, err = s.d.Cmd("events", "--since=0", "--until", daemonUnixTime(c)) out, err = s.d.Cmd("events", "--since=0", "--until", daemonUnixTime(c))
c.Assert(err, checker.IsNil) 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) { func (s *DockerDaemonSuite) TestDaemonEventsWithFilters(c *check.C) {

View file

@ -7,6 +7,7 @@ dockerd - Enable daemon mode
# SYNOPSIS # SYNOPSIS
**dockerd** **dockerd**
[**--add-runtime**[=*[]*]] [**--add-runtime**[=*[]*]]
[**--allow-nondistributable-artifacts**[=*[]*]]
[**--api-cors-header**=[=*API-CORS-HEADER*]] [**--api-cors-header**=[=*API-CORS-HEADER*]]
[**--authorization-plugin**[=*[]*]] [**--authorization-plugin**[=*[]*]]
[**-b**|**--bridge**[=*BRIDGE*]] [**-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. **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**="" **--api-cors-header**=""
Set CORS headers in the Engine API. Default is cors disabled. Give urls like Set CORS headers in the Engine API. Default is cors disabled. Give urls like
"http://foo, http://bar, ...". Give "*" to allow all. "http://foo, http://bar, ...". Give "*" to allow all.

View file

@ -18,8 +18,9 @@ import (
// ServiceOptions holds command line options. // ServiceOptions holds command line options.
type ServiceOptions struct { type ServiceOptions struct {
Mirrors []string `json:"registry-mirrors,omitempty"` AllowNondistributableArtifacts []string `json:"allow-nondistributable-artifacts,omitempty"`
InsecureRegistries []string `json:"insecure-registries,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 // 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 // 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 // InstallCliFlags adds command-line options to the top-level flag parser for
// the current process. // the current process.
func (options *ServiceOptions) InstallCliFlags(flags *pflag.FlagSet) { func (options *ServiceOptions) InstallCliFlags(flags *pflag.FlagSet) {
ana := opts.NewNamedListOptsRef("allow-nondistributable-artifacts", &options.AllowNondistributableArtifacts, ValidateIndexName)
mirrors := opts.NewNamedListOptsRef("registry-mirrors", &options.Mirrors, ValidateMirror) mirrors := opts.NewNamedListOptsRef("registry-mirrors", &options.Mirrors, ValidateMirror)
insecureRegistries := opts.NewNamedListOptsRef("insecure-registries", &options.InsecureRegistries, ValidateIndexName) 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(mirrors, "registry-mirror", "Preferred Docker registry mirror")
flags.Var(insecureRegistries, "insecure-registry", "Enable insecure registry communication") flags.Var(insecureRegistries, "insecure-registry", "Enable insecure registry communication")
@ -95,12 +98,50 @@ func newServiceConfig(options ServiceOptions) *serviceConfig {
V2Only: options.V2Only, V2Only: options.V2Only,
} }
config.LoadAllowNondistributableArtifacts(options.AllowNondistributableArtifacts)
config.LoadMirrors(options.Mirrors) config.LoadMirrors(options.Mirrors)
config.LoadInsecureRegistries(options.InsecureRegistries) config.LoadInsecureRegistries(options.InsecureRegistries)
return config 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. // LoadMirrors loads mirrors to config, after removing duplicates.
// Returns an error if mirrors contains an invalid mirror. // Returns an error if mirrors contains an invalid mirror.
func (config *serviceConfig) LoadMirrors(mirrors []string) error { func (config *serviceConfig) LoadMirrors(mirrors []string) error {
@ -211,6 +252,25 @@ skip:
return nil 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 // 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. // 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 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 { if err != nil {
// assume indexName is of the form `host` without the port and go on. // Assume URLHost is of the form `host` without the port and go on.
host = indexName host = URLHost
} }
addrs, err := lookupIP(host) 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. // Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
for _, addr := range addrs { for _, addr := range addrs {
for _, ipnet := range config.InsecureRegistryCIDRs { for _, ipnet := range cidrs {
// check if the addr falls in the subnet // check if the addr falls in the subnet
if (*net.IPNet)(ipnet).Contains(addr) { if (*net.IPNet)(ipnet).Contains(addr) {
return false return true
} }
} }
} }
return true return false
} }
// ValidateMirror validates an HTTP(S) registry mirror // ValidateMirror validates an HTTP(S) registry mirror

View file

@ -1,10 +1,129 @@
package registry package registry
import ( import (
"reflect"
"sort"
"strings" "strings"
"testing" "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) { func TestValidateMirror(t *testing.T) {
valid := []string{ valid := []string{
"http://mirror-1.com", "http://mirror-1.com",

View file

@ -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) { func TestIsSecureIndex(t *testing.T) {
tests := []struct { tests := []struct {
addr string addr string

View file

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

View file

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