github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/registry/service.go (about) 1 package registry 2 3 import ( 4 "crypto/tls" 5 "net/http" 6 "net/url" 7 "strings" 8 "sync" 9 10 "golang.org/x/net/context" 11 12 "github.com/docker/distribution/reference" 13 "github.com/docker/distribution/registry/client/auth" 14 "github.com/docker/docker/api/types" 15 registrytypes "github.com/docker/docker/api/types/registry" 16 "github.com/pkg/errors" 17 "github.com/sirupsen/logrus" 18 ) 19 20 const ( 21 // DefaultSearchLimit is the default value for maximum number of returned search results. 22 DefaultSearchLimit = 25 23 ) 24 25 // Service is the interface defining what a registry service should implement. 26 type Service interface { 27 Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) 28 LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) 29 LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) 30 ResolveRepository(name reference.Named) (*RepositoryInfo, error) 31 Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) 32 ServiceConfig() *registrytypes.ServiceConfig 33 TLSConfig(hostname string) (*tls.Config, error) 34 LoadAllowNondistributableArtifacts([]string) error 35 LoadMirrors([]string) error 36 LoadInsecureRegistries([]string) error 37 } 38 39 // DefaultService is a registry service. It tracks configuration data such as a list 40 // of mirrors. 41 type DefaultService struct { 42 config *serviceConfig 43 mu sync.Mutex 44 } 45 46 // NewService returns a new instance of DefaultService ready to be 47 // installed into an engine. 48 func NewService(options ServiceOptions) (*DefaultService, error) { 49 config, err := newServiceConfig(options) 50 51 return &DefaultService{config: config}, err 52 } 53 54 // ServiceConfig returns the public registry service configuration. 55 func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig { 56 s.mu.Lock() 57 defer s.mu.Unlock() 58 59 servConfig := registrytypes.ServiceConfig{ 60 AllowNondistributableArtifactsCIDRs: make([]*(registrytypes.NetIPNet), 0), 61 AllowNondistributableArtifactsHostnames: make([]string, 0), 62 InsecureRegistryCIDRs: make([]*(registrytypes.NetIPNet), 0), 63 IndexConfigs: make(map[string]*(registrytypes.IndexInfo)), 64 Mirrors: make([]string, 0), 65 } 66 67 // construct a new ServiceConfig which will not retrieve s.Config directly, 68 // and look up items in s.config with mu locked 69 servConfig.AllowNondistributableArtifactsCIDRs = append(servConfig.AllowNondistributableArtifactsCIDRs, s.config.ServiceConfig.AllowNondistributableArtifactsCIDRs...) 70 servConfig.AllowNondistributableArtifactsHostnames = append(servConfig.AllowNondistributableArtifactsHostnames, s.config.ServiceConfig.AllowNondistributableArtifactsHostnames...) 71 servConfig.InsecureRegistryCIDRs = append(servConfig.InsecureRegistryCIDRs, s.config.ServiceConfig.InsecureRegistryCIDRs...) 72 73 for key, value := range s.config.ServiceConfig.IndexConfigs { 74 servConfig.IndexConfigs[key] = value 75 } 76 77 servConfig.Mirrors = append(servConfig.Mirrors, s.config.ServiceConfig.Mirrors...) 78 79 return &servConfig 80 } 81 82 // LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries for Service. 83 func (s *DefaultService) LoadAllowNondistributableArtifacts(registries []string) error { 84 s.mu.Lock() 85 defer s.mu.Unlock() 86 87 return s.config.LoadAllowNondistributableArtifacts(registries) 88 } 89 90 // LoadMirrors loads registry mirrors for Service 91 func (s *DefaultService) LoadMirrors(mirrors []string) error { 92 s.mu.Lock() 93 defer s.mu.Unlock() 94 95 return s.config.LoadMirrors(mirrors) 96 } 97 98 // LoadInsecureRegistries loads insecure registries for Service 99 func (s *DefaultService) LoadInsecureRegistries(registries []string) error { 100 s.mu.Lock() 101 defer s.mu.Unlock() 102 103 return s.config.LoadInsecureRegistries(registries) 104 } 105 106 // Auth contacts the public registry with the provided credentials, 107 // and returns OK if authentication was successful. 108 // It can be used to verify the validity of a client's credentials. 109 func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) { 110 // TODO Use ctx when searching for repositories 111 serverAddress := authConfig.ServerAddress 112 if serverAddress == "" { 113 serverAddress = IndexServer 114 } 115 if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") { 116 serverAddress = "https://" + serverAddress 117 } 118 u, err := url.Parse(serverAddress) 119 if err != nil { 120 return "", "", validationError{errors.Errorf("unable to parse server address: %v", err)} 121 } 122 123 endpoints, err := s.LookupPushEndpoints(u.Host) 124 if err != nil { 125 return "", "", validationError{err} 126 } 127 128 for _, endpoint := range endpoints { 129 login := loginV2 130 if endpoint.Version == APIVersion1 { 131 login = loginV1 132 } 133 134 status, token, err = login(authConfig, endpoint, userAgent) 135 if err == nil { 136 return 137 } 138 if fErr, ok := err.(fallbackError); ok { 139 err = fErr.err 140 logrus.Infof("Error logging in to %s endpoint, trying next endpoint: %v", endpoint.Version, err) 141 continue 142 } 143 144 return "", "", err 145 } 146 147 return "", "", err 148 } 149 150 // splitReposSearchTerm breaks a search term into an index name and remote name 151 func splitReposSearchTerm(reposName string) (string, string) { 152 nameParts := strings.SplitN(reposName, "/", 2) 153 var indexName, remoteName string 154 if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && 155 !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") { 156 // This is a Docker Index repos (ex: samalba/hipache or ubuntu) 157 // 'docker.io' 158 indexName = IndexName 159 remoteName = reposName 160 } else { 161 indexName = nameParts[0] 162 remoteName = nameParts[1] 163 } 164 return indexName, remoteName 165 } 166 167 // Search queries the public registry for images matching the specified 168 // search terms, and returns the results. 169 func (s *DefaultService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) { 170 // TODO Use ctx when searching for repositories 171 if err := validateNoScheme(term); err != nil { 172 return nil, err 173 } 174 175 indexName, remoteName := splitReposSearchTerm(term) 176 177 // Search is a long-running operation, just lock s.config to avoid block others. 178 s.mu.Lock() 179 index, err := newIndexInfo(s.config, indexName) 180 s.mu.Unlock() 181 182 if err != nil { 183 return nil, err 184 } 185 186 // *TODO: Search multiple indexes. 187 endpoint, err := NewV1Endpoint(index, userAgent, http.Header(headers)) 188 if err != nil { 189 return nil, err 190 } 191 192 var client *http.Client 193 if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" { 194 creds := NewStaticCredentialStore(authConfig) 195 scopes := []auth.Scope{ 196 auth.RegistryScope{ 197 Name: "catalog", 198 Actions: []string{"search"}, 199 }, 200 } 201 202 modifiers := Headers(userAgent, nil) 203 v2Client, foundV2, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes) 204 if err != nil { 205 if fErr, ok := err.(fallbackError); ok { 206 logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err) 207 } else { 208 return nil, err 209 } 210 } else if foundV2 { 211 // Copy non transport http client features 212 v2Client.Timeout = endpoint.client.Timeout 213 v2Client.CheckRedirect = endpoint.client.CheckRedirect 214 v2Client.Jar = endpoint.client.Jar 215 216 logrus.Debugf("using v2 client for search to %s", endpoint.URL) 217 client = v2Client 218 } 219 } 220 221 if client == nil { 222 client = endpoint.client 223 if err := authorizeClient(client, authConfig, endpoint); err != nil { 224 return nil, err 225 } 226 } 227 228 r := newSession(client, authConfig, endpoint) 229 230 if index.Official { 231 localName := remoteName 232 if strings.HasPrefix(localName, "library/") { 233 // If pull "library/foo", it's stored locally under "foo" 234 localName = strings.SplitN(localName, "/", 2)[1] 235 } 236 237 return r.SearchRepositories(localName, limit) 238 } 239 return r.SearchRepositories(remoteName, limit) 240 } 241 242 // ResolveRepository splits a repository name into its components 243 // and configuration of the associated registry. 244 func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) { 245 s.mu.Lock() 246 defer s.mu.Unlock() 247 return newRepositoryInfo(s.config, name) 248 } 249 250 // APIEndpoint represents a remote API endpoint 251 type APIEndpoint struct { 252 Mirror bool 253 URL *url.URL 254 Version APIVersion 255 AllowNondistributableArtifacts bool 256 Official bool 257 TrimHostname bool 258 TLSConfig *tls.Config 259 } 260 261 // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint 262 func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) *V1Endpoint { 263 return newV1Endpoint(*e.URL, e.TLSConfig, userAgent, metaHeaders) 264 } 265 266 // TLSConfig constructs a client TLS configuration based on server defaults 267 func (s *DefaultService) TLSConfig(hostname string) (*tls.Config, error) { 268 s.mu.Lock() 269 defer s.mu.Unlock() 270 271 return newTLSConfig(hostname, isSecureIndex(s.config, hostname)) 272 } 273 274 // tlsConfig constructs a client TLS configuration based on server defaults 275 func (s *DefaultService) tlsConfig(hostname string) (*tls.Config, error) { 276 return newTLSConfig(hostname, isSecureIndex(s.config, hostname)) 277 } 278 279 func (s *DefaultService) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) { 280 return s.tlsConfig(mirrorURL.Host) 281 } 282 283 // LookupPullEndpoints creates a list of endpoints to try to pull from, in order of preference. 284 // It gives preference to v2 endpoints over v1, mirrors over the actual 285 // registry, and HTTPS over plain HTTP. 286 func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) { 287 s.mu.Lock() 288 defer s.mu.Unlock() 289 290 return s.lookupEndpoints(hostname) 291 } 292 293 // LookupPushEndpoints creates a list of endpoints to try to push to, in order of preference. 294 // It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP. 295 // Mirrors are not included. 296 func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) { 297 s.mu.Lock() 298 defer s.mu.Unlock() 299 300 allEndpoints, err := s.lookupEndpoints(hostname) 301 if err == nil { 302 for _, endpoint := range allEndpoints { 303 if !endpoint.Mirror { 304 endpoints = append(endpoints, endpoint) 305 } 306 } 307 } 308 return endpoints, err 309 } 310 311 func (s *DefaultService) lookupEndpoints(hostname string) (endpoints []APIEndpoint, err error) { 312 endpoints, err = s.lookupV2Endpoints(hostname) 313 if err != nil { 314 return nil, err 315 } 316 317 if s.config.V2Only { 318 return endpoints, nil 319 } 320 321 legacyEndpoints, err := s.lookupV1Endpoints(hostname) 322 if err != nil { 323 return nil, err 324 } 325 endpoints = append(endpoints, legacyEndpoints...) 326 327 return endpoints, nil 328 }