mirror of
https://github.com/containers/skopeo.git
synced 2025-05-08 07:56:18 +00:00
This addresses CVE-2024-3727 https://issues.redhat.com/browse/OCPBUGS-33267 Signed-off-by: tomsweeneyredhat <tsweeney@redhat.com>
172 lines
5.0 KiB
Go
172 lines
5.0 KiB
Go
package docker
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
|
||
"github.com/containers/image/v5/docker/reference"
|
||
"github.com/containers/image/v5/internal/image"
|
||
"github.com/containers/image/v5/manifest"
|
||
"github.com/containers/image/v5/types"
|
||
"github.com/opencontainers/go-digest"
|
||
)
|
||
|
||
// Image is a Docker-specific implementation of types.ImageCloser with a few extra methods
|
||
// which are specific to Docker.
|
||
type Image struct {
|
||
types.ImageCloser
|
||
src *dockerImageSource
|
||
}
|
||
|
||
// newImage returns a new Image interface type after setting up
|
||
// a client to the registry hosting the given image.
|
||
// The caller must call .Close() on the returned Image.
|
||
func newImage(ctx context.Context, sys *types.SystemContext, ref dockerReference) (types.ImageCloser, error) {
|
||
s, err := newImageSource(ctx, sys, ref)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
img, err := image.FromSource(ctx, sys, s)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &Image{ImageCloser: img, src: s}, nil
|
||
}
|
||
|
||
// SourceRefFullName returns a fully expanded name for the repository this image is in.
|
||
func (i *Image) SourceRefFullName() string {
|
||
return i.src.logicalRef.ref.Name()
|
||
}
|
||
|
||
// GetRepositoryTags list all tags available in the repository. The tag
|
||
// provided inside the ImageReference will be ignored. (This is a
|
||
// backward-compatible shim method which calls the module-level
|
||
// GetRepositoryTags)
|
||
func (i *Image) GetRepositoryTags(ctx context.Context) ([]string, error) {
|
||
return GetRepositoryTags(ctx, i.src.c.sys, i.src.logicalRef)
|
||
}
|
||
|
||
// GetRepositoryTags list all tags available in the repository. The tag
|
||
// provided inside the ImageReference will be ignored.
|
||
func GetRepositoryTags(ctx context.Context, sys *types.SystemContext, ref types.ImageReference) ([]string, error) {
|
||
dr, ok := ref.(dockerReference)
|
||
if !ok {
|
||
return nil, errors.New("ref must be a dockerReference")
|
||
}
|
||
|
||
registryConfig, err := loadRegistryConfiguration(sys)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
path := fmt.Sprintf(tagsPath, reference.Path(dr.ref))
|
||
client, err := newDockerClientFromRef(sys, dr, registryConfig, false, "pull")
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create client: %w", err)
|
||
}
|
||
defer client.Close()
|
||
|
||
tags := make([]string, 0)
|
||
|
||
for {
|
||
res, err := client.makeRequest(ctx, http.MethodGet, path, nil, nil, v2Auth, nil)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer res.Body.Close()
|
||
if res.StatusCode != http.StatusOK {
|
||
return nil, fmt.Errorf("fetching tags list: %w", registryHTTPResponseToError(res))
|
||
}
|
||
|
||
var tagsHolder struct {
|
||
Tags []string
|
||
}
|
||
if err = json.NewDecoder(res.Body).Decode(&tagsHolder); err != nil {
|
||
return nil, err
|
||
}
|
||
for _, tag := range tagsHolder.Tags {
|
||
if _, err := reference.WithTag(dr.ref, tag); err != nil { // Ensure the tag does not contain unexpected values
|
||
return nil, fmt.Errorf("registry returned invalid tag %q: %w", tag, err)
|
||
}
|
||
tags = append(tags, tag)
|
||
}
|
||
|
||
link := res.Header.Get("Link")
|
||
if link == "" {
|
||
break
|
||
}
|
||
|
||
linkURLPart, _, _ := strings.Cut(link, ";")
|
||
linkURL, err := url.Parse(strings.Trim(linkURLPart, "<>"))
|
||
if err != nil {
|
||
return tags, err
|
||
}
|
||
|
||
// can be relative or absolute, but we only want the path (and I
|
||
// guess we're in trouble if it forwards to a new place...)
|
||
path = linkURL.Path
|
||
if linkURL.RawQuery != "" {
|
||
path += "?"
|
||
path += linkURL.RawQuery
|
||
}
|
||
}
|
||
return tags, nil
|
||
}
|
||
|
||
// GetDigest returns the image's digest
|
||
// Use this to optimize and avoid use of an ImageSource based on the returned digest;
|
||
// if you are going to use an ImageSource anyway, it’s more efficient to create it first
|
||
// and compute the digest from the value returned by GetManifest.
|
||
// NOTE: Implemented to avoid Docker Hub API limits, and mirror configuration may be
|
||
// ignored (but may be implemented in the future)
|
||
func GetDigest(ctx context.Context, sys *types.SystemContext, ref types.ImageReference) (digest.Digest, error) {
|
||
dr, ok := ref.(dockerReference)
|
||
if !ok {
|
||
return "", errors.New("ref must be a dockerReference")
|
||
}
|
||
if dr.isUnknownDigest {
|
||
return "", fmt.Errorf("docker: reference %q is for unknown digest case; cannot get digest", dr.StringWithinTransport())
|
||
}
|
||
|
||
tagOrDigest, err := dr.tagOrDigest()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
registryConfig, err := loadRegistryConfiguration(sys)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
client, err := newDockerClientFromRef(sys, dr, registryConfig, false, "pull")
|
||
if err != nil {
|
||
return "", fmt.Errorf("failed to create client: %w", err)
|
||
}
|
||
defer client.Close()
|
||
|
||
path := fmt.Sprintf(manifestPath, reference.Path(dr.ref), tagOrDigest)
|
||
headers := map[string][]string{
|
||
"Accept": manifest.DefaultRequestedManifestMIMETypes,
|
||
}
|
||
|
||
res, err := client.makeRequest(ctx, http.MethodHead, path, headers, nil, v2Auth, nil)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
defer res.Body.Close()
|
||
if res.StatusCode != http.StatusOK {
|
||
return "", fmt.Errorf("reading digest %s in %s: %w", tagOrDigest, dr.ref.Name(), registryHTTPResponseToError(res))
|
||
}
|
||
|
||
dig, err := digest.Parse(res.Header.Get("Docker-Content-Digest"))
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
return dig, nil
|
||
}
|