зеркало из https://github.com/microsoft/docker.git
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:
Родитель
e8c2a33b74
Коммит
67fdf574d5
|
@ -7,9 +7,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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1962,6 +1962,7 @@ _docker_daemon() {
|
|||
local options_with_args="
|
||||
$global_options_with_args
|
||||
--add-runtime
|
||||
--allow-nondistributable-artifacts
|
||||
--api-cors-header
|
||||
--authorization-plugin
|
||||
--bip
|
||||
|
|
|
@ -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" \
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
Загрузка…
Ссылка в новой задаче