/* Copyright The containerd Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "errors" "net" "net/http" ) // HostCapabilities represent the capabilities of the registry // host. This also represents the set of operations for which // the registry host may be trusted to perform. // // For example pushing is a capability which should only be // performed on an upstream source, not a mirror. // Resolving (the process of converting a name into a digest) // must be considered a trusted operation and only done by // a host which is trusted (or more preferably by secure process // which can prove the provenance of the mapping). A public // mirror should never be trusted to do a resolve action. // // | Registry Type | Pull | Resolve | Push | // |------------------|------|---------|------| // | Public Registry | yes | yes | yes | // | Private Registry | yes | yes | yes | // | Public Mirror | yes | no | no | // | Private Mirror | yes | yes | no | type HostCapabilities uint8 const ( // HostCapabilityPull represents the capability to fetch manifests // and blobs by digest HostCapabilityPull HostCapabilities = 1 << iota // HostCapabilityResolve represents the capability to fetch manifests // by name HostCapabilityResolve // HostCapabilityPush represents the capability to push blobs and // manifests HostCapabilityPush // Reserved for future capabilities (i.e. search, catalog, remove) ) // Has checks whether the capabilities list has the provide capability func (c HostCapabilities) Has(t HostCapabilities) bool { return c&t == t } // RegistryHost represents a complete configuration for a registry // host, representing the capabilities, authorizations, connection // configuration, and location. type RegistryHost struct { Client *http.Client Authorizer Authorizer Host string Scheme string Path string Capabilities HostCapabilities Header http.Header } func (h RegistryHost) isProxy(refhost string) bool { if refhost != h.Host { if refhost != "docker.io" || h.Host != "registry-1.docker.io" { return true } } return false } // RegistryHosts fetches the registry hosts for a given namespace, // provided by the host component of an distribution image reference. type RegistryHosts func(string) ([]RegistryHost, error) // Registries joins multiple registry configuration functions, using the same // order as provided within the arguments. When an empty registry configuration // is returned with a nil error, the next function will be called. // NOTE: This function will not join configurations, as soon as a non-empty // configuration is returned from a configuration function, it will be returned // to the caller. func Registries(registries ...RegistryHosts) RegistryHosts { return func(host string) ([]RegistryHost, error) { for _, registry := range registries { config, err := registry(host) if err != nil { return config, err } if len(config) > 0 { return config, nil } } return nil, nil } } type registryOpts struct { authorizer Authorizer plainHTTP func(string) (bool, error) host func(string) (string, error) client *http.Client } // RegistryOpt defines a registry default option type RegistryOpt func(*registryOpts) // WithPlainHTTP configures registries to use plaintext http scheme // for the provided host match function. func WithPlainHTTP(f func(string) (bool, error)) RegistryOpt { return func(opts *registryOpts) { opts.plainHTTP = f } } // WithAuthorizer configures the default authorizer for a registry func WithAuthorizer(a Authorizer) RegistryOpt { return func(opts *registryOpts) { opts.authorizer = a } } // WithHostTranslator defines the default translator to use for registry hosts func WithHostTranslator(h func(string) (string, error)) RegistryOpt { return func(opts *registryOpts) { opts.host = h } } // WithClient configures the default http client for a registry func WithClient(c *http.Client) RegistryOpt { return func(opts *registryOpts) { opts.client = c } } // ConfigureDefaultRegistries is used to create a default configuration for // registries. For more advanced configurations or per-domain setups, // the RegistryHosts interface should be used directly. // NOTE: This function will always return a non-empty value or error func ConfigureDefaultRegistries(ropts ...RegistryOpt) RegistryHosts { var opts registryOpts for _, opt := range ropts { opt(&opts) } return func(host string) ([]RegistryHost, error) { config := RegistryHost{ Client: opts.client, Authorizer: opts.authorizer, Host: host, Scheme: "https", Path: "/v2", Capabilities: HostCapabilityPull | HostCapabilityResolve | HostCapabilityPush, } if config.Client == nil { config.Client = http.DefaultClient } if opts.plainHTTP != nil { match, err := opts.plainHTTP(host) if err != nil { return nil, err } if match { config.Scheme = "http" } } if opts.host != nil { var err error config.Host, err = opts.host(config.Host) if err != nil { return nil, err } } else if host == "docker.io" { config.Host = "registry-1.docker.io" } return []RegistryHost{config}, nil } } // MatchAllHosts is a host match function which is always true. func MatchAllHosts(string) (bool, error) { return true, nil } // MatchLocalhost is a host match function which returns true for // localhost. // // Note: this does not handle matching of ip addresses in octal, // decimal or hex form. func MatchLocalhost(host string) (bool, error) { switch { case host == "::1": return true, nil case host == "[::1]": return true, nil } h, p, err := net.SplitHostPort(host) // addrError helps distinguish between errors of form // "no colon in address" and "too many colons in address". // The former is fine as the host string need not have a // port. Latter needs to be handled. addrError := &net.AddrError{ Err: "missing port in address", Addr: host, } if err != nil { if err.Error() != addrError.Error() { return false, err } // host string without any port specified h = host } else if len(p) == 0 { return false, errors.New("invalid host name format") } // use ipv4 dotted decimal for further checking if h == "localhost" { h = "127.0.0.1" } ip := net.ParseIP(h) return ip.IsLoopback(), nil }