diff --git a/api/server/server.go b/api/server/server.go index 5e337dfcbef2b..a2120cd3b4a36 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -565,6 +565,7 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon job.SetenvBool("parallel", version.GreaterThan("1.3")) job.SetenvJson("metaHeaders", metaHeaders) job.SetenvJson("authConfig", authConfig) + job.SetenvBool("protectOfficialRegistry", false) } else { //import if tag == "" { repo, tag = parsers.ParseRepositoryTag(repo) diff --git a/builder/internals.go b/builder/internals.go index 7523d121913f3..10ab0c5a916bf 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -438,6 +438,7 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { job.SetenvBool("json", b.StreamFormatter.Json()) job.SetenvBool("parallel", true) job.SetenvJson("authConfig", pullRegistryAuth) + job.SetenvBool("protectOfficialRegistry", true) job.Stdout.Add(b.OutOld) if err := job.Run(); err != nil { return nil, err diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 4891194bd8672..0f208186d36f9 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -895,7 +895,9 @@ _docker() { ) local main_options_with_args=" + --add-registry --bip + --block-registry --bridge -b --dns --dns-search diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index 0af1169204093..6f3739d1111d6 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -43,9 +43,11 @@ function __fish_print_docker_repositories --description 'Print a list of docker end # common options +complete -c docker -f -n '__fish_docker_no_subcommand' -l add-registry -d "[EXPERIMENTAL] Each given registry will be queried before a public Docker registry during image pulls or searches. They will be searched in the order given and treated as insecure." complete -c docker -f -n '__fish_docker_no_subcommand' -l api-enable-cors -d 'Enable CORS headers in the remote API' complete -c docker -f -n '__fish_docker_no_subcommand' -s b -l bridge -d 'Attach containers to a pre-existing network bridge' complete -c docker -f -n '__fish_docker_no_subcommand' -l bip -d "Use this CIDR notation address for the network bridge's IP, not compatible with -b" +complete -c docker -f -n '__fish_docker_no_subcommand' -l block-registry -d '[EXPERIMENTAL] Prevent docker daemon from contacting specified registries. Special keyword "public" represents public Docker registry.' complete -c docker -f -n '__fish_docker_no_subcommand' -s D -l debug -d 'Enable debug mode' complete -c docker -f -n '__fish_docker_no_subcommand' -s d -l daemon -d 'Enable daemon mode' complete -c docker -f -n '__fish_docker_no_subcommand' -l dns -d 'Force Docker to use specific DNS servers' @@ -64,8 +66,6 @@ complete -c docker -f -n '__fish_docker_no_subcommand' -l iptables -d "Enable Do complete -c docker -f -n '__fish_docker_no_subcommand' -l mtu -d 'Set the containers network MTU' complete -c docker -f -n '__fish_docker_no_subcommand' -s p -l pidfile -d 'Path to use for daemon PID file' complete -c docker -f -n '__fish_docker_no_subcommand' -l registry-mirror -d "Specify a preferred Docker registry mirror for pulls from official registry" -complete -c docker -f -n '__fish_docker_no_subcommand' -l registry-replace -d "Registry that shall replace official registry and index. Registry is expected to be insecure." -complete -c docker -f -n '__fish_docker_no_subcommand' -l registry-prepend -d "Each given registry will be prepended to a list of registries queried during image pulls or searches. The last registry given will be queried first. They will be treated as insecure." complete -c docker -f -n '__fish_docker_no_subcommand' -s s -l storage-driver -d 'Force the Docker runtime to use a specific storage driver' complete -c docker -f -n '__fish_docker_no_subcommand' -l selinux-enabled -d 'Enable selinux support. SELinux does not presently support the BTRFS storage driver' complete -c docker -f -n '__fish_docker_no_subcommand' -l storage-opt -d 'Set storage driver options' diff --git a/daemon/config.go b/daemon/config.go index 99e5ce4c8b719..f17032c2e0d20 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -6,6 +6,7 @@ import ( "github.com/docker/docker/daemon/networkdriver" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/registry" ) const ( @@ -42,6 +43,8 @@ type Config struct { Context map[string][]string TrustKeyPath string Labels []string + BlockedRegistries opts.ListOpts + AdditionalRegistries opts.ListOpts } // InstallFlags adds command-line options to the top-level flag parser for @@ -71,6 +74,10 @@ func (config *Config) InstallFlags() { opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "Force Docker to use specific DNS servers") opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains") opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon (displayed in `docker info`)") + config.BlockedRegistries = opts.NewListOpts(registry.ValidateIndexName) + flag.Var(&config.BlockedRegistries, []string{"-block-registry"}, "[EXPERIMENTAL] Prevent Docker daemon from contacting specified registries. Special keyword \"public\" represents public Docker registry.") + config.AdditionalRegistries = opts.NewListOpts(registry.ValidateIndexName) + flag.Var(&config.AdditionalRegistries, []string{"-add-registry"}, "[EXPERIMENTAL] Each given registry will be queried before a public Docker registry during image pulls or searches. They will be searched in the order given and treated as insecure.") } func getDefaultNetworkMtu() int { diff --git a/docker/daemon.go b/docker/daemon.go index f4f15e7aed1ee..58224c6a02e0e 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -80,19 +80,23 @@ func mainDaemon() { return } - if *registryCfg.DefaultRegistry != "" { - defaultRegistry, err := registry.ValidateIndexName(*registryCfg.DefaultRegistry) - if err != nil { - log.Fatal("Given invalid default registry \"%s\": %s", *registryCfg.DefaultRegistry, err.Error()) + for _, r := range daemonCfg.BlockedRegistries.GetAll() { + if r == "public" { + r = registry.INDEXNAME + } + registry.BlockedRegistries[r] = struct{}{} + if r == registry.INDEXNAME { + registry.RegistryList = []string{} } - registry.RegistryList[0] = defaultRegistry } - for _, r := range registryCfg.AdditionalRegistries.GetAll() { - if r != "" { - registry.RegistryList = append([]string{r}, registry.RegistryList...) + newRegistryList := []string{} + for _, r := range daemonCfg.AdditionalRegistries.GetAll() { + if _, ok := registry.BlockedRegistries[r]; !ok { + newRegistryList = append(newRegistryList, r) } } + registry.RegistryList = append(newRegistryList, registry.RegistryList...) eng := engine.New() signal.Trap(eng.Shutdown) diff --git a/docker/docker.go b/docker/docker.go index 6410171fab1ee..6e26ea7f6c210 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -84,6 +84,12 @@ func main() { log.Fatal(err) } + for _, lopt := range []string{"-add-registry", "-block-registry"} { + if flag.IsSet(lopt) { + log.Fatalf("The -%s option is recognized only by Docker daemon.", lopt) + } + } + var ( cli *client.DockerCli tlsConfig tls.Config diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index ec6d32d5e5913..5104ae64c2a87 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -34,6 +34,9 @@ unix://[/path/to/socket] to use. The socket(s) to bind to in daemon mode specified using one or more tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd. +**--add-registry**=[] + **EXPERIMENTAL** Each given registry will be queried before a public Docker registry during image pulls or searches. They will be searched in the order given and treated as insecure. Registry mirrors won't apply to them. + **--api-enable-cors**=*true*|*false* Enable CORS headers in the remote API. Default is false. @@ -43,6 +46,9 @@ unix://[/path/to/socket] to use. **--bip**="" Use the provided CIDR notation address for the dynamically created bridge (docker0); Mutually exclusive of \-b +**--block-registry**=[] + **EXPERIMENTAL** Prevent Docker daemon from contacting specified registries. Special keyword "public" represents public Docker registry. + **-d**=*true*|*false* Enable daemon mode. Default is false. @@ -91,12 +97,6 @@ unix://[/path/to/socket] to use. **--registry-mirror**=:// Prepend a registry mirror to be used for image pulls from public Docker registry. May be specified multiple times. -**--registry-prepend**=[] - Each given registry will be prepended to a list of registries queried during image pulls or searches. The last registry given will be queried first. They will be treated as insecure. Registry mirrors won't apply to them. - -**--registry-replace**="" - Registry that shall replace official Docker registry and index (e.g. 10.172.10.2:5000, private-registry.foo.bar). Additional registries added with --registry-prepend will be queried before this one. Use this option if you do not want to query official registry at all. It will be treated as insecure. Registry mirrors won't apply to given registry. - **-s**="" Force the Docker runtime to use a specific storage driver. diff --git a/graph/export.go b/graph/export.go index 3f7ecd3c4e9e9..fcd96a6a01410 100644 --- a/graph/export.go +++ b/graph/export.go @@ -40,7 +40,9 @@ func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status { } } for _, name := range job.Args { - name = registry.NormalizeLocalName(name) + if _, exists := s.Repositories[name]; !exists { + name = registry.NormalizeLocalName(name) + } log.Debugf("Serializing %s", name) rootRepo := s.Repositories[name] if rootRepo != nil { diff --git a/graph/pull.go b/graph/pull.go index 65cb5eaf789ba..0c9a5fc123289 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -28,6 +28,13 @@ func (s *TagStore) CmdRegistryPull(job *engine.Job) engine.Status { // the matching image is found. if registry.RepositoryNameHasIndex(tmp) { registries = []string{""} + } else if len(registries) == 0 { + return job.Errorf("No configured registry to pull from.") + } else if job.GetenvBool("protectOfficialRegistry") && registries[0] != registry.INDEXNAME { + // We must ensure that registry missing hostname will be pulled from + // official one, if the `protectOfficialRegistry` tells us so. + registries = []string{""} + tmp = fmt.Sprintf("%s/%s", registry.INDEXNAME, tmp) } for i, r := range registries { if i > 0 { diff --git a/graph/tags.go b/graph/tags.go index 6bdb296cd1814..346a42ce24b08 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -178,7 +178,9 @@ func (store *TagStore) Delete(repoName, tag string) (bool, error) { if err := store.reload(); err != nil { return false, err } - repoName = registry.NormalizeLocalName(repoName) + if _, exists := store.Repositories[repoName]; !exists { + repoName = registry.NormalizeLocalName(repoName) + } if r, exists := store.Repositories[repoName]; exists { if tag != "" { if _, exists2 := r[tag]; exists2 { @@ -240,10 +242,12 @@ func (store *TagStore) Get(repoName string) (Repository, error) { if err := store.reload(); err != nil { return nil, err } - repoName = registry.NormalizeLocalName(repoName) if r, exists := store.Repositories[repoName]; exists { return r, nil } + if r, exists := store.Repositories[registry.NormalizeLocalName(repoName)]; exists { + return r, nil + } return nil, nil } diff --git a/registry/config.go b/registry/config.go index dba3a51c129c8..555568c2d2fdc 100644 --- a/registry/config.go +++ b/registry/config.go @@ -16,10 +16,8 @@ import ( // Options holds command line options. type Options struct { - Mirrors opts.ListOpts - InsecureRegistries opts.ListOpts - DefaultRegistry *string - AdditionalRegistries opts.ListOpts + Mirrors opts.ListOpts + InsecureRegistries opts.ListOpts } const ( @@ -33,6 +31,8 @@ const ( ) var ( + // A set of blocked registries + BlockedRegistries map[string]struct{} // List of registries to query. RegistryList = []string{INDEXNAME} ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") @@ -41,8 +41,15 @@ var ( validRepo = regexp.MustCompile(`^([a-z0-9-_.]+)$`) ) +func init() { + BlockedRegistries = make(map[string]struct{}) +} + // IndexServerName returns the name of default index server. func IndexServerName() string { + if len(RegistryList) < 1 { + return "" + } return RegistryList[0] } @@ -53,6 +60,8 @@ func IndexServerAddress(indexName string) string { return INDEXSERVER } else if indexName != "" { return fmt.Sprintf("http://%s/v1/", indexName) + } else if IndexServerName() == "" { + return "" } else { return fmt.Sprintf("http://%s/v1/", IndexServerName()) } @@ -65,6 +74,8 @@ func RegistryServerAddress(indexName string) string { return REGISTRYSERVER } else if indexName != "" { return fmt.Sprintf("http://%s/v2/", indexName) + } else if IndexServerName() == "" { + return "" } else { return fmt.Sprintf("http://%s/v2/", IndexServerName()) } @@ -77,9 +88,6 @@ func (options *Options) InstallFlags() { flag.Var(&options.Mirrors, []string{"-registry-mirror"}, "Specify a preferred Docker registry mirror for pulls from official registry") options.InsecureRegistries = opts.NewListOpts(ValidateIndexName) flag.Var(&options.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback) (e.g., localhost:5000 or 10.20.0.0/16)") - options.DefaultRegistry = flag.String([]string{"-registry-replace"}, "", "Registry that shall replace official registry and index. It will be treated as insecure.") - options.AdditionalRegistries = opts.NewListOpts(ValidateIndexName) - flag.Var(&options.AdditionalRegistries, []string{"-registry-prepend"}, "Each given registry will be prepended to a list of registries queried during image pulls or searches. The last registry given will be queried first. They will be treated as insecure.") } type netIPNet net.IPNet @@ -234,8 +242,13 @@ func ValidateMirror(val string) (string, error) { // ValidateIndexName validates an index name. func ValidateIndexName(val string) (string, error) { // 'index.docker.io' => 'docker.io' - if val == "index."+IndexServerName() { - val = IndexServerName() + if val == "index."+INDEXNAME { + val = INDEXNAME + } + for _, r := range RegistryList { + if val == "index."+r { + val = r + } } // *TODO: Check if valid hostname[:port]/ip[:port]? return val, nil @@ -373,6 +386,10 @@ func (config *ServiceConfig) NewRepositoryInfo(reposName string) (*RepositoryInf RemoteName: remoteName, } + if _, ok := BlockedRegistries[indexName]; ok { + return nil, fmt.Errorf("Blocked registry \"%s\"", indexName) + } + var err error repoInfo.Index, err = config.NewIndexInfo(indexName) if err != nil { diff --git a/registry/service.go b/registry/service.go index 88609d772ccc4..f282c0127c725 100644 --- a/registry/service.go +++ b/registry/service.go @@ -55,6 +55,9 @@ func (s *Service) Auth(job *engine.Job) engine.Status { // Use the official registry address if not specified. addr = IndexServerAddress("") } + if addr == "" { + return job.Errorf("No configured registry to authenticate to.") + } if index, err = ResolveIndexInfo(job, addr); err != nil { return job.Error(err) @@ -136,6 +139,8 @@ func (s *Service) Search(job *engine.Job) engine.Status { if err := doSearch(term); err != nil { return job.Error(err) } + } else if len(RegistryList) < 1 { + return job.Errorf("No configured repository to search.") } else { var ( err error diff --git a/registry/session.go b/registry/session.go index 9905626a60297..cd50cc5399aca 100644 --- a/registry/session.go +++ b/registry/session.go @@ -200,6 +200,15 @@ func (r *Session) GetRemoteImageLayer(imgID, registry string, token []string, im return res.Body, nil } +func isEndpointBlocked(endpoint string) bool { + if parsedURL, err := url.Parse(endpoint); err == nil { + if _, ok := BlockedRegistries[parsedURL.Host]; !ok { + return false + } + } + return true +} + func (r *Session) GetRemoteTags(registries []string, repository string, token []string) (map[string]string, error) { if strings.Count(repository, "/") == 0 { // This will be removed once the Registry supports auto-resolution on @@ -207,6 +216,10 @@ func (r *Session) GetRemoteTags(registries []string, repository string, token [] repository = "library/" + repository } for _, host := range registries { + if isEndpointBlocked(host) { + log.Errorf("Cannot query blocked registry at %s for remote tags.", host) + continue + } endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository) req, err := r.reqFactory.NewRequest("GET", endpoint, nil)