func (p *v1Pusher) Push(ctx context.Context) error { tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name) if err != nil { return err } // Adds Docker-specific headers as well as user-specified headers (metaHeaders) tr := transport.NewTransport( // TODO(tiborvass): was NoTimeout registry.NewTransport(tlsConfig), registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)..., ) client := registry.HTTPClient(tr) v1Endpoint, err := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders) if err != nil { logrus.Debugf("Could not get v1 endpoint: %v", err) return fallbackError{err: err} } p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint) if err != nil { // TODO(dmcgowan): Check if should fallback return fallbackError{err: err} } if err := p.pushRepository(ctx); err != nil { // TODO(dmcgowan): Check if should fallback return err } return nil }
func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) error { if _, isCanonical := ref.(reference.Canonical); isCanonical { // Allowing fallback, because HTTPS v1 is before HTTP v2 return fallbackError{err: ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}} } tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name) if err != nil { return err } // Adds Docker-specific headers as well as user-specified headers (metaHeaders) tr := transport.NewTransport( // TODO(tiborvass): was ReceiveTimeout registry.NewTransport(tlsConfig), registry.DockerHeaders(dockerversion.DockerUserAgent(), p.config.MetaHeaders)..., ) client := registry.HTTPClient(tr) v1Endpoint, err := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(), p.config.MetaHeaders) if err != nil { logrus.Debugf("Could not get v1 endpoint: %v", err) return fallbackError{err: err} } p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint) if err != nil { // TODO(dmcgowan): Check if should fallback logrus.Debugf("Fallback from error: %s", err) return fallbackError{err: err} } if err := p.pullRepository(ctx, ref); err != nil { // TODO(dmcgowan): Check if should fallback return err } progress.Message(p.config.ProgressOutput, "", p.repoInfo.FullName()+": this image was pulled from a legacy registry. Important: This registry version will not be supported in future versions of docker.") return nil }
func (mf *v1ManifestFetcher) Fetch(ctx context.Context, ref reference.Named) (*types.ImageInspect, error) { var ( imgInspect *types.ImageInspect ) if _, isCanonical := ref.(reference.Canonical); isCanonical { // Allowing fallback, because HTTPS v1 is before HTTP v2 return nil, fallbackError{err: dockerdistribution.ErrNoSupport{errors.New("Cannot pull by digest with v1 registry")}} } tlsConfig, err := mf.service.TLSConfig(mf.repoInfo.Index.Name) if err != nil { return nil, err } // Adds Docker-specific headers as well as user-specified headers (metaHeaders) tr := transport.NewTransport( registry.NewTransport(tlsConfig), //registry.DockerHeaders(mf.config.MetaHeaders)..., registry.DockerHeaders(dockerversion.DockerUserAgent(), nil)..., ) client := registry.HTTPClient(tr) //v1Endpoint, err := mf.endpoint.ToV1Endpoint(mf.config.MetaHeaders) v1Endpoint, err := mf.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(), nil) if err != nil { logrus.Debugf("Could not get v1 endpoint: %v", err) return nil, fallbackError{err: err} } mf.session, err = registry.NewSession(client, &mf.authConfig, v1Endpoint) if err != nil { logrus.Debugf("Fallback from error: %s", err) return nil, fallbackError{err: err} } imgInspect, err = mf.fetchWithSession(ctx, ref) if err != nil { return nil, err } return imgInspect, nil }
func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig) (*client.NotaryRepository, error) { server, err := trustServer(repoInfo.Index) if err != nil { return nil, err } var cfg = tlsconfig.ClientDefault cfg.InsecureSkipVerify = !repoInfo.Index.Secure // Get certificate base directory certDir, err := cli.certificateDirectory(server) if err != nil { return nil, err } logrus.Debugf("reading certificate directory: %s", certDir) if err := registry.ReadCertsDirectory(&cfg, certDir); err != nil { return nil, err } base := &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, DualStack: true, }).Dial, TLSHandshakeTimeout: 10 * time.Second, TLSClientConfig: &cfg, DisableKeepAlives: true, } // Skip configuration headers since request is not going to Docker daemon modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(), http.Header{}) authTransport := transport.NewTransport(base, modifiers...) pingClient := &http.Client{ Transport: authTransport, Timeout: 5 * time.Second, } endpointStr := server + "/v2/" req, err := http.NewRequest("GET", endpointStr, nil) if err != nil { return nil, err } challengeManager := auth.NewSimpleChallengeManager() resp, err := pingClient.Do(req) if err != nil { // Ignore error on ping to operate in offline mode logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err) } else { defer resp.Body.Close() // Add response to the challenge manager to parse out // authentication header and register authentication method if err := challengeManager.AddResponse(resp); err != nil { return nil, err } } creds := simpleCredentialStore{auth: authConfig} tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), "push", "pull") basicHandler := auth.NewBasicHandler(creds) modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))) tr := transport.NewTransport(base, modifiers...) return client.NewNotaryRepository(cli.trustDirectory(), repoInfo.FullName(), server, tr, cli.getPassphraseRetriever()) }
// NewV2Repository returns a repository (v2 only). It creates a HTTP transport // providing timeout settings and authentication support, and also verifies the // remote API version. func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string) (repo distribution.Repository, foundVersion bool, err error) { repoName := repoInfo.FullName() // If endpoint does not support CanonicalName, use the RemoteName instead if endpoint.TrimHostname { repoName = repoInfo.RemoteName() } // TODO(dmcgowan): Call close idle connections when complete, use keep alive base := &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: func(network, address string) (net.Conn, error) { dialer := &net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, DualStack: true, } netConn, err := dialer.Dial(network, address) if err != nil { return netConn, err } return &conn{ Conn: netConn, readTimeout: time.Minute, writeTimeout: time.Minute, }, nil }, TLSHandshakeTimeout: 10 * time.Second, TLSClientConfig: endpoint.TLSConfig, // TODO(dmcgowan): Call close idle connections when complete and use keep alive DisableKeepAlives: true, } modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(), metaHeaders) authTransport := transport.NewTransport(base, modifiers...) pingClient := &http.Client{ Transport: authTransport, Timeout: 15 * time.Second, } endpointStr := strings.TrimRight(endpoint.URL, "/") + "/v2/" req, err := http.NewRequest("GET", endpointStr, nil) if err != nil { return nil, false, err } resp, err := pingClient.Do(req) if err != nil { return nil, false, err } defer resp.Body.Close() v2Version := auth.APIVersion{ Type: "registry", Version: "2.0", } versions := auth.APIVersions(resp, registry.DefaultRegistryVersionHeader) for _, pingVersion := range versions { if pingVersion == v2Version { // The version header indicates we're definitely // talking to a v2 registry. So don't allow future // fallbacks to the v1 protocol. foundVersion = true break } } challengeManager := auth.NewSimpleChallengeManager() if err := challengeManager.AddResponse(resp); err != nil { return nil, foundVersion, err } if authConfig.RegistryToken != "" { passThruTokenHandler := &existingTokenHandler{token: authConfig.RegistryToken} modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler)) } else { creds := dumbCredentialStore{auth: authConfig} tokenHandler := auth.NewTokenHandler(authTransport, creds, repoName, actions...) basicHandler := auth.NewBasicHandler(creds) modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) } tr := transport.NewTransport(base, modifiers...) repo, err = client.NewRepository(ctx, repoName, endpoint.URL, tr) return repo, foundVersion, err }
// SearchRegistryForImages queries the registry for images matching // term. authConfig is used to login. func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, term string, authConfig *types.AuthConfig, headers map[string][]string) (*registrytypes.SearchResults, error) { return daemon.RegistryService.Search(term, authConfig, dockerversion.DockerUserAgent(ctx), headers) }
// AuthenticateToRegistry checks the validity of credentials in authConfig func (daemon *Daemon) AuthenticateToRegistry(ctx context.Context, authConfig *types.AuthConfig) (string, string, error) { return daemon.RegistryService.Auth(authConfig, dockerversion.DockerUserAgent(ctx)) }
// SearchRegistryForImages queries the registry for images matching // term. authConfig is used to login. func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs string, term string, limit int, authConfig *types.AuthConfig, headers map[string][]string) (*registrytypes.SearchResults, error) { searchFilters, err := filters.FromParam(filtersArgs) if err != nil { return nil, err } if err := searchFilters.Validate(acceptedSearchFilterTags); err != nil { return nil, err } var isAutomated, isOfficial bool var hasStarFilter = 0 if searchFilters.Include("is-automated") { if searchFilters.UniqueExactMatch("is-automated", "true") { isAutomated = true } else if !searchFilters.UniqueExactMatch("is-automated", "false") { return nil, fmt.Errorf("Invalid filter 'is-automated=%s'", searchFilters.Get("is-automated")) } } if searchFilters.Include("is-official") { if searchFilters.UniqueExactMatch("is-official", "true") { isOfficial = true } else if !searchFilters.UniqueExactMatch("is-official", "false") { return nil, fmt.Errorf("Invalid filter 'is-official=%s'", searchFilters.Get("is-official")) } } if searchFilters.Include("stars") { hasStars := searchFilters.Get("stars") for _, hasStar := range hasStars { iHasStar, err := strconv.Atoi(hasStar) if err != nil { return nil, fmt.Errorf("Invalid filter 'stars=%s'", hasStar) } if iHasStar > hasStarFilter { hasStarFilter = iHasStar } } } unfilteredResult, err := daemon.RegistryService.Search(ctx, term, limit, authConfig, dockerversion.DockerUserAgent(ctx), headers) if err != nil { return nil, err } filteredResults := []registrytypes.SearchResult{} for _, result := range unfilteredResult.Results { if searchFilters.Include("is-automated") { if isAutomated != result.IsAutomated { continue } } if searchFilters.Include("is-official") { if isOfficial != result.IsOfficial { continue } } if searchFilters.Include("stars") { if result.StarCount < hasStarFilter { continue } } filteredResults = append(filteredResults, result) } return ®istrytypes.SearchResults{ Query: unfilteredResult.Query, NumResults: len(filteredResults), Results: filteredResults, }, nil }
// NewV2Repository returns a repository (v2 only). It creates a HTTP transport // providing timeout settings and authentication support, and also verifies the // remote API version. func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string) (repo distribution.Repository, foundVersion bool, err error) { repoName := repoInfo.FullName() // If endpoint does not support CanonicalName, use the RemoteName instead if endpoint.TrimHostname { repoName = repoInfo.RemoteName() } // TODO(dmcgowan): Call close idle connections when complete, use keep alive base := &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, DualStack: true, }).Dial, TLSHandshakeTimeout: 10 * time.Second, TLSClientConfig: endpoint.TLSConfig, // TODO(dmcgowan): Call close idle connections when complete and use keep alive DisableKeepAlives: true, } modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(), metaHeaders) authTransport := transport.NewTransport(base, modifiers...) challengeManager, foundVersion, err := registry.PingV2Registry(endpoint, authTransport) if err != nil { transportOK := false if responseErr, ok := err.(registry.PingResponseError); ok { transportOK = true err = responseErr.Err } return nil, foundVersion, fallbackError{ err: err, confirmedV2: foundVersion, transportOK: transportOK, } } if authConfig.RegistryToken != "" { passThruTokenHandler := &existingTokenHandler{token: authConfig.RegistryToken} modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler)) } else { creds := dumbCredentialStore{auth: authConfig} tokenHandler := auth.NewTokenHandler(authTransport, creds, repoName, actions...) basicHandler := auth.NewBasicHandler(creds) modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) } tr := transport.NewTransport(base, modifiers...) repoNameRef, err := distreference.ParseNamed(repoName) if err != nil { return nil, foundVersion, fallbackError{ err: err, confirmedV2: foundVersion, transportOK: true, } } repo, err = client.NewRepository(ctx, repoNameRef, endpoint.URL.String(), tr) if err != nil { err = fallbackError{ err: err, confirmedV2: foundVersion, transportOK: true, } } return }
func GetData(c *cli.Context, name string) (*types.ImageInspect, error) { if err := validateName(name); err != nil { return nil, err } ref, err := reference.ParseNamed(name) if err != nil { return nil, err } repoInfo, err := registry.ParseRepositoryInfo(ref) if err != nil { return nil, err } authConfig, err := getAuthConfig(c, repoInfo.Index) if err != nil { return nil, err } if err := validateRepoName(repoInfo.Name()); err != nil { return nil, err } options := ®istry.Options{} options.Mirrors = opts.NewListOpts(nil) options.InsecureRegistries = opts.NewListOpts(nil) options.InsecureRegistries.Set("0.0.0.0/0") registryService := registry.NewService(options) // TODO(runcom): hacky, provide a way of passing tls cert (flag?) to be used to lookup for _, ic := range registryService.Config.IndexConfigs { ic.Secure = false } endpoints, err := registryService.LookupPullEndpoints(repoInfo.Hostname()) if err != nil { return nil, err } logrus.Debugf("endpoints: %v", endpoints) var ( ctx = context.Background() lastErr error discardNoSupportErrors bool imgInspect *types.ImageInspect confirmedV2 bool confirmedTLSRegistries = make(map[string]struct{}) ) for _, endpoint := range endpoints { // make sure I can reach the registry, same as docker pull does v1endpoint, err := endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(), nil) if err != nil { return nil, err } if _, err := v1endpoint.Ping(); err != nil { if strings.Contains(err.Error(), "timeout") { return nil, err } continue } if confirmedV2 && endpoint.Version == registry.APIVersion1 { logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL) continue } if endpoint.URL.Scheme != "https" { if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS { logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL) continue } } logrus.Debugf("Trying to fetch image manifest of %s repository from %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version) //fetcher, err := newManifestFetcher(endpoint, repoInfo, config) fetcher, err := newManifestFetcher(endpoint, repoInfo, authConfig, registryService) if err != nil { lastErr = err continue } if imgInspect, err = fetcher.Fetch(ctx, ref); err != nil { // Was this fetch cancelled? If so, don't try to fall back. fallback := false select { case <-ctx.Done(): default: if fallbackErr, ok := err.(fallbackError); ok { fallback = true confirmedV2 = confirmedV2 || fallbackErr.confirmedV2 if fallbackErr.transportOK && endpoint.URL.Scheme == "https" { confirmedTLSRegistries[endpoint.URL.Host] = struct{}{} } err = fallbackErr.err } } if fallback { if _, ok := err.(distribution.ErrNoSupport); !ok { // Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors. discardNoSupportErrors = true // save the current error lastErr = err } else if !discardNoSupportErrors { // Save the ErrNoSupport error, because it's either the first error or all encountered errors // were also ErrNoSupport errors. lastErr = err } continue } logrus.Errorf("Not continuing with pull after error: %v", err) return nil, err } return imgInspect, nil } if lastErr == nil { lastErr = fmt.Errorf("no endpoints found for %s", ref.String()) } return nil, lastErr }
// NewV2Repository returns a repository (v2 only). It creates an HTTP transport // providing timeout settings and authentication support, and also verifies the // remote API version. func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string) (repo distribution.Repository, foundVersion bool, err error) { repoName := repoInfo.FullName() // If endpoint does not support CanonicalName, use the RemoteName instead if endpoint.TrimHostname { repoName = repoInfo.RemoteName() } direct := &net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, DualStack: true, } // TODO(dmcgowan): Call close idle connections when complete, use keep alive base := &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: direct.Dial, TLSHandshakeTimeout: 10 * time.Second, TLSClientConfig: endpoint.TLSConfig, // TODO(dmcgowan): Call close idle connections when complete and use keep alive DisableKeepAlives: true, } proxyDialer, err := sockets.DialerFromEnvironment(direct) if err == nil { base.Dial = proxyDialer.Dial } modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), metaHeaders) authTransport := transport.NewTransport(base, modifiers...) challengeManager, foundVersion, err := registry.PingV2Registry(endpoint.URL, authTransport) if err != nil { transportOK := false if responseErr, ok := err.(registry.PingResponseError); ok { transportOK = true err = responseErr.Err } return nil, foundVersion, fallbackError{ err: err, confirmedV2: foundVersion, transportOK: transportOK, } } if authConfig.RegistryToken != "" { passThruTokenHandler := &existingTokenHandler{token: authConfig.RegistryToken} modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler)) } else { scope := auth.RepositoryScope{ Repository: repoName, Actions: actions, } // Keep image repositories blank for scope compatibility if repoInfo.Class != "image" { scope.Class = repoInfo.Class } creds := registry.NewStaticCredentialStore(authConfig) tokenHandlerOptions := auth.TokenHandlerOptions{ Transport: authTransport, Credentials: creds, Scopes: []auth.Scope{scope}, ClientID: registry.AuthClientID, } tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions) basicHandler := auth.NewBasicHandler(creds) modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) } tr := transport.NewTransport(base, modifiers...) repoNameRef, err := distreference.ParseNamed(repoName) if err != nil { return nil, foundVersion, fallbackError{ err: err, confirmedV2: foundVersion, transportOK: true, } } repo, err = client.NewRepository(ctx, repoNameRef, endpoint.URL.String(), tr) if err != nil { err = fallbackError{ err: err, confirmedV2: foundVersion, transportOK: true, } } return }