64b000c3ea
Passing RepositoryInfo to ResolveAuthConfig, pullRepository, and pushRepository Moving --registry-mirror configuration to registry config Created resolve_repository job Repo names with 'index.docker.io' or 'docker.io' are now synonymous with omitting an index name. Adding test for RepositoryInfo Adding tests for opts.StringSetOpts and registry.ValidateMirror Fixing search term use of repoInfo Adding integration tests for registry mirror configuration Normalizing LookupImage image name to match LocalName parsing rules Normalizing repository LocalName to avoid multiple references to an official image Removing errorOut use in tests Removing TODO comment gofmt changes golint comments cleanup. renaming RegistryOptions => registry.Options, and RegistryServiceConfig => registry.ServiceConfig Splitting out builtins.Registry and registry.NewService calls Stray whitespace cleanup Moving integration tests for Mirrors and InsecureRegistries into TestNewIndexInfo unit test Factoring out ValidateRepositoryName from NewRepositoryInfo Removing unused IndexServerURL Allowing json marshaling of ServiceConfig. Exposing ServiceConfig in /info Switching to CamelCase for json marshaling PR cleanup; removing 'Is' prefix from boolean members. Removing unneeded json tags. Removing non-cleanup related fix for 'localhost:[port]' in splitReposName Merge fixes for gh9735 Fixing integration test Reapplying #9754 Adding comment on config.IndexConfigs use from isSecureIndex Remove unused error return value from isSecureIndex Signed-off-by: Don Kjer <don.kjer@gmail.com> Adding back comment in isSecureIndex Signed-off-by: Don Kjer <don.kjer@gmail.com>
209 lines
6.7 KiB
Go
209 lines
6.7 KiB
Go
package registry
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
)
|
|
|
|
// for mocking in unit tests
|
|
var lookupIP = net.LookupIP
|
|
|
|
// scans string for api version in the URL path. returns the trimmed hostname, if version found, string and API version.
|
|
func scanForAPIVersion(hostname string) (string, APIVersion) {
|
|
var (
|
|
chunks []string
|
|
apiVersionStr string
|
|
)
|
|
if strings.HasSuffix(hostname, "/") {
|
|
chunks = strings.Split(hostname[:len(hostname)-1], "/")
|
|
apiVersionStr = chunks[len(chunks)-1]
|
|
} else {
|
|
chunks = strings.Split(hostname, "/")
|
|
apiVersionStr = chunks[len(chunks)-1]
|
|
}
|
|
for k, v := range apiVersions {
|
|
if apiVersionStr == v {
|
|
hostname = strings.Join(chunks[:len(chunks)-1], "/")
|
|
return hostname, k
|
|
}
|
|
}
|
|
return hostname, DefaultAPIVersion
|
|
}
|
|
|
|
func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
|
|
// *TODO: Allow per-registry configuration of endpoints.
|
|
endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Try HTTPS ping to registry
|
|
endpoint.URL.Scheme = "https"
|
|
if _, err := endpoint.Ping(); err != nil {
|
|
|
|
//TODO: triggering highland build can be done there without "failing"
|
|
|
|
if index.Secure {
|
|
// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
|
|
// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP.
|
|
return nil, fmt.Errorf("Invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host)
|
|
}
|
|
|
|
// If registry is insecure and HTTPS failed, fallback to HTTP.
|
|
log.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err)
|
|
endpoint.URL.Scheme = "http"
|
|
_, err2 := endpoint.Ping()
|
|
if err2 == nil {
|
|
return endpoint, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("Invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
|
|
}
|
|
|
|
return endpoint, nil
|
|
}
|
|
func newEndpoint(hostname string, secure bool) (*Endpoint, error) {
|
|
var (
|
|
endpoint = Endpoint{}
|
|
trimmedHostname string
|
|
err error
|
|
)
|
|
if !strings.HasPrefix(hostname, "http") {
|
|
hostname = "https://" + hostname
|
|
}
|
|
trimmedHostname, endpoint.Version = scanForAPIVersion(hostname)
|
|
endpoint.URL, err = url.Parse(trimmedHostname)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
endpoint.secure = secure
|
|
return &endpoint, nil
|
|
}
|
|
|
|
func (repoInfo *RepositoryInfo) GetEndpoint() (*Endpoint, error) {
|
|
return NewEndpoint(repoInfo.Index)
|
|
}
|
|
|
|
type Endpoint struct {
|
|
URL *url.URL
|
|
Version APIVersion
|
|
secure bool
|
|
}
|
|
|
|
// Get the formated URL for the root of this registry Endpoint
|
|
func (e Endpoint) String() string {
|
|
return fmt.Sprintf("%s/v%d/", e.URL.String(), e.Version)
|
|
}
|
|
|
|
func (e Endpoint) VersionString(version APIVersion) string {
|
|
return fmt.Sprintf("%s/v%d/", e.URL.String(), version)
|
|
}
|
|
|
|
func (e Endpoint) Ping() (RegistryInfo, error) {
|
|
if e.String() == IndexServerAddress() {
|
|
// Skip the check, we now this one is valid
|
|
// (and we never want to fallback to http in case of error)
|
|
return RegistryInfo{Standalone: false}, nil
|
|
}
|
|
|
|
req, err := http.NewRequest("GET", e.String()+"_ping", nil)
|
|
if err != nil {
|
|
return RegistryInfo{Standalone: false}, err
|
|
}
|
|
|
|
resp, _, err := doRequest(req, nil, ConnectTimeout, e.secure)
|
|
if err != nil {
|
|
return RegistryInfo{Standalone: false}, err
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
jsonString, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return RegistryInfo{Standalone: false}, fmt.Errorf("Error while reading the http response: %s", err)
|
|
}
|
|
|
|
// If the header is absent, we assume true for compatibility with earlier
|
|
// versions of the registry. default to true
|
|
info := RegistryInfo{
|
|
Standalone: true,
|
|
}
|
|
if err := json.Unmarshal(jsonString, &info); err != nil {
|
|
log.Debugf("Error unmarshalling the _ping RegistryInfo: %s", err)
|
|
// don't stop here. Just assume sane defaults
|
|
}
|
|
if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" {
|
|
log.Debugf("Registry version header: '%s'", hdr)
|
|
info.Version = hdr
|
|
}
|
|
log.Debugf("RegistryInfo.Version: %q", info.Version)
|
|
|
|
standalone := resp.Header.Get("X-Docker-Registry-Standalone")
|
|
log.Debugf("Registry standalone header: '%s'", standalone)
|
|
// Accepted values are "true" (case-insensitive) and "1".
|
|
if strings.EqualFold(standalone, "true") || standalone == "1" {
|
|
info.Standalone = true
|
|
} else if len(standalone) > 0 {
|
|
// there is a header set, and it is not "true" or "1", so assume fails
|
|
info.Standalone = false
|
|
}
|
|
log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
|
|
return info, nil
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// The list of insecure registries can contain an element with CIDR notation to specify a whole subnet.
|
|
// If the subnet contains one of the IPs of the registry specified by indexName, the latter is considered
|
|
// insecure.
|
|
//
|
|
// indexName 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 in order to check if the IP is contained
|
|
// in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
|
|
// of insecureRegistries.
|
|
func (config *ServiceConfig) isSecureIndex(indexName string) bool {
|
|
// Check for configured index, first. This is needed in case isSecureIndex
|
|
// is called from anything besides NewIndexInfo, in order to honor per-index configurations.
|
|
if index, ok := config.IndexConfigs[indexName]; ok {
|
|
return index.Secure
|
|
}
|
|
|
|
host, _, err := net.SplitHostPort(indexName)
|
|
if err != nil {
|
|
// assume indexName is of the form `host` without the port and go on.
|
|
host = indexName
|
|
}
|
|
|
|
addrs, err := lookupIP(host)
|
|
if err != nil {
|
|
ip := net.ParseIP(host)
|
|
if ip != nil {
|
|
addrs = []net.IP{ip}
|
|
}
|
|
|
|
// if ip == nil, then `host` is neither an IP nor it could be looked up,
|
|
// either because the index is unreachable, or because the index is behind an HTTP proxy.
|
|
// So, len(addrs) == 0 and we're not aborting.
|
|
}
|
|
|
|
// 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 {
|
|
// check if the addr falls in the subnet
|
|
if (*net.IPNet)(ipnet).Contains(addr) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|