diff --git a/docker/docker_image_src.go b/docker/docker_image_src.go index f2e9eb17be..15856aa4f6 100644 --- a/docker/docker_image_src.go +++ b/docker/docker_image_src.go @@ -236,22 +236,27 @@ func (s *dockerImageSource) ensureManifestIsLoaded(ctx context.Context) error { return nil } -func (s *dockerImageSource) getExternalBlob(ctx context.Context, urls []string) (io.ReadCloser, int64, error) { +func (s *dockerImageSource) getExternalBlob(ctx context.Context, urls []string) (_ io.ReadCloser, _ int64, fallback bool, _ error) { var ( resp *http.Response err error ) if len(urls) == 0 { - return nil, 0, errors.New("internal error: getExternalBlob called with no URLs") + return nil, 0, true, errors.New("internal error: getExternalBlob called with no URLs") } - for _, url := range urls { + noSupportedURL := true + for _, u := range urls { + if u, err := url.Parse(u); err != nil || (u.Scheme != "http" && u.Scheme != "https") { + continue // unsupported url. skip this url. + } + noSupportedURL = false // NOTE: we must not authenticate on additional URLs as those // can be abused to leak credentials or tokens. Please // refer to CVE-2020-15157 for more information. - resp, err = s.c.makeRequestToResolvedURL(ctx, http.MethodGet, url, nil, nil, -1, noAuth, nil) + resp, err = s.c.makeRequestToResolvedURL(ctx, http.MethodGet, u, nil, nil, -1, noAuth, nil) if err == nil { if resp.StatusCode != http.StatusOK { - err = errors.Errorf("error fetching external blob from %q: %d (%s)", url, resp.StatusCode, http.StatusText(resp.StatusCode)) + err = errors.Errorf("error fetching external blob from %q: %d (%s)", u, resp.StatusCode, http.StatusText(resp.StatusCode)) logrus.Debug(err) resp.Body.Close() continue @@ -259,10 +264,13 @@ func (s *dockerImageSource) getExternalBlob(ctx context.Context, urls []string) break } } + if noSupportedURL { + return nil, 0, true, errors.New("no supported url is specified") + } if err != nil { - return nil, 0, err + return nil, 0, false, err } - return resp.Body, getBlobSize(resp), nil + return resp.Body, getBlobSize(resp), false, nil } func getBlobSize(resp *http.Response) int64 { @@ -408,7 +416,12 @@ func (s *dockerImageSource) GetBlobAt(ctx context.Context, info types.BlobInfo, // May update BlobInfoCache, preferably after it knows for certain that a blob truly exists at a specific location. func (s *dockerImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache) (io.ReadCloser, int64, error) { if len(info.URLs) != 0 { - return s.getExternalBlob(ctx, info.URLs) + r, s, fallback, err := s.getExternalBlob(ctx, info.URLs) + if err == nil { + return r, s, nil + } else if !fallback { + return nil, 0, err + } } path := fmt.Sprintf(blobsPath, reference.Path(s.physicalRef.ref), info.Digest.String()) diff --git a/oci/layout/oci_src.go b/oci/layout/oci_src.go index 55d3f637a2..258112a188 100644 --- a/oci/layout/oci_src.go +++ b/oci/layout/oci_src.go @@ -5,6 +5,7 @@ import ( "io" "io/ioutil" "net/http" + "net/url" "os" "strconv" @@ -113,7 +114,12 @@ func (s *ociImageSource) HasThreadSafeGetBlob() bool { // May update BlobInfoCache, preferably after it knows for certain that a blob truly exists at a specific location. func (s *ociImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache) (io.ReadCloser, int64, error) { if len(info.URLs) != 0 { - return s.getExternalBlob(ctx, info.URLs) + r, s, fallback, err := s.getExternalBlob(ctx, info.URLs) + if err == nil { + return r, s, nil + } else if !fallback { + return nil, 0, err + } } path, err := s.ref.blobPath(info.Digest, s.sharedBlobDir) @@ -140,36 +146,43 @@ func (s *ociImageSource) GetSignatures(ctx context.Context, instanceDigest *dige return [][]byte{}, nil } -func (s *ociImageSource) getExternalBlob(ctx context.Context, urls []string) (io.ReadCloser, int64, error) { +func (s *ociImageSource) getExternalBlob(ctx context.Context, urls []string) (_ io.ReadCloser, _ int64, fallback bool, _ error) { if len(urls) == 0 { - return nil, 0, errors.New("internal error: getExternalBlob called with no URLs") + return nil, 0, true, errors.New("internal error: getExternalBlob called with no URLs") } errWrap := errors.New("failed fetching external blob from all urls") - for _, url := range urls { - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + noSupportedURL := true + for _, u := range urls { + if u, err := url.Parse(u); err != nil || (u.Scheme != "http" && u.Scheme != "https") { + continue // unsupported url. skip this url. + } + noSupportedURL = false + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { - errWrap = errors.Wrapf(errWrap, "fetching %s failed %s", url, err.Error()) + errWrap = errors.Wrapf(errWrap, "fetching %s failed %s", u, err.Error()) continue } resp, err := s.client.Do(req) if err != nil { - errWrap = errors.Wrapf(errWrap, "fetching %s failed %s", url, err.Error()) + errWrap = errors.Wrapf(errWrap, "fetching %s failed %s", u, err.Error()) continue } if resp.StatusCode != http.StatusOK { resp.Body.Close() - errWrap = errors.Wrapf(errWrap, "fetching %s failed, response code not 200", url) + errWrap = errors.Wrapf(errWrap, "fetching %s failed, response code not 200", u) continue } - return resp.Body, getBlobSize(resp), nil + return resp.Body, getBlobSize(resp), false, nil + } + if noSupportedURL { + return nil, 0, true, errors.New("no supported url is specified") } - return nil, 0, errWrap + return nil, 0, false, errWrap } // LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer