Beispiel #1
0
func (rb *RepositoryBackend) getAncestryV1(imgID, registry string, repoData *RepoData) ([]string, error) {
	client := util.GetTLSClient(rb.insecure.SkipVerify)
	req, err := http.NewRequest("GET", rb.schema+path.Join(registry, "images", imgID, "ancestry"), nil)
	if err != nil {
		return nil, err
	}

	setAuthTokenV1(req, repoData.Tokens)
	setCookieV1(req, repoData.Cookie)
	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	if res.StatusCode != 200 {
		return nil, &httpStatusErr{res.StatusCode, req.URL}
	}

	var ancestry []string

	j, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, fmt.Errorf("Failed to read downloaded json: %s (%s)", err, j)
	}

	if err := json.Unmarshal(j, &ancestry); err != nil {
		return nil, fmt.Errorf("error unmarshaling: %v", err)
	}

	return ancestry, nil
}
Beispiel #2
0
func (rb *RepositoryBackend) getJsonV1(imgID, registry string, repoData *RepoData) ([]byte, int64, error) {
	client := util.GetTLSClient(rb.insecure.SkipVerify)
	req, err := http.NewRequest("GET", rb.schema+path.Join(registry, "images", imgID, "json"), nil)
	if err != nil {
		return nil, -1, err
	}
	setAuthTokenV1(req, repoData.Tokens)
	setCookieV1(req, repoData.Cookie)
	res, err := client.Do(req)
	if err != nil {
		return nil, -1, err
	}
	defer res.Body.Close()

	if res.StatusCode != 200 {
		return nil, -1, &httpStatusErr{res.StatusCode, req.URL}
	}

	imageSize := int64(-1)

	if hdr := res.Header.Get("X-Docker-Size"); hdr != "" {
		imageSize, err = strconv.ParseInt(hdr, 10, 64)
		if err != nil {
			return nil, -1, err
		}
	}

	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, -1, fmt.Errorf("failed to read downloaded json: %v (%s)", err, b)
	}

	return b, imageSize, nil
}
Beispiel #3
0
func (rb *RepositoryBackend) getRepoDataV1(indexURL string, remote string) (*RepoData, error) {
	client := util.GetTLSClient(rb.insecure.SkipVerify)
	repositoryURL := rb.schema + path.Join(indexURL, "v1", "repositories", remote, "images")

	req, err := http.NewRequest("GET", repositoryURL, nil)
	if err != nil {
		return nil, err
	}

	if rb.username != "" && rb.password != "" {
		req.SetBasicAuth(rb.username, rb.password)
	}

	req.Header.Set("X-Docker-Token", "true")

	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	if res.StatusCode != 200 {
		return nil, &httpStatusErr{res.StatusCode, req.URL}
	}

	var tokens []string
	if res.Header.Get("X-Docker-Token") != "" {
		tokens = res.Header["X-Docker-Token"]
	}

	var cookies []string
	if res.Header.Get("Set-Cookie") != "" {
		cookies = res.Header["Set-Cookie"]
	}

	var endpoints []string
	if res.Header.Get("X-Docker-Endpoints") != "" {
		endpoints = makeEndpointsListV1(res.Header["X-Docker-Endpoints"])
	} else {
		// Assume same endpoint
		endpoints = append(endpoints, indexURL)
	}

	return &RepoData{
		Endpoints: endpoints,
		Tokens:    tokens,
		Cookie:    cookies,
	}, nil
}
Beispiel #4
0
func (rb *RepositoryBackend) supportsRegistry(indexURL string, version registryVersion) (schema string, ok bool, err error) {
	var URLPath string
	switch version {
	case registryV1:
		// the v1 API defines this URL to check if the registry's status
		URLPath = "v1/_ping"
	case registryV2:
		URLPath = "v2"
	}
	URLStr := path.Join(indexURL, URLPath)

	fetch := func(schema string) (res *http.Response, err error) {
		url := schema + "://" + URLStr
		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			return nil, err
		}

		rb.setBasicAuth(req)

		client := util.GetTLSClient(rb.insecure)
		res, err = client.Do(req)
		return
	}

	schema = "https"
	res, err := fetch(schema)
	if err == nil {
		ok, err = checkRegistryStatus(res.StatusCode, res.Header, version)
		defer res.Body.Close()
	}
	if err != nil || !ok {
		if rb.insecure {
			schema = "http"
			res, err = fetch(schema)
			if err == nil {
				ok, err = checkRegistryStatus(res.StatusCode, res.Header, version)
				defer res.Body.Close()
			}
		}
		return schema, ok, err
	}

	return schema, ok, err
}
Beispiel #5
0
func (rb *RepositoryBackend) supportsRegistry(indexURL string, version registryVersion) (schema string, ok bool, err error) {
	var URLPath string
	switch version {
	case registryV1:
		URLPath = "v1/_ping"
	case registryV2:
		URLPath = "v2/"
	}

	fetch := func(schema string) (res *http.Response, err error) {
		u := url.URL{Scheme: schema, Host: indexURL, Path: URLPath}
		req, err := http.NewRequest("GET", u.String(), nil)
		if err != nil {
			return nil, err
		}

		rb.setBasicAuth(req)

		client := util.GetTLSClient(rb.insecure.SkipVerify)
		res, err = client.Do(req)
		return
	}

	schema = "https"
	res, err := fetch(schema)
	if err == nil {
		ok, err = checkRegistryStatus(res.StatusCode, res.Header, version)
		defer res.Body.Close()
	}
	if err != nil || !ok {
		if rb.insecure.AllowHTTP {
			schema = "http"
			res, err = fetch(schema)
			if err == nil {
				ok, err = checkRegistryStatus(res.StatusCode, res.Header, version)
				defer res.Body.Close()
			}
		}
		return schema, ok, err
	}

	return schema, ok, err
}
Beispiel #6
0
func (rb *RepositoryBackend) getImageIDFromTagV1(registry string, appName string, tag string, repoData *RepoData) (string, error) {
	client := util.GetTLSClient(rb.insecure.SkipVerify)
	// we get all the tags instead of directly getting the imageID of the
	// requested one (.../tags/TAG) because even though it's specified in the
	// Docker API, some registries (e.g. Google Container Registry) don't
	// implement it.
	req, err := http.NewRequest("GET", rb.schema+path.Join(registry, "repositories", appName, "tags"), nil)
	if err != nil {
		return "", fmt.Errorf("failed to get Image ID: %s, URL: %s", err, req.URL)
	}

	setAuthTokenV1(req, repoData.Tokens)
	setCookieV1(req, repoData.Cookie)
	res, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("failed to get Image ID: %s, URL: %s", err, req.URL)
	}
	defer res.Body.Close()

	if res.StatusCode != 200 {
		return "", &httpStatusErr{res.StatusCode, req.URL}
	}

	j, err := ioutil.ReadAll(res.Body)

	if err != nil {
		return "", err
	}

	var tags map[string]string

	if err := json.Unmarshal(j, &tags); err != nil {
		return "", fmt.Errorf("error unmarshaling: %v", err)
	}

	imageID, ok := tags[tag]
	if !ok {
		return "", fmt.Errorf("tag %s not found", tag)
	}

	return imageID, nil
}
Beispiel #7
0
func (rb *RepositoryBackend) getLayerV1(imgID, registry string, repoData *RepoData, imgSize int64, tmpDir string) (*os.File, error) {
	client := util.GetTLSClient(rb.insecure.SkipVerify)
	req, err := http.NewRequest("GET", rb.schema+path.Join(registry, "images", imgID, "layer"), nil)
	if err != nil {
		return nil, err
	}

	setAuthTokenV1(req, repoData.Tokens)
	setCookieV1(req, repoData.Cookie)

	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	if res.StatusCode != 200 {
		res.Body.Close()
		return nil, &httpStatusErr{res.StatusCode, req.URL}
	}

	// if we didn't receive the size via X-Docker-Size when we retrieved the
	// layer's json, try Content-Length
	if imgSize == -1 {
		if hdr := res.Header.Get("Content-Length"); hdr != "" {
			imgSize, err = strconv.ParseInt(hdr, 10, 64)
			if err != nil {
				return nil, err
			}
		}
	}

	prefix := "Downloading " + imgID[:12]
	fmtBytesSize := 18
	barSize := int64(80 - len(prefix) - fmtBytesSize)
	bar := ioprogress.DrawTextFormatBarForW(barSize, os.Stderr)
	fmtfunc := func(progress, total int64) string {
		return fmt.Sprintf(
			"%s: %s %s",
			prefix,
			bar(progress, total),
			ioprogress.DrawTextFormatBytes(progress, total),
		)
	}

	progressReader := &ioprogress.Reader{
		Reader:       res.Body,
		Size:         imgSize,
		DrawFunc:     ioprogress.DrawTerminalf(os.Stderr, fmtfunc),
		DrawInterval: 500 * time.Millisecond,
	}

	layerFile, err := ioutil.TempFile(tmpDir, "dockerlayer-")
	if err != nil {
		return nil, err
	}

	_, err = io.Copy(layerFile, progressReader)
	if err != nil {
		return nil, err
	}

	if err := layerFile.Sync(); err != nil {
		return nil, err
	}

	return layerFile, nil
}
Beispiel #8
0
func (rb *RepositoryBackend) makeRequest(req *http.Request, repo string) (*http.Response, error) {
	setBearerHeader := false
	hostAuthTokens, ok := rb.hostsV2AuthTokens[req.URL.Host]
	if ok {
		authToken, ok := hostAuthTokens[repo]
		if ok {
			req.Header.Set("Authorization", "Bearer "+authToken)
			setBearerHeader = true
		}
	}

	client := util.GetTLSClient(rb.insecure)
	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	if res.StatusCode == http.StatusUnauthorized && setBearerHeader {
		return res, err
	}

	hdr := res.Header.Get("www-authenticate")
	if res.StatusCode != http.StatusUnauthorized || hdr == "" {
		return res, err
	}

	tokens := strings.Split(hdr, ",")
	if len(tokens) != 3 ||
		!strings.HasPrefix(strings.ToLower(tokens[0]), "bearer realm") {
		return res, err
	}
	res.Body.Close()

	var realm, service, scope string
	for _, token := range tokens {
		if strings.HasPrefix(strings.ToLower(token), "bearer realm") {
			realm = strings.Trim(token[len("bearer realm="):], "\"")
		}
		if strings.HasPrefix(token, "service") {
			service = strings.Trim(token[len("service="):], "\"")
		}
		if strings.HasPrefix(token, "scope") {
			scope = strings.Trim(token[len("scope="):], "\"")
		}
	}

	if realm == "" {
		return nil, fmt.Errorf("missing realm in bearer auth challenge")
	}
	if service == "" {
		return nil, fmt.Errorf("missing service in bearer auth challenge")
	}
	// The scope can be empty if we're not getting a token for a specific repo
	if scope == "" && repo != "" {
		// If the scope is empty and it shouldn't be, we can infer it based on the repo
		scope = fmt.Sprintf("repository:%s:pull", repo)
	}

	authReq, err := http.NewRequest("GET", realm, nil)
	if err != nil {
		return nil, err
	}

	getParams := authReq.URL.Query()
	getParams.Add("service", service)
	if scope != "" {
		getParams.Add("scope", scope)
	}
	authReq.URL.RawQuery = getParams.Encode()

	rb.setBasicAuth(authReq)

	res, err = client.Do(authReq)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	switch res.StatusCode {
	case http.StatusUnauthorized:
		return nil, fmt.Errorf("unable to retrieve auth token: 401 unauthorized")
	case http.StatusOK:
		break
	default:
		return nil, fmt.Errorf("unexpected http code: %d, URL: %s", res.StatusCode, authReq.URL)
	}

	tokenBlob, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	tokenStruct := struct {
		Token string `json:"token"`
	}{}

	err = json.Unmarshal(tokenBlob, &tokenStruct)
	if err != nil {
		return nil, err
	}

	hostAuthTokens, ok = rb.hostsV2AuthTokens[req.URL.Host]
	if !ok {
		hostAuthTokens = make(map[string]string)
		rb.hostsV2AuthTokens[req.URL.Host] = hostAuthTokens
	}

	hostAuthTokens[repo] = tokenStruct.Token

	return rb.makeRequest(req, repo)
}