1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
package image
import (
"context"
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"github.com/containers/image/v5/copy"
"github.com/containers/image/v5/directory"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/signature"
"github.com/crc-org/crc/v2/pkg/crc/constants"
"github.com/crc-org/crc/v2/pkg/crc/gpg"
"github.com/crc-org/crc/v2/pkg/crc/logging"
crcpreset "github.com/crc-org/crc/v2/pkg/crc/preset"
"github.com/crc-org/crc/v2/pkg/extract"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)
type imageHandler struct {
imageURI string
}
func ValidateURI(uri *url.URL) error {
/* For now we are very restrictive on the docker:// URLs we accept, it
* has to contain the bundle version as the tag, and it has to use the
* same image names as the official registry (openshift-bundle,
* okd-bundle, podman-bundle). In future releases, we'll make this more
* flexible
*/
imageAndTag := strings.Split(path.Base(uri.Path), ":")
if len(imageAndTag) != 2 {
return fmt.Errorf("invalid %s registry URL, tag is required (such as docker://quay.io/crcont/openshift-bundle:4.11.0)", uri)
}
_, err := getPresetNameE(imageAndTag[0])
return err
}
func (img *imageHandler) policyContext() (*signature.PolicyContext, error) {
policy := &signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}}
policyContext, err := signature.NewPolicyContext(policy)
if err != nil {
return nil, fmt.Errorf("error creating security context: %w", err)
}
return policyContext, nil
}
// copyImage pulls the image from the registry and puts it to destination path
func (img *imageHandler) copyImage(destPath string, reportWriter io.Writer) (*v1.Manifest, error) {
// Source Image from docker transport
srcImg := img.imageURI
srcRef, err := docker.ParseReference(srcImg)
if err != nil {
return nil, fmt.Errorf("invalid source image name %s: %w", srcImg, err)
}
destRef, err := directory.Transport.ParseReference(destPath)
if err != nil {
return nil, fmt.Errorf("invalid destination name %s: %w", destPath, err)
}
policyContext, err := img.policyContext()
if err != nil {
return nil, err
}
manifestData, err := copy.Image(context.Background(), policyContext,
destRef, srcRef, ©.Options{
ReportWriter: reportWriter,
})
if err != nil {
return nil, err
}
manifest := &v1.Manifest{}
if err := json.Unmarshal(manifestData, manifest); err != nil {
return nil, err
}
return manifest, nil
}
func getLayerPath(m *v1.Manifest, index int, mediaType string) (string, error) {
if len(m.Layers) < (index + 1) {
return "", fmt.Errorf("image layers in manifest is less than %d", index+1)
}
if m.Layers[index].MediaType != mediaType {
return "", fmt.Errorf("expected media type for layer %s, got %s", mediaType, m.Layers[index].MediaType)
}
return strings.TrimPrefix(m.Layers[index].Digest.String(), "sha256:"), nil
}
func getPresetNameE(imageName string) (crcpreset.Preset, error) {
switch imageName {
case "openshift-bundle":
return crcpreset.OpenShift, nil
case "okd-bundle":
return crcpreset.OKD, nil
case "podman-bundle":
return crcpreset.Podman, nil
case "microshift-bundle":
return crcpreset.Microshift, nil
default:
return crcpreset.OpenShift, fmt.Errorf("invalid image name '%s' (Should be openshift-bundle, okd-bundle, podman-bundle or microshift-bundle)", imageName)
}
}
func GetPresetName(imageName string) crcpreset.Preset {
preset, _ := getPresetNameE(imageName)
return preset
}
func PullBundle(imageURI string) (string, error) {
imgHandler := imageHandler{
imageURI: strings.TrimPrefix(imageURI, "docker:"),
}
destDir, err := os.MkdirTemp(constants.MachineCacheDir, "tmpBundleImage")
if err != nil {
return "", err
}
defer os.RemoveAll(destDir)
imgManifest, err := imgHandler.copyImage(destDir, os.Stdout)
if err != nil {
return "", err
}
logging.Info("Extracting the image bundle layer...")
imgLayer, err := getLayerPath(imgManifest, 0, "application/vnd.oci.image.layer.v1.tar+gzip")
if err != nil {
return "", err
}
fileList, err := extract.Uncompress(filepath.Join(destDir, imgLayer), constants.MachineCacheDir)
if err != nil {
return "", err
}
logging.Debugf("Bundle and sign path: %v", fileList)
logging.Info("Verifying the bundle signature...")
if len(fileList) != 2 {
return "", fmt.Errorf("image layer contains more files than expected: %v", fileList)
}
bundleFilePath, sigFilePath := fileList[0], fileList[1]
if !strings.HasSuffix(sigFilePath, ".crcbundle.sig") {
sigFilePath, bundleFilePath = fileList[0], fileList[1]
}
if err := gpg.Verify(bundleFilePath, sigFilePath); err != nil {
return "", err
}
return bundleFilePath, nil
}
|