diff options
Diffstat (limited to 'pkg/domain/infra/abi/images.go')
-rw-r--r-- | pkg/domain/infra/abi/images.go | 291 |
1 files changed, 216 insertions, 75 deletions
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 7c63276e5..d8af4d339 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -4,14 +4,22 @@ import ( "context" "fmt" "io" + "io/ioutil" + "net/url" "os" + "path/filepath" + "strconv" "strings" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker" dockerarchive "github.com/containers/image/v5/docker/archive" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/signature" + "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/libpod/libpod/define" @@ -19,14 +27,17 @@ import ( libpodImage "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/domain/entities" domainUtils "github.com/containers/libpod/pkg/domain/utils" + "github.com/containers/libpod/pkg/trust" "github.com/containers/libpod/pkg/util" "github.com/containers/storage" - "github.com/hashicorp/go-multierror" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) +// SignatureStoreDir defines default directory to store signatures +const SignatureStoreDir = "/var/lib/containers/sigstore" + func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.BoolReport, error) { _, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) if err != nil && errors.Cause(err) != define.ErrNoSuchImage { @@ -36,7 +47,11 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo } func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { - results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter) + return ir.pruneImagesHelper(ctx, opts.All, opts.Filter) +} + +func (ir *ImageEngine) pruneImagesHelper(ctx context.Context, all bool, filters []string) (*entities.ImagePruneReport, error) { + results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, all, filters) if err != nil { return nil, err } @@ -106,19 +121,18 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti } var registryCreds *types.DockerAuthConfig - if options.Credentials != "" { - creds, err := util.ParseRegistryCreds(options.Credentials) - if err != nil { - return nil, err + if len(options.Username) > 0 && len(options.Password) > 0 { + registryCreds = &types.DockerAuthConfig{ + Username: options.Username, + Password: options.Password, } - registryCreds = creds } dockerRegistryOptions := image.DockerRegistryOptions{ DockerRegistryCreds: registryCreds, DockerCertPath: options.CertDir, OSChoice: options.OverrideOS, ArchitectureChoice: options.OverrideArch, - DockerInsecureSkipTLSVerify: options.TLSVerify, + DockerInsecureSkipTLSVerify: options.SkipTLSVerify, } if !options.AllTags { @@ -211,17 +225,16 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri } var registryCreds *types.DockerAuthConfig - if options.Credentials != "" { - creds, err := util.ParseRegistryCreds(options.Credentials) - if err != nil { - return err + if len(options.Username) > 0 && len(options.Password) > 0 { + registryCreds = &types.DockerAuthConfig{ + Username: options.Username, + Password: options.Password, } - registryCreds = creds } dockerRegistryOptions := image.DockerRegistryOptions{ DockerRegistryCreds: registryCreds, DockerCertPath: options.CertDir, - DockerInsecureSkipTLSVerify: options.TLSVerify, + DockerInsecureSkipTLSVerify: options.SkipTLSVerify, } signOptions := image.SigningOptions{ @@ -370,7 +383,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im Filter: *filter, Limit: opts.Limit, NoTrunc: opts.NoTrunc, - InsecureSkipTLSVerify: opts.TLSVerify, + InsecureSkipTLSVerify: opts.SkipTLSVerify, } searchResults, err := image.SearchImages(term, searchOpts) @@ -419,8 +432,10 @@ func (ir *ImageEngine) Tree(ctx context.Context, nameOrId string, opts entities. return &entities.ImageTreeReport{Tree: results}, nil } -// Remove removes one or more images from local storage. -func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, finalError error) { +// removeErrorsToExitCode returns an exit code for the specified slice of +// image-removal errors. The error codes are set according to the documented +// behaviour in the Podman man pages. +func removeErrorsToExitCode(rmErrors []error) int { var ( // noSuchImageErrors indicates that at least one image was not found. noSuchImageErrors bool @@ -430,57 +445,53 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie // otherErrors indicates that at least one error other than the two // above occured. otherErrors bool - // deleteError is a multierror to conveniently collect errors during - // removal. We really want to delete as many images as possible and not - // error out immediately. - deleteError *multierror.Error ) - report = &entities.ImageRemoveReport{} + if len(rmErrors) == 0 { + return 0 + } - // Set the removalCode and the error after all work is done. - defer func() { - switch { - // 2 - case inUseErrors: - // One of the specified images has child images or is - // being used by a container. - report.ExitCode = 2 - // 1 - case noSuchImageErrors && !(otherErrors || inUseErrors): - // One of the specified images did not exist, and no other - // failures. - report.ExitCode = 1 - // 0 + for _, e := range rmErrors { + switch errors.Cause(e) { + case define.ErrNoSuchImage: + noSuchImageErrors = true + case define.ErrImageInUse, storage.ErrImageUsedByContainer: + inUseErrors = true default: - // Nothing to do. - } - if deleteError != nil { - // go-multierror has a trailing new line which we need to remove to normalize the string. - finalError = deleteError.ErrorOrNil() - finalError = errors.New(strings.TrimSpace(finalError.Error())) + otherErrors = true } + } + + switch { + case inUseErrors: + // One of the specified images has child images or is + // being used by a container. + return 2 + case noSuchImageErrors && !(otherErrors || inUseErrors): + // One of the specified images did not exist, and no other + // failures. + return 1 + default: + return 125 + } +} + +// Remove removes one or more images from local storage. +func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, rmErrors []error) { + report = &entities.ImageRemoveReport{} + + // Set the exit code at very end. + defer func() { + report.ExitCode = removeErrorsToExitCode(rmErrors) }() // deleteImage is an anonymous function to conveniently delete an image // without having to pass all local data around. deleteImage := func(img *image.Image) error { results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force) - switch errors.Cause(err) { - case nil: - break - case storage.ErrImageUsedByContainer: - inUseErrors = true // Important for exit codes in Podman. - return errors.New( - fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID())) - case define.ErrImageInUse: - inUseErrors = true - return err - default: - otherErrors = true // Important for exit codes in Podman. + if err != nil { return err } - report.Deleted = append(report.Deleted, results.Deleted) report.Untagged = append(report.Untagged, results.Untagged...) return nil @@ -493,9 +504,7 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie for { storageImages, err := ir.Libpod.ImageRuntime().GetRWImages() if err != nil { - deleteError = multierror.Append(deleteError, - errors.Wrapf(err, "unable to query local images")) - otherErrors = true // Important for exit codes in Podman. + rmErrors = append(rmErrors, err) return } // No images (left) to remove, so we're done. @@ -504,9 +513,7 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie } // Prevent infinity loops by making a delete-progress check. if previousImages == len(storageImages) { - otherErrors = true // Important for exit codes in Podman. - deleteError = multierror.Append(deleteError, - errors.New("unable to delete all images, check errors and re-run image removal if needed")) + rmErrors = append(rmErrors, errors.New("unable to delete all images, check errors and re-run image removal if needed")) break } previousImages = len(storageImages) @@ -514,15 +521,15 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie for _, img := range storageImages { isParent, err := img.IsParent(ctx) if err != nil { - otherErrors = true // Important for exit codes in Podman. - deleteError = multierror.Append(deleteError, err) + rmErrors = append(rmErrors, err) + continue } // Skip parent images. if isParent { continue } if err := deleteImage(img); err != nil { - deleteError = multierror.Append(deleteError, err) + rmErrors = append(rmErrors, err) } } } @@ -533,21 +540,13 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie // Delete only the specified images. for _, id := range images { img, err := ir.Libpod.ImageRuntime().NewFromLocal(id) - switch errors.Cause(err) { - case nil: - break - case image.ErrNoSuchImage: - noSuchImageErrors = true // Important for exit codes in Podman. - fallthrough - default: - deleteError = multierror.Append(deleteError, err) + if err != nil { + rmErrors = append(rmErrors, err) continue } - err = deleteImage(img) if err != nil { - otherErrors = true // Important for exit codes in Podman. - deleteError = multierror.Append(deleteError, err) + rmErrors = append(rmErrors, err) } } @@ -560,3 +559,145 @@ func (ir *ImageEngine) Shutdown(_ context.Context) { _ = ir.Libpod.Shutdown(false) }) } + +func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entities.SignOptions) (*entities.SignReport, error) { + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerCertPath: options.CertDir, + } + + mech, err := signature.NewGPGSigningMechanism() + if err != nil { + return nil, errors.Wrap(err, "error initializing GPG") + } + defer mech.Close() + if err := mech.SupportsSigning(); err != nil { + return nil, errors.Wrap(err, "signing is not supported") + } + sc := ir.Libpod.SystemContext() + sc.DockerCertPath = options.CertDir + + systemRegistriesDirPath := trust.RegistriesDirPath(sc) + registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) + if err != nil { + return nil, errors.Wrapf(err, "error reading registry configuration") + } + + for _, signimage := range names { + var sigStoreDir string + srcRef, err := alltransports.ParseImageName(signimage) + if err != nil { + return nil, errors.Wrapf(err, "error parsing image name") + } + rawSource, err := srcRef.NewImageSource(ctx, sc) + if err != nil { + return nil, errors.Wrapf(err, "error getting image source") + } + err = rawSource.Close() + if err != nil { + logrus.Errorf("unable to close new image source %q", err) + } + getManifest, _, err := rawSource.GetManifest(ctx, nil) + if err != nil { + return nil, errors.Wrapf(err, "error getting getManifest") + } + dockerReference := rawSource.Reference().DockerReference() + if dockerReference == nil { + return nil, errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference())) + } + + // create the signstore file + rtc, err := ir.Libpod.GetConfig() + if err != nil { + return nil, err + } + newImage, err := ir.Libpod.ImageRuntime().New(ctx, signimage, rtc.Engine.SignaturePolicyPath, "", os.Stderr, &dockerRegistryOptions, image.SigningOptions{SignBy: options.SignBy}, nil, util.PullImageMissing) + if err != nil { + return nil, errors.Wrapf(err, "error pulling image %s", signimage) + } + if sigStoreDir == "" { + if rootless.IsRootless() { + sigStoreDir = filepath.Join(filepath.Dir(ir.Libpod.StorageConfig().GraphRoot), "sigstore") + } else { + registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs) + if registryInfo != nil { + if sigStoreDir = registryInfo.SigStoreStaging; sigStoreDir == "" { + sigStoreDir = registryInfo.SigStore + + } + } + } + } + sigStoreDir, err = isValidSigStoreDir(sigStoreDir) + if err != nil { + return nil, errors.Wrapf(err, "invalid signature storage %s", sigStoreDir) + } + repos, err := newImage.RepoDigests() + if err != nil { + return nil, errors.Wrapf(err, "error calculating repo digests for %s", signimage) + } + if len(repos) == 0 { + logrus.Errorf("no repodigests associated with the image %s", signimage) + continue + } + + // create signature + newSig, err := signature.SignDockerManifest(getManifest, dockerReference.String(), mech, options.SignBy) + if err != nil { + return nil, errors.Wrapf(err, "error creating new signature") + } + + trimmedDigest := strings.TrimPrefix(repos[0], strings.Split(repos[0], "/")[0]) + sigStoreDir = filepath.Join(sigStoreDir, strings.Replace(trimmedDigest, ":", "=", 1)) + if err := os.MkdirAll(sigStoreDir, 0751); err != nil { + // The directory is allowed to exist + if !os.IsExist(err) { + logrus.Errorf("error creating directory %s: %s", sigStoreDir, err) + continue + } + } + sigFilename, err := getSigFilename(sigStoreDir) + if err != nil { + logrus.Errorf("error creating sigstore file: %v", err) + continue + } + err = ioutil.WriteFile(filepath.Join(sigStoreDir, sigFilename), newSig, 0644) + if err != nil { + logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String()) + continue + } + } + return nil, nil +} + +func getSigFilename(sigStoreDirPath string) (string, error) { + sigFileSuffix := 1 + sigFiles, err := ioutil.ReadDir(sigStoreDirPath) + if err != nil { + return "", err + } + sigFilenames := make(map[string]bool) + for _, file := range sigFiles { + sigFilenames[file.Name()] = true + } + for { + sigFilename := "signature-" + strconv.Itoa(sigFileSuffix) + if _, exists := sigFilenames[sigFilename]; !exists { + return sigFilename, nil + } + sigFileSuffix++ + } +} + +func isValidSigStoreDir(sigStoreDir string) (string, error) { + writeURIs := map[string]bool{"file": true} + url, err := url.Parse(sigStoreDir) + if err != nil { + return sigStoreDir, errors.Wrapf(err, "invalid directory %s", sigStoreDir) + } + _, exists := writeURIs[url.Scheme] + if !exists { + return sigStoreDir, errors.Errorf("writing to %s is not supported. Use a supported scheme", sigStoreDir) + } + sigStoreDir = url.Path + return sigStoreDir, nil +} |