// loginV2 tries to login to the v2 registry server. The given registry // endpoint will be pinged to get authorization challenges. These challenges // will be used to authenticate against the registry to validate credentials. func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent string) (string, error) { logrus.Debugf("attempting v2 login to registry endpoint %s", endpoint) modifiers := DockerHeaders(userAgent, nil) authTransport := transport.NewTransport(NewTransport(endpoint.TLSConfig), modifiers...) challengeManager, foundV2, err := PingV2Registry(endpoint, authTransport) if err != nil { if !foundV2 { err = fallbackError{err: err} } return "", err } creds := loginCredentialStore{ authConfig: authConfig, } tokenHandler := auth.NewTokenHandler(authTransport, creds, "") basicHandler := auth.NewBasicHandler(creds) modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) tr := transport.NewTransport(authTransport, modifiers...) loginClient := &http.Client{ Transport: tr, Timeout: 15 * time.Second, } endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/" req, err := http.NewRequest("GET", endpointStr, nil) if err != nil { if !foundV2 { err = fallbackError{err: err} } return "", err } resp, err := loginClient.Do(req) if err != nil { if !foundV2 { err = fallbackError{err: err} } return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { // TODO(dmcgowan): Attempt to further interpret result, status code and error code string err := fmt.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode)) if !foundV2 { err = fallbackError{err: err} } return "", err } return "Login Succeeded", nil }
func tokenAuth(trustServerURL string, baseTransport *http.Transport, gun string, readOnly bool) (http.RoundTripper, error) { // TODO(dmcgowan): add notary specific headers authTransport := transport.NewTransport(baseTransport) pingClient := &http.Client{ Transport: authTransport, Timeout: 5 * time.Second, } endpoint, err := url.Parse(trustServerURL) if err != nil { return nil, fmt.Errorf("Could not parse remote trust server url (%s): %s", trustServerURL, err.Error()) } if endpoint.Scheme == "" { return nil, fmt.Errorf("Trust server url has to be in the form of http(s)://URL:PORT. Got: %s", trustServerURL) } subPath, err := url.Parse("v2/") if err != nil { return nil, fmt.Errorf("Failed to parse v2 subpath. This error should not have been reached. Please report it as an issue at https://github.com/docker/notary/issues: %s", err.Error()) } endpoint = endpoint.ResolveReference(subPath) req, err := http.NewRequest("GET", endpoint.String(), nil) if err != nil { return nil, err } resp, err := pingClient.Do(req) if err != nil { logrus.Errorf("could not reach %s: %s", trustServerURL, err.Error()) logrus.Info("continuing in offline mode") return nil, nil } // non-nil err means we must close body defer resp.Body.Close() if (resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices) && resp.StatusCode != http.StatusUnauthorized { // If we didn't get a 2XX range or 401 status code, we're not talking to a notary server. // The http client should be configured to handle redirects so at this point, 3XX is // not a valid status code. logrus.Errorf("could not reach %s: %d", trustServerURL, resp.StatusCode) logrus.Info("continuing in offline mode") return nil, nil } challengeManager := auth.NewSimpleChallengeManager() if err := challengeManager.AddResponse(resp); err != nil { return nil, err } ps := passwordStore{anonymous: readOnly} tokenHandler := auth.NewTokenHandler(authTransport, ps, gun, "push", "pull") basicHandler := auth.NewBasicHandler(ps) modifier := transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) return transport.NewTransport(baseTransport, modifier), nil }
func (p *v1Puller) Pull(tag string) (fallback bool, err error) { if utils.DigestReference(tag) { // Allowing fallback, because HTTPS v1 is before HTTP v2 return true, registry.ErrNoSupport{errors.New("Cannot pull by digest with v1 registry")} } tlsConfig, err := p.registryService.TLSConfig(p.repoInfo.Index.Name) if err != nil { return false, err } // Adds Docker-specific headers as well as user-specified headers (metaHeaders) tr := transport.NewTransport( // TODO(tiborvass): was ReceiveTimeout registry.NewTransport(tlsConfig), registry.DockerHeaders(p.config.MetaHeaders)..., ) client := registry.HTTPClient(tr) v1Endpoint, err := p.endpoint.ToV1Endpoint(p.config.MetaHeaders) if err != nil { logrus.Debugf("Could not get v1 endpoint: %v", err) return true, 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 true, err } if err := p.pullRepository(tag); err != nil { // TODO(dmcgowan): Check if should fallback return false, err } return false, nil }
func (p *v1Pusher) Push() (fallback bool, err error) { tlsConfig, err := p.registryService.TLSConfig(p.repoInfo.Index.Name) if err != nil { return false, err } // Adds Docker-specific headers as well as user-specified headers (metaHeaders) tr := transport.NewTransport( // TODO(tiborvass): was NoTimeout registry.NewTransport(tlsConfig), registry.DockerHeaders(p.config.MetaHeaders)..., ) client := registry.HTTPClient(tr) v1Endpoint, err := p.endpoint.ToV1Endpoint(p.config.MetaHeaders) if err != nil { logrus.Debugf("Could not get v1 endpoint: %v", err) return true, err } p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint) if err != nil { // TODO(dmcgowan): Check if should fallback return true, err } if err := p.pushRepository(p.config.Tag); err != nil { // TODO(dmcgowan): Check if should fallback return false, err } return false, nil }
func (r *repositoryRetriever) Repository(ctx gocontext.Context, registry *url.URL, repoName string, insecure bool) (distribution.Repository, error) { src := *registry // ping the registry to get challenge headers if err, ok := r.pings[src]; ok { if err != nil { return nil, err } if redirect, ok := r.redirect[src]; ok { src = *redirect } } else { redirect, err := r.ping(src, insecure) r.pings[src] = err if err != nil { return nil, err } if redirect != nil { r.redirect[src] = redirect src = *redirect } } rt := transport.NewTransport( r.context.Transport, // TODO: slightly smarter authorizer that retries unauthenticated requests auth.NewAuthorizer( r.context.Challenges, auth.NewTokenHandler(r.context.Transport, r.credentials, repoName, "pull"), auth.NewBasicHandler(r.credentials), ), ) return registryclient.NewRepository(context.Context(ctx), repoName, src.String(), rt) }
func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, bool, error) { challengeManager, foundV2, err := PingV2Registry(endpoint, authTransport) if err != nil { if !foundV2 { err = fallbackError{err: err} } return nil, foundV2, err } tokenHandlerOptions := auth.TokenHandlerOptions{ Transport: authTransport, Credentials: creds, OfflineAccess: true, ClientID: AuthClientID, Scopes: scopes, } tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions) basicHandler := auth.NewBasicHandler(creds) modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) tr := transport.NewTransport(authTransport, modifiers...) return &http.Client{ Transport: tr, Timeout: 15 * time.Second, }, foundV2, nil }
func spawnTestRegistrySession(t *testing.T) *Session { authConfig := &cliconfig.AuthConfig{} endpoint, err := NewEndpoint(makeIndex("/v1/"), nil, APIVersionUnknown) if err != nil { t.Fatal(err) } var tr http.RoundTripper = debugTransport{NewTransport(nil), t.Log} tr = transport.NewTransport(AuthTransport(tr, authConfig, false), DockerHeaders(nil)...) client := HTTPClient(tr) r, err := NewSession(client, authConfig, endpoint) if err != nil { t.Fatal(err) } // In a normal scenario for the v1 registry, the client should send a `X-Docker-Token: true` // header while authenticating, in order to retrieve a token that can be later used to // perform authenticated actions. // // The mock v1 registry does not support that, (TODO(tiborvass): support it), instead, // it will consider authenticated any request with the header `X-Docker-Token: fake-token`. // // Because we know that the client's transport is an `*authTransport` we simply cast it, // in order to set the internal cached token to the fake token, and thus send that fake token // upon every subsequent requests. r.client.Transport.(*authTransport).token = token return r }
func tokenAuth(config *viper.Viper, baseTransport *http.Transport, gun string, readOnly bool) http.RoundTripper { // TODO(dmcgowan): add notary specific headers authTransport := transport.NewTransport(baseTransport) pingClient := &http.Client{ Transport: authTransport, Timeout: 5 * time.Second, } trustServerURL := getRemoteTrustServer(config) endpoint, err := url.Parse(trustServerURL) if err != nil { fatalf("Could not parse remote trust server url (%s): %s", trustServerURL, err.Error()) } if endpoint.Scheme == "" { fatalf("Trust server url has to be in the form of http(s)://URL:PORT. Got: %s", trustServerURL) } subPath, err := url.Parse("v2/") if err != nil { fatalf("Failed to parse v2 subpath. This error should not have been reached. Please report it as an issue at https://github.com/docker/notary/issues: %s", err.Error()) } endpoint = endpoint.ResolveReference(subPath) req, err := http.NewRequest("GET", endpoint.String(), nil) if err != nil { fatalf(err.Error()) } resp, err := pingClient.Do(req) if err != nil { fatalf(err.Error()) } defer resp.Body.Close() challengeManager := auth.NewSimpleChallengeManager() if err := challengeManager.AddResponse(resp); err != nil { fatalf(err.Error()) } ps := passwordStore{anonymous: readOnly} tokenHandler := auth.NewTokenHandler(authTransport, ps, gun, "push", "pull") basicHandler := auth.NewBasicHandler(ps) modifier := transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) return transport.NewTransport(baseTransport, modifier) }
func uploadBlobsToRegistry(repostr string, file *os.File, layers []*Layer, manifest *manifest.SignedManifest) error { /* rewind first */ file.Seek(0, 0) archive := tar.NewReader(bufio.NewReader(file)) url, repo := splitUrlAndRepo(repostr) tr := transport.NewTransport(http.DefaultTransport) repository, err := client.NewRepository(context.Background(), repo, "http://"+url, tr) for _, v := range layers { /* probe remote endpoint */ dsc, err := repository.Blobs(context.Background()).Stat(context.Background(), v.BlobSum) switch err { case nil: case distribution.ErrBlobUnknown: default: return err } if err == distribution.ErrBlobUnknown { /* rewind after each seek */ file.Seek(0, 0) bb, err := getLayerRaw(archive, v.Id) if err != nil { return err } if verbose { fmt.Printf("Uploading layer: %q size: %d\n", v.BlobSum, len(bb)) } dsc, err := repository.Blobs(context.Background()).Put(context.Background(), LayerType, bb) if err != nil { return err } if verbose { fmt.Printf(" uploaded with digest: %q\n", dsc.Digest) } } else { if verbose { fmt.Printf("Already in blob store: %q\n", dsc.Digest) } } } manSvc, err := repository.Manifests(context.Background()) if err == nil { return manSvc.Put(manifest) } return err }
func newV1Endpoint(address url.URL, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) (*V1Endpoint, error) { endpoint := &V1Endpoint{ IsSecure: (tlsConfig == nil || !tlsConfig.InsecureSkipVerify), URL: new(url.URL), } *endpoint.URL = address // TODO(tiborvass): make sure a ConnectTimeout transport is used tr := NewTransport(tlsConfig) endpoint.client = HTTPClient(transport.NewTransport(tr, DockerHeaders(userAgent, metaHeaders)...)) return endpoint, nil }
func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named) (distribution.Repository, error) { c := pr.authChallenger tr := transport.NewTransport(http.DefaultTransport, auth.NewAuthorizer(c.challengeManager(), auth.NewTokenHandler(http.DefaultTransport, c.credentialStore(), name.Name(), "pull"))) localRepo, err := pr.embedded.Repository(ctx, name) if err != nil { return nil, err } localManifests, err := localRepo.Manifests(ctx, storage.SkipLayerVerification()) if err != nil { return nil, err } remoteRepo, err := client.NewRepository(ctx, name, pr.remoteURL.String(), tr) if err != nil { return nil, err } remoteManifests, err := remoteRepo.Manifests(ctx) if err != nil { return nil, err } return &proxiedRepository{ blobStore: &proxyBlobStore{ localStore: localRepo.Blobs(ctx), remoteStore: remoteRepo.Blobs(ctx), scheduler: pr.scheduler, repositoryName: name, authChallenger: pr.authChallenger, }, manifests: &proxyManifestStore{ repositoryName: name, localManifests: localManifests, // Options? remoteManifests: remoteManifests, ctx: ctx, scheduler: pr.scheduler, authChallenger: pr.authChallenger, }, name: name, tags: &proxyTagService{ localTags: localRepo.Tags(ctx), remoteTags: remoteRepo.Tags(ctx), authChallenger: pr.authChallenger, }, }, nil }
func (r *repositoryRetriever) Repository(ctx gocontext.Context, registry *url.URL, repoName string, insecure bool) (distribution.Repository, error) { named, err := reference.ParseNamed(repoName) if err != nil { return nil, err } t := r.context.Transport if insecure && r.context.InsecureTransport != nil { t = r.context.InsecureTransport } src := *registry // ping the registry to get challenge headers if err, ok := r.pings[src]; ok { if err != nil { return nil, err } if redirect, ok := r.redirect[src]; ok { src = *redirect } } else { redirect, err := r.ping(src, insecure, t) r.pings[src] = err if err != nil { return nil, err } if redirect != nil { r.redirect[src] = redirect src = *redirect } } rt := transport.NewTransport( t, // TODO: slightly smarter authorizer that retries unauthenticated requests // TODO: make multiple attempts if the first credential fails auth.NewAuthorizer( r.context.Challenges, auth.NewTokenHandler(t, r.credentials, repoName, "pull"), auth.NewBasicHandler(r.credentials), ), ) repo, err := registryclient.NewRepository(context.Context(ctx), named, src.String(), rt) if err != nil { return nil, err } return NewRetryRepository(repo, 2, 3/2*time.Second), nil }
// loginV2 tries to login to the v2 registry server. The given registry // endpoint will be pinged to get authorization challenges. These challenges // will be used to authenticate against the registry to validate credentials. func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent string) (string, string, error) { logrus.Debugf("attempting v2 login to registry endpoint %s", strings.TrimRight(endpoint.URL.String(), "/")+"/v2/") modifiers := DockerHeaders(userAgent, nil) authTransport := transport.NewTransport(NewTransport(endpoint.TLSConfig), modifiers...) credentialAuthConfig := *authConfig creds := loginCredentialStore{ authConfig: &credentialAuthConfig, } loginClient, foundV2, err := v2AuthHTTPClient(endpoint.URL, authTransport, modifiers, creds, nil) if err != nil { return "", "", err } endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/" req, err := http.NewRequest("GET", endpointStr, nil) if err != nil { if !foundV2 { err = fallbackError{err: err} } return "", "", err } resp, err := loginClient.Do(req) if err != nil { if !foundV2 { err = fallbackError{err: err} } return "", "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { // TODO(dmcgowan): Attempt to further interpret result, status code and error code string err := fmt.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode)) if !foundV2 { err = fallbackError{err: err} } return "", "", err } return "Login Succeeded", credentialAuthConfig.IdentityToken, nil }
func TestEndpointAuthorizeBasic(t *testing.T) { m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{ { Request: testutil.Request{ Method: "GET", Route: "/v2/hello", }, Response: testutil.Response{ StatusCode: http.StatusAccepted, }, }, }) username := "******" password := "******" authenicate := fmt.Sprintf("Basic realm=localhost") validCheck := func(a string) bool { return a == fmt.Sprintf("Basic %s", basicAuth(username, password)) } e, c := testServerWithAuth(m, authenicate, validCheck) defer c() creds := &testCredentialStore{ username: username, password: password, } challengeManager := NewSimpleChallengeManager() _, err := ping(challengeManager, e+"/v2/", "") if err != nil { t.Fatal(err) } transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, NewBasicHandler(creds))) client := &http.Client{Transport: transport1} req, _ := http.NewRequest("GET", e+"/v2/hello", nil) resp, err := client.Do(req) if err != nil { t.Fatalf("Error sending get request: %s", err) } if resp.StatusCode != http.StatusAccepted { t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted) } }
func (pr *proxyingRegistry) Repository(ctx context.Context, name string) (distribution.Repository, error) { tr := transport.NewTransport(http.DefaultTransport, auth.NewAuthorizer(pr.challengeManager, auth.NewTokenHandler(http.DefaultTransport, pr.credentialStore, name, "pull"))) localRepo, err := pr.embedded.Repository(ctx, name) if err != nil { return nil, err } localManifests, err := localRepo.Manifests(ctx, storage.SkipLayerVerification) if err != nil { return nil, err } remoteRepo, err := client.NewRepository(ctx, name, pr.remoteURL, tr) if err != nil { return nil, err } remoteManifests, err := remoteRepo.Manifests(ctx) if err != nil { return nil, err } return &proxiedRepository{ blobStore: &proxyBlobStore{ localStore: localRepo.Blobs(ctx), remoteStore: remoteRepo.Blobs(ctx), scheduler: pr.scheduler, }, manifests: proxyManifestStore{ repositoryName: name, localManifests: localManifests, // Options? remoteManifests: remoteManifests, ctx: ctx, scheduler: pr.scheduler, }, name: name, signatures: localRepo.Signatures(), }, nil }
func (p *v1Puller) Pull(ref reference.Named) (fallback bool, err error) { if _, isDigested := ref.(reference.Digested); isDigested { // Allowing fallback, because HTTPS v1 is before HTTP v2 return true, registry.ErrNoSupport{errors.New("Cannot pull by digest with v1 registry")} } tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name) if err != nil { return false, err } // Adds Docker-specific headers as well as user-specified headers (metaHeaders) tr := transport.NewTransport( // TODO(tiborvass): was ReceiveTimeout registry.NewTransport(tlsConfig), registry.DockerHeaders(p.config.MetaHeaders)..., ) client := registry.HTTPClient(tr) v1Endpoint, err := p.endpoint.ToV1Endpoint(p.config.MetaHeaders) if err != nil { logrus.Debugf("Could not get v1 endpoint: %v", err) return true, 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 true, err } if err := p.pullRepository(ref); err != nil { // TODO(dmcgowan): Check if should fallback return false, err } out := p.config.OutStream out.Write(p.sf.FormatStatus("", "%s: this image was pulled from a legacy registry. Important: This registry version will not be supported in future versions of docker.", p.repoInfo.CanonicalName.Name())) return false, 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 newEndpoint(address string, tlsConfig *tls.Config, metaHeaders http.Header) (*Endpoint, error) { var ( endpoint = new(Endpoint) trimmedAddress string err error ) if !strings.HasPrefix(address, "http") { address = "https://" + address } endpoint.IsSecure = (tlsConfig == nil || !tlsConfig.InsecureSkipVerify) trimmedAddress, endpoint.Version = scanForAPIVersion(address) if endpoint.URL, err = url.Parse(trimmedAddress); err != nil { return nil, err } // TODO(tiborvass): make sure a ConnectTimeout transport is used tr := NewTransport(tlsConfig) endpoint.client = HTTPClient(transport.NewTransport(tr, DockerHeaders(metaHeaders)...)) return endpoint, 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 }
// 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(repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, metaHeaders http.Header, authConfig *cliconfig.AuthConfig, actions ...string) (distribution.Repository, error) { ctx := context.Background() repoName := repoInfo.CanonicalName // 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(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, err } resp, err := pingClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() versions := auth.APIVersions(resp, endpoint.VersionHeader) if endpoint.VersionHeader != "" && len(endpoint.Versions) > 0 { var foundVersion bool for _, version := range endpoint.Versions { for _, pingVersion := range versions { if version == pingVersion { foundVersion = true } } } if !foundVersion { return nil, errors.New("endpoint does not support v2 API") } } challengeManager := auth.NewSimpleChallengeManager() if err := challengeManager.AddResponse(resp); err != nil { return nil, err } 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...) return client.NewRepository(ctx, repoName, endpoint.URL, tr) }
// 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 }
// New constructs a new Driver with the given AWS credentials, region, encryption flag, and // bucketName func New(params DriverParameters) (*Driver, error) { auth, err := aws.GetAuth(params.AccessKey, params.SecretKey, "", time.Time{}) if err != nil { return nil, fmt.Errorf("unable to resolve aws credentials, please ensure that 'accesskey' and 'secretkey' are properly set or the credentials are available in $HOME/.aws/credentials: %v", err) } if !params.Secure { params.Region.S3Endpoint = strings.Replace(params.Region.S3Endpoint, "https", "http", 1) } s3obj := s3.New(auth, params.Region) if params.UserAgent != "" { s3obj.Client = &http.Client{ Transport: transport.NewTransport(http.DefaultTransport, transport.NewHeaderRequestModifier(http.Header{ http.CanonicalHeaderKey("User-Agent"): []string{params.UserAgent}, }), ), } } if params.V4Auth { s3obj.Signature = aws.V4Signature } else { if params.Region.Name == "eu-central-1" { return nil, fmt.Errorf("The eu-central-1 region only works with v4 authentication") } } bucket := s3obj.Bucket(params.Bucket) // TODO Currently multipart uploads have no timestamps, so this would be unwise // if you initiated a new s3driver while another one is running on the same bucket. // multis, _, err := bucket.ListMulti("", "") // if err != nil { // return nil, err // } // for _, multi := range multis { // err := multi.Abort() // //TODO appropriate to do this error checking? // if err != nil { // return nil, err // } // } d := &driver{ S3: s3obj, Bucket: bucket, ChunkSize: params.ChunkSize, Encrypt: params.Encrypt, RootDirectory: params.RootDirectory, StorageClass: params.StorageClass, } return &Driver{ baseEmbed: baseEmbed{ Base: base.Base{ StorageDriver: d, }, }, }, nil }
func TestEndpointAuthorizeTokenBasicWithExpiresInAndIssuedAt(t *testing.T) { service := "localhost.localdomain" repo := "some/fun/registry" scope := fmt.Sprintf("repository:%s:pull,push", repo) username := "******" password := "******" // This test sets things up such that the token was issued one increment // earlier than its sibling in TestEndpointAuthorizeTokenBasicWithExpiresIn. // This will mean that the token expires after 3 increments instead of 4. clock := &fakeClock{current: time.Now()} timeIncrement := 1000 * time.Second firstIssuedAt := clock.Now() clock.current = clock.current.Add(timeIncrement) secondIssuedAt := clock.current.Add(2 * timeIncrement) tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{ { Request: testutil.Request{ Method: "GET", Route: fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service), }, Response: testutil.Response{ StatusCode: http.StatusOK, Body: []byte(`{"token":"statictoken", "issued_at": "` + firstIssuedAt.Format(time.RFC3339Nano) + `", "expires_in": 3001}`), }, }, { Request: testutil.Request{ Method: "GET", Route: fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service), }, Response: testutil.Response{ StatusCode: http.StatusOK, Body: []byte(`{"access_token":"statictoken", "issued_at": "` + secondIssuedAt.Format(time.RFC3339Nano) + `", "expires_in": 3001}`), }, }, }) authenicate1 := fmt.Sprintf("Basic realm=localhost") tokenExchanges := 0 basicCheck := func(a string) bool { tokenExchanges = tokenExchanges + 1 return a == fmt.Sprintf("Basic %s", basicAuth(username, password)) } te, tc := testServerWithAuth(tokenMap, authenicate1, basicCheck) defer tc() m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{ { Request: testutil.Request{ Method: "GET", Route: "/v2/hello", }, Response: testutil.Response{ StatusCode: http.StatusAccepted, }, }, { Request: testutil.Request{ Method: "GET", Route: "/v2/hello", }, Response: testutil.Response{ StatusCode: http.StatusAccepted, }, }, { Request: testutil.Request{ Method: "GET", Route: "/v2/hello", }, Response: testutil.Response{ StatusCode: http.StatusAccepted, }, }, { Request: testutil.Request{ Method: "GET", Route: "/v2/hello", }, Response: testutil.Response{ StatusCode: http.StatusAccepted, }, }, }) authenicate2 := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service) bearerCheck := func(a string) bool { return a == "Bearer statictoken" } e, c := testServerWithAuth(m, authenicate2, bearerCheck) defer c() creds := &testCredentialStore{ username: username, password: password, } challengeManager := NewSimpleChallengeManager() _, err := ping(challengeManager, e+"/v2/", "") if err != nil { t.Fatal(err) } options := TokenHandlerOptions{ Transport: nil, Credentials: creds, Scopes: []Scope{ RepositoryScope{ Repository: repo, Actions: []string{"pull", "push"}, }, }, } tHandler := NewTokenHandlerWithOptions(options) tHandler.(*tokenHandler).clock = clock transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, tHandler, NewBasicHandler(creds))) client := &http.Client{Transport: transport1} // First call should result in a token exchange // Subsequent calls should recycle the token from the first request, until the expiration has lapsed. // We shaved one increment off of the equivalent logic in TestEndpointAuthorizeTokenBasicWithExpiresIn // so this loop should have one fewer iteration. for i := 0; i < 3; i++ { req, _ := http.NewRequest("GET", e+"/v2/hello", nil) resp, err := client.Do(req) if err != nil { t.Fatalf("Error sending get request: %s", err) } if resp.StatusCode != http.StatusAccepted { t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted) } if tokenExchanges != 1 { t.Fatalf("Unexpected number of token exchanges, want: 1, got %d (iteration: %d)", tokenExchanges, i) } clock.current = clock.current.Add(timeIncrement) } // After we've exceeded the expiration, we should see a second token exchange. req, _ := http.NewRequest("GET", e+"/v2/hello", nil) resp, err := client.Do(req) if err != nil { t.Fatalf("Error sending get request: %s", err) } if resp.StatusCode != http.StatusAccepted { t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted) } if tokenExchanges != 2 { t.Fatalf("Unexpected number of token exchanges, want: 2, got %d", tokenExchanges) } }
// 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 TestEndpointAuthorizeRefreshToken(t *testing.T) { service := "localhost.localdomain" repo1 := "some/registry" repo2 := "other/registry" scope1 := fmt.Sprintf("repository:%s:pull,push", repo1) scope2 := fmt.Sprintf("repository:%s:pull,push", repo2) refreshToken1 := "0123456790abcdef" refreshToken2 := "0123456790fedcba" tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{ { Request: testutil.Request{ Method: "POST", Route: "/token", Body: []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken1, url.QueryEscape(scope1), service)), }, Response: testutil.Response{ StatusCode: http.StatusOK, Body: []byte(fmt.Sprintf(`{"access_token":"statictoken","refresh_token":"%s"}`, refreshToken1)), }, }, { // In the future this test may fail and require using basic auth to get a different refresh token Request: testutil.Request{ Method: "POST", Route: "/token", Body: []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken1, url.QueryEscape(scope2), service)), }, Response: testutil.Response{ StatusCode: http.StatusOK, Body: []byte(fmt.Sprintf(`{"access_token":"statictoken","refresh_token":"%s"}`, refreshToken2)), }, }, { Request: testutil.Request{ Method: "POST", Route: "/token", Body: []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken2, url.QueryEscape(scope2), service)), }, Response: testutil.Response{ StatusCode: http.StatusOK, Body: []byte(`{"access_token":"badtoken","refresh_token":"%s"}`), }, }, }) te, tc := testServer(tokenMap) defer tc() m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{ { Request: testutil.Request{ Method: "GET", Route: "/v2/hello", }, Response: testutil.Response{ StatusCode: http.StatusAccepted, }, }, }) authenicate := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service) validCheck := func(a string) bool { return a == "Bearer statictoken" } e, c := testServerWithAuth(m, authenicate, validCheck) defer c() challengeManager1 := NewSimpleChallengeManager() versions, err := ping(challengeManager1, e+"/v2/", "x-api-version") if err != nil { t.Fatal(err) } if len(versions) != 1 { t.Fatalf("Unexpected version count: %d, expected 1", len(versions)) } if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check { t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check) } creds := &testCredentialStore{ refreshTokens: map[string]string{ service: refreshToken1, }, } transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager1, NewTokenHandler(nil, creds, repo1, "pull", "push"))) client := &http.Client{Transport: transport1} req, _ := http.NewRequest("GET", e+"/v2/hello", nil) resp, err := client.Do(req) if err != nil { t.Fatalf("Error sending get request: %s", err) } if resp.StatusCode != http.StatusAccepted { t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted) } // Try with refresh token setting e2, c2 := testServerWithAuth(m, authenicate, validCheck) defer c2() challengeManager2 := NewSimpleChallengeManager() versions, err = ping(challengeManager2, e2+"/v2/", "x-api-version") if err != nil { t.Fatal(err) } if len(versions) != 1 { t.Fatalf("Unexpected version count: %d, expected 1", len(versions)) } if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check { t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check) } transport2 := transport.NewTransport(nil, NewAuthorizer(challengeManager2, NewTokenHandler(nil, creds, repo2, "pull", "push"))) client2 := &http.Client{Transport: transport2} req, _ = http.NewRequest("GET", e2+"/v2/hello", nil) resp, err = client2.Do(req) if err != nil { t.Fatalf("Error sending get request: %s", err) } if resp.StatusCode != http.StatusAccepted { t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusUnauthorized) } if creds.refreshTokens[service] != refreshToken2 { t.Fatalf("Refresh token not set after change") } // Try with bad token e3, c3 := testServerWithAuth(m, authenicate, validCheck) defer c3() challengeManager3 := NewSimpleChallengeManager() versions, err = ping(challengeManager3, e3+"/v2/", "x-api-version") if err != nil { t.Fatal(err) } if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check { t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check) } transport3 := transport.NewTransport(nil, NewAuthorizer(challengeManager3, NewTokenHandler(nil, creds, repo2, "pull", "push"))) client3 := &http.Client{Transport: transport3} req, _ = http.NewRequest("GET", e3+"/v2/hello", nil) resp, err = client3.Do(req) if err != nil { t.Fatalf("Error sending get request: %s", err) } if resp.StatusCode != http.StatusUnauthorized { t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusUnauthorized) } }
func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig cliconfig.AuthConfig) (*client.NotaryRepository, error) { server := trustServer(repoInfo.Index) if !strings.HasPrefix(server, "https://") { return nil, errors.New("unsupported scheme: https required for trust server") } 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(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 } resp, err := pingClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() challengeManager := auth.NewSimpleChallengeManager() if err := challengeManager.AddResponse(resp); err != nil { return nil, err } creds := simpleCredentialStore{auth: authConfig} tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.CanonicalName, "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.CanonicalName, server, tr, cli.getPassphraseRetriever()) }
func TestEndpointAuthorizeV2RefreshToken(t *testing.T) { service := "localhost.localdomain" scope1 := "registry:catalog:search" refreshToken1 := "0123456790abcdef" tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{ { Request: testutil.Request{ Method: "POST", Route: "/token", Body: []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken1, url.QueryEscape(scope1), service)), }, Response: testutil.Response{ StatusCode: http.StatusOK, Body: []byte(fmt.Sprintf(`{"access_token":"statictoken","refresh_token":"%s"}`, refreshToken1)), }, }, }) te, tc := testServer(tokenMap) defer tc() m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{ { Request: testutil.Request{ Method: "GET", Route: "/v1/search", }, Response: testutil.Response{ StatusCode: http.StatusAccepted, }, }, }) authenicate := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service) validCheck := func(a string) bool { return a == "Bearer statictoken" } e, c := testServerWithAuth(m, authenicate, validCheck) defer c() challengeManager1 := NewSimpleChallengeManager() versions, err := ping(challengeManager1, e+"/v2/", "x-api-version") if err != nil { t.Fatal(err) } if len(versions) != 1 { t.Fatalf("Unexpected version count: %d, expected 1", len(versions)) } if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check { t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check) } tho := TokenHandlerOptions{ Credentials: &testCredentialStore{ refreshTokens: map[string]string{ service: refreshToken1, }, }, Scopes: []Scope{ RegistryScope{ Name: "catalog", Actions: []string{"search"}, }, }, } transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager1, NewTokenHandlerWithOptions(tho))) client := &http.Client{Transport: transport1} req, _ := http.NewRequest("GET", e+"/v1/search", nil) resp, err := client.Do(req) if err != nil { t.Fatalf("Error sending get request: %s", err) } if resp.StatusCode != http.StatusAccepted { t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted) } }
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()) }
// New constructs a new Driver with the given AWS credentials, region, encryption flag, and // bucketName func New(params DriverParameters) (*Driver, error) { if !params.V4Auth && (params.RegionEndpoint == "" || strings.Contains(params.RegionEndpoint, "s3.amazonaws.com")) { return nil, fmt.Errorf("On Amazon S3 this storage driver can only be used with v4 authentication") } awsConfig := aws.NewConfig() var creds *credentials.Credentials if params.RegionEndpoint == "" { creds = credentials.NewChainCredentials([]credentials.Provider{ &credentials.StaticProvider{ Value: credentials.Value{ AccessKeyID: params.AccessKey, SecretAccessKey: params.SecretKey, }, }, &credentials.EnvProvider{}, &credentials.SharedCredentialsProvider{}, &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(session.New())}, }) } else { creds = credentials.NewChainCredentials([]credentials.Provider{ &credentials.StaticProvider{ Value: credentials.Value{ AccessKeyID: params.AccessKey, SecretAccessKey: params.SecretKey, }, }, &credentials.EnvProvider{}, }) awsConfig.WithS3ForcePathStyle(true) awsConfig.WithEndpoint(params.RegionEndpoint) } awsConfig.WithCredentials(creds) awsConfig.WithRegion(params.Region) awsConfig.WithDisableSSL(!params.Secure) if params.UserAgent != "" { awsConfig.WithHTTPClient(&http.Client{ Transport: transport.NewTransport(http.DefaultTransport, transport.NewHeaderRequestModifier(http.Header{http.CanonicalHeaderKey("User-Agent"): []string{params.UserAgent}})), }) } s3obj := s3.New(session.New(awsConfig)) // enable S3 compatible signature v2 signing instead if !params.V4Auth { setv2Handlers(s3obj) } // TODO Currently multipart uploads have no timestamps, so this would be unwise // if you initiated a new s3driver while another one is running on the same bucket. // multis, _, err := bucket.ListMulti("", "") // if err != nil { // return nil, err // } // for _, multi := range multis { // err := multi.Abort() // //TODO appropriate to do this error checking? // if err != nil { // return nil, err // } // } d := &driver{ S3: s3obj, Bucket: params.Bucket, ChunkSize: params.ChunkSize, Encrypt: params.Encrypt, KeyID: params.KeyID, MultipartCopyChunkSize: params.MultipartCopyChunkSize, MultipartCopyMaxConcurrency: params.MultipartCopyMaxConcurrency, MultipartCopyThresholdSize: params.MultipartCopyThresholdSize, RootDirectory: params.RootDirectory, StorageClass: params.StorageClass, ObjectAcl: params.ObjectAcl, } return &Driver{ baseEmbed: baseEmbed{ Base: base.Base{ StorageDriver: d, }, }, }, nil }
func TestEndpointAuthorizeTokenBasic(t *testing.T) { service := "localhost.localdomain" repo := "some/fun/registry" scope := fmt.Sprintf("repository:%s:pull,push", repo) username := "******" password := "******" tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{ { Request: testutil.Request{ Method: "GET", Route: fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service), }, Response: testutil.Response{ StatusCode: http.StatusOK, Body: []byte(`{"access_token":"statictoken"}`), }, }, }) authenicate1 := fmt.Sprintf("Basic realm=localhost") basicCheck := func(a string) bool { return a == fmt.Sprintf("Basic %s", basicAuth(username, password)) } te, tc := testServerWithAuth(tokenMap, authenicate1, basicCheck) defer tc() m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{ { Request: testutil.Request{ Method: "GET", Route: "/v2/hello", }, Response: testutil.Response{ StatusCode: http.StatusAccepted, }, }, }) authenicate2 := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service) bearerCheck := func(a string) bool { return a == "Bearer statictoken" } e, c := testServerWithAuth(m, authenicate2, bearerCheck) defer c() creds := &testCredentialStore{ username: username, password: password, } challengeManager := NewSimpleChallengeManager() _, err := ping(challengeManager, e+"/v2/", "") if err != nil { t.Fatal(err) } transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, NewTokenHandler(nil, creds, repo, "pull", "push"), NewBasicHandler(creds))) client := &http.Client{Transport: transport1} req, _ := http.NewRequest("GET", e+"/v2/hello", nil) resp, err := client.Do(req) if err != nil { t.Fatalf("Error sending get request: %s", err) } if resp.StatusCode != http.StatusAccepted { t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted) } }