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