Bump github.com/containers/image/v5 from 5.18.0 to 5.19.0

Bumps [github.com/containers/image/v5](https://github.com/containers/image) from 5.18.0 to 5.19.0.
- [Release notes](https://github.com/containers/image/releases)
- [Commits](https://github.com/containers/image/compare/v5.18.0...v5.19.0)

---
updated-dependencies:
- dependency-name: github.com/containers/image/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2022-01-26 15:15:46 +00:00
committed by GitHub
parent 2a39fe9913
commit ab22a688d8
53 changed files with 3125 additions and 178 deletions

View File

@ -124,6 +124,7 @@ type ImageListSelection int
type Options struct {
RemoveSignatures bool // Remove any pre-existing signatures. SignBy will still add a new signature.
SignBy string // If non-empty, asks for a signature to be added during the copy, and specifies a key ID, as accepted by signature.NewGPGSigningMechanism().SignDockerManifest(),
SignPassphrase string // Passphare to use when signing with the key ID from `SignBy`.
ReportWriter io.Writer
SourceCtx *types.SystemContext
DestinationCtx *types.SystemContext
@ -569,7 +570,7 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur
// Sign the manifest list.
if options.SignBy != "" {
newSig, err := c.createSignature(manifestList, options.SignBy)
newSig, err := c.createSignature(manifestList, options.SignBy, options.SignPassphrase)
if err != nil {
return nil, err
}
@ -791,7 +792,7 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli
}
if options.SignBy != "" {
newSig, err := c.createSignature(manifestBytes, options.SignBy)
newSig, err := c.createSignature(manifestBytes, options.SignBy, options.SignPassphrase)
if err != nil {
return nil, "", "", err
}

View File

@ -7,7 +7,7 @@ import (
)
// createSignature creates a new signature of manifest using keyIdentity.
func (c *copier) createSignature(manifest []byte, keyIdentity string) ([]byte, error) {
func (c *copier) createSignature(manifest []byte, keyIdentity string, passphrase string) ([]byte, error) {
mech, err := signature.NewGPGSigningMechanism()
if err != nil {
return nil, errors.Wrap(err, "initializing GPG")
@ -23,7 +23,7 @@ func (c *copier) createSignature(manifest []byte, keyIdentity string) ([]byte, e
}
c.Printf("Signing manifest\n")
newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, keyIdentity)
newSig, err := signature.SignDockerManifestWithOptions(manifest, dockerReference.String(), mech, keyIdentity, &signature.SignOptions{Passphrase: passphrase})
if err != nil {
return nil, errors.Wrap(err, "creating signature")
}

View File

@ -51,7 +51,7 @@ const (
// other than the ones the caller specifically allows.
// expectedMIMEType is used only for diagnostics.
// NOTE: The caller should do the non-heuristic validations (e.g. check for any specified format
// identification/version, or other “magic numbers”) before calling this, to cleanly reject unambigous
// identification/version, or other “magic numbers”) before calling this, to cleanly reject unambiguous
// data that just isnt what was expected, as opposed to actually ambiguous data.
func validateUnambiguousManifestFormat(manifest []byte, expectedMIMEType string,
allowed allowedManifestFields) error {
@ -71,7 +71,7 @@ func validateUnambiguousManifestFormat(manifest []byte, expectedMIMEType string,
Manifests interface{} `json:"manifests"`
}{}
if err := json.Unmarshal(manifest, &detectedFields); err != nil {
// The caller was supposed to already validate version numbers, so this shold not happen;
// The caller was supposed to already validate version numbers, so this should not happen;
// lets not bother with making this error “nice”.
return err
}

View File

@ -667,6 +667,7 @@ func findCredentialsInFile(key, registry, path string, legacyFormat bool) (types
// This intentionally uses "registry", not "key"; we don't support namespaced
// credentials in helpers.
if ch, exists := auths.CredHelpers[registry]; exists {
logrus.Debugf("Looking up in credential helper %s based on credHelpers entry in %s", ch, path)
return getAuthFromCredHelper(ch, registry)
}
@ -703,6 +704,9 @@ func findCredentialsInFile(key, registry, path string, legacyFormat bool) (types
}
}
// Only log this if we found nothing; getCredentialsWithHomeDir logs the
// source of found data.
logrus.Debugf("No credentials matching %s found in %s", key, path)
return types.DockerAuthConfig{}, nil
}

211
vendor/github.com/containers/image/v5/sif/load.go generated vendored Normal file
View File

@ -0,0 +1,211 @@
package sif
import (
"bufio"
"context"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/sirupsen/logrus"
"github.com/sylabs/sif/v2/pkg/sif"
)
// injectedScriptTargetPath is the path injectedScript should be written to in the created image.
const injectedScriptTargetPath = "/podman/runscript"
// parseDefFile parses a SIF definition file from reader,
// and returns non-trivial contents of the %environment and %runscript sections.
func parseDefFile(reader io.Reader) ([]string, []string, error) {
type parserState int
const (
parsingOther parserState = iota
parsingEnvironment
parsingRunscript
)
environment := []string{}
runscript := []string{}
state := parsingOther
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
s := strings.TrimSpace(scanner.Text())
switch {
case s == `%environment`:
state = parsingEnvironment
case s == `%runscript`:
state = parsingRunscript
case strings.HasPrefix(s, "%"):
state = parsingOther
case state == parsingEnvironment:
if s != "" && !strings.HasPrefix(s, "#") {
environment = append(environment, s)
}
case state == parsingRunscript:
runscript = append(runscript, s)
default: // parsingOther: ignore the line
}
}
if err := scanner.Err(); err != nil {
return nil, nil, fmt.Errorf("reading lines from SIF definition file object: %w", err)
}
return environment, runscript, nil
}
// generateInjectedScript generates a shell script based on
// SIF definition file %environment and %runscript data, and returns it.
func generateInjectedScript(environment []string, runscript []string) []byte {
script := fmt.Sprintf("#!/bin/bash\n"+
"%s\n"+
"%s\n", strings.Join(environment, "\n"), strings.Join(runscript, "\n"))
return []byte(script)
}
// processDefFile finds sif.DataDeffile in sifImage, if any,
// and returns:
// - the command to run
// - contents of a script to inject as injectedScriptTargetPath, or nil
func processDefFile(sifImage *sif.FileImage) (string, []byte, error) {
var environment, runscript []string
desc, err := sifImage.GetDescriptor(sif.WithDataType(sif.DataDeffile))
if err == nil {
environment, runscript, err = parseDefFile(desc.GetReader())
if err != nil {
return "", nil, err
}
}
var command string
var injectedScript []byte
if len(environment) == 0 && len(runscript) == 0 {
command = "bash"
injectedScript = nil
} else {
injectedScript = generateInjectedScript(environment, runscript)
command = injectedScriptTargetPath
}
return command, injectedScript, nil
}
func writeInjectedScript(extractedRootPath string, injectedScript []byte) error {
if injectedScript == nil {
return nil
}
filePath := filepath.Join(extractedRootPath, injectedScriptTargetPath)
parentDirPath := filepath.Dir(filePath)
if err := os.MkdirAll(parentDirPath, 0755); err != nil {
return fmt.Errorf("creating %s: %w", parentDirPath, err)
}
if err := ioutil.WriteFile(filePath, injectedScript, 0755); err != nil {
return fmt.Errorf("writing %s to %s: %w", injectedScriptTargetPath, filePath, err)
}
return nil
}
// createTarFromSIFInputs creates a tar file at tarPath, using a squashfs image at squashFSPath.
// It can also use extractedRootPath and scriptPath, which are allocated for its exclusive use,
// if necessary.
func createTarFromSIFInputs(ctx context.Context, tarPath, squashFSPath string, injectedScript []byte, extractedRootPath, scriptPath string) error {
// It's safe for the Remove calls to happen even before we create the files, because tempDir is exclusive
// for our use.
defer os.RemoveAll(extractedRootPath)
// Almost everything in extractedRootPath comes from squashFSPath.
conversionCommand := fmt.Sprintf("unsquashfs -d %s -f %s && tar --acls --xattrs -C %s -cpf %s ./",
extractedRootPath, squashFSPath, extractedRootPath, tarPath)
script := "#!/bin/sh\n" + conversionCommand + "\n"
if err := ioutil.WriteFile(scriptPath, []byte(script), 0755); err != nil {
return err
}
defer os.Remove(scriptPath)
// On top of squashFSPath, we only add injectedScript, if necessary.
if err := writeInjectedScript(extractedRootPath, injectedScript); err != nil {
return err
}
logrus.Debugf("Converting squashfs to tar, command: %s ...", conversionCommand)
cmd := exec.CommandContext(ctx, "fakeroot", "--", scriptPath)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("converting image: %w, output: %s", err, string(output))
}
logrus.Debugf("... finished converting squashfs to tar")
return nil
}
// convertSIFToElements processes sifImage and creates/returns
// the relevant elements for constructing an OCI-like image:
// - A path to a tar file containing a root filesystem,
// - A command to run.
// The returned tar file path is inside tempDir, which can be assumed to be empty
// at start, and is exclusively used by the current process (i.e. it is safe
// to use hard-coded relative paths within it).
func convertSIFToElements(ctx context.Context, sifImage *sif.FileImage, tempDir string) (string, []string, error) {
// We could allocate unique names for all of these using ioutil.Temp*, but tempDir is exclusive,
// so we can just hard-code a set of unique values here.
// We create and/or manage cleanup of these two paths.
squashFSPath := filepath.Join(tempDir, "rootfs.squashfs")
tarPath := filepath.Join(tempDir, "rootfs.tar")
// We only allocate these paths, the user is responsible for cleaning them up.
extractedRootPath := filepath.Join(tempDir, "rootfs")
scriptPath := filepath.Join(tempDir, "script")
succeeded := false
// It's safe for the Remove calls to happen even before we create the files, because tempDir is exclusive
// for our use.
// Ideally we would remove squashFSPath immediately after creating extractedRootPath, but we need
// to run both creation and consumption of extractedRootPath in the same fakeroot context.
// So, overall, this process requires at least 2 compressed copies (SIF and squashFSPath) and 2
// uncompressed copies (extractedRootPath and tarPath) of the data, all using up space at the same time.
// That's rather unsatisfactory, ideally we would be streaming the data directly from a squashfs parser
// reading from the SIF file to a tarball, for 1 compressed and 1 uncompressed copy.
defer os.Remove(squashFSPath)
defer func() {
if !succeeded {
os.Remove(tarPath)
}
}()
command, injectedScript, err := processDefFile(sifImage)
if err != nil {
return "", nil, err
}
rootFS, err := sifImage.GetDescriptor(sif.WithPartitionType(sif.PartPrimSys))
if err != nil {
return "", nil, fmt.Errorf("looking up rootfs from SIF file: %w", err)
}
// TODO: We'd prefer not to make a full copy of the file here; unsquashfs ≥ 4.4
// has an -o option that allows extracting a squashfs from the SIF file directly,
// but that version is not currently available in RHEL 8.
logrus.Debugf("Creating a temporary squashfs image %s ...", squashFSPath)
if err := func() error { // A scope for defer
f, err := os.Create(squashFSPath)
if err != nil {
return err
}
defer f.Close()
// TODO: This can take quite some time, and should ideally be cancellable using ctx.Done().
if _, err := io.CopyN(f, rootFS.GetReader(), rootFS.Size()); err != nil {
return err
}
return nil
}(); err != nil {
return "", nil, err
}
logrus.Debugf("... finished creating a temporary squashfs image")
if err := createTarFromSIFInputs(ctx, tarPath, squashFSPath, injectedScript, extractedRootPath, scriptPath); err != nil {
return "", nil, err
}
succeeded = true
return tarPath, []string{command}, nil
}

217
vendor/github.com/containers/image/v5/sif/src.go generated vendored Normal file
View File

@ -0,0 +1,217 @@
package sif
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"github.com/containers/image/v5/internal/tmpdir"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
imgspecs "github.com/opencontainers/image-spec/specs-go"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
"github.com/sylabs/sif/v2/pkg/sif"
)
type sifImageSource struct {
ref sifReference
workDir string
layerDigest digest.Digest
layerSize int64
layerFile string
config []byte
configDigest digest.Digest
manifest []byte
}
// getBlobInfo returns the digest, and size of the provided file.
func getBlobInfo(path string) (digest.Digest, int64, error) {
f, err := os.Open(path)
if err != nil {
return "", -1, fmt.Errorf("opening %q for reading: %w", path, err)
}
defer f.Close()
// TODO: Instead of writing the tar file to disk, and reading
// it here again, stream the tar file to a pipe and
// compute the digest while writing it to disk.
logrus.Debugf("Computing a digest of the SIF conversion output...")
digester := digest.Canonical.Digester()
// TODO: This can take quite some time, and should ideally be cancellable using ctx.Done().
size, err := io.Copy(digester.Hash(), f)
if err != nil {
return "", -1, fmt.Errorf("reading %q: %w", path, err)
}
digest := digester.Digest()
logrus.Debugf("... finished computing the digest of the SIF conversion output")
return digest, size, nil
}
// newImageSource returns an ImageSource for reading from an existing directory.
// newImageSource extracts SIF objects and saves them in a temp directory.
func newImageSource(ctx context.Context, sys *types.SystemContext, ref sifReference) (types.ImageSource, error) {
sifImg, err := sif.LoadContainerFromPath(ref.file, sif.OptLoadWithFlag(os.O_RDONLY))
if err != nil {
return nil, fmt.Errorf("loading SIF file: %w", err)
}
defer func() {
_ = sifImg.UnloadContainer()
}()
workDir, err := ioutil.TempDir(tmpdir.TemporaryDirectoryForBigFiles(sys), "sif")
if err != nil {
return nil, fmt.Errorf("creating temp directory: %w", err)
}
succeeded := false
defer func() {
if !succeeded {
os.RemoveAll(workDir)
}
}()
layerPath, commandLine, err := convertSIFToElements(ctx, sifImg, workDir)
if err != nil {
return nil, fmt.Errorf("converting rootfs from SquashFS to Tarball: %w", err)
}
layerDigest, layerSize, err := getBlobInfo(layerPath)
if err != nil {
return nil, fmt.Errorf("gathering blob information: %w", err)
}
created := sifImg.ModifiedAt()
config := imgspecv1.Image{
Created: &created,
Architecture: sifImg.PrimaryArch(),
OS: "linux",
Config: imgspecv1.ImageConfig{
Cmd: commandLine,
},
RootFS: imgspecv1.RootFS{
Type: "layers",
DiffIDs: []digest.Digest{layerDigest},
},
History: []imgspecv1.History{
{
Created: &created,
CreatedBy: fmt.Sprintf("/bin/sh -c #(nop) ADD file:%s in %c", layerDigest.Hex(), os.PathSeparator),
Comment: "imported from SIF, uuid: " + sifImg.ID(),
},
{
Created: &created,
CreatedBy: "/bin/sh -c #(nop) CMD [\"bash\"]",
EmptyLayer: true,
},
},
}
configBytes, err := json.Marshal(&config)
if err != nil {
return nil, fmt.Errorf("generating configuration blob for %q: %w", ref.resolvedFile, err)
}
configDigest := digest.Canonical.FromBytes(configBytes)
manifest := imgspecv1.Manifest{
Versioned: imgspecs.Versioned{SchemaVersion: 2},
MediaType: imgspecv1.MediaTypeImageManifest,
Config: imgspecv1.Descriptor{
Digest: configDigest,
Size: int64(len(configBytes)),
MediaType: imgspecv1.MediaTypeImageConfig,
},
Layers: []imgspecv1.Descriptor{{
Digest: layerDigest,
Size: layerSize,
MediaType: imgspecv1.MediaTypeImageLayer,
}},
}
manifestBytes, err := json.Marshal(&manifest)
if err != nil {
return nil, fmt.Errorf("generating manifest for %q: %w", ref.resolvedFile, err)
}
succeeded = true
return &sifImageSource{
ref: ref,
workDir: workDir,
layerDigest: layerDigest,
layerSize: layerSize,
layerFile: layerPath,
config: configBytes,
configDigest: configDigest,
manifest: manifestBytes,
}, nil
}
// Reference returns the reference used to set up this source.
func (s *sifImageSource) Reference() types.ImageReference {
return s.ref
}
// Close removes resources associated with an initialized ImageSource, if any.
func (s *sifImageSource) Close() error {
return os.RemoveAll(s.workDir)
}
// HasThreadSafeGetBlob indicates whether GetBlob can be executed concurrently.
func (s *sifImageSource) HasThreadSafeGetBlob() bool {
return true
}
// GetBlob returns a stream for the specified blob, and the blobs size (or -1 if unknown).
// The Digest field in BlobInfo is guaranteed to be provided, Size may be -1 and MediaType may be optionally provided.
// May update BlobInfoCache, preferably after it knows for certain that a blob truly exists at a specific location.
func (s *sifImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache) (io.ReadCloser, int64, error) {
switch info.Digest {
case s.configDigest:
return ioutil.NopCloser(bytes.NewBuffer(s.config)), int64(len(s.config)), nil
case s.layerDigest:
reader, err := os.Open(s.layerFile)
if err != nil {
return nil, -1, fmt.Errorf("opening %q: %w", s.layerFile, err)
}
return reader, s.layerSize, nil
default:
return nil, -1, fmt.Errorf("no blob with digest %q found", info.Digest.String())
}
}
// GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available).
// It may use a remote (= slow) service.
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
func (s *sifImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
if instanceDigest != nil {
return nil, "", errors.New("manifest lists are not supported by the sif transport")
}
return s.manifest, imgspecv1.MediaTypeImageManifest, nil
}
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for
// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
// (e.g. if the source never returns manifest lists).
func (s *sifImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
if instanceDigest != nil {
return nil, errors.New("manifest lists are not supported by the sif transport")
}
return nil, nil
}
// LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer
// blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob()
// to read the image's layers.
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for
// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
// (e.g. if the source never returns manifest lists).
// The Digest field is guaranteed to be provided; Size may be -1.
// WARNING: The list may contain duplicates, and they are semantically relevant.
func (s *sifImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) {
return nil, nil
}

164
vendor/github.com/containers/image/v5/sif/transport.go generated vendored Normal file
View File

@ -0,0 +1,164 @@
package sif
import (
"context"
"errors"
"fmt"
"path/filepath"
"strings"
"github.com/containers/image/v5/directory/explicitfilepath"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/image"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/types"
)
func init() {
transports.Register(Transport)
}
// Transport is an ImageTransport for SIF images.
var Transport = sifTransport{}
type sifTransport struct{}
func (t sifTransport) Name() string {
return "sif"
}
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference.
func (t sifTransport) ParseReference(reference string) (types.ImageReference, error) {
return NewReference(reference)
}
// ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys
// (i.e. a valid PolicyConfigurationIdentity() or PolicyConfigurationNamespaces() return value).
// It is acceptable to allow an invalid value which will never be matched, it can "only" cause user confusion.
// scope passed to this function will not be "", that value is always allowed.
func (t sifTransport) ValidatePolicyConfigurationScope(scope string) error {
if !strings.HasPrefix(scope, "/") {
return fmt.Errorf("Invalid scope %s: Must be an absolute path", scope)
}
// Refuse also "/", otherwise "/" and "" would have the same semantics,
// and "" could be unexpectedly shadowed by the "/" entry.
if scope == "/" {
return errors.New(`Invalid scope "/": Use the generic default scope ""`)
}
cleaned := filepath.Clean(scope)
if cleaned != scope {
return fmt.Errorf(`Invalid scope %s: Uses non-canonical format, perhaps try %s`, scope, cleaned)
}
return nil
}
// sifReference is an ImageReference for SIF images.
type sifReference struct {
// Note that the interpretation of paths below depends on the underlying filesystem state, which may change under us at any time!
// Either of the paths may point to a different, or no, inode over time. resolvedFile may contain symbolic links, and so on.
// Generally we follow the intent of the user, and use the "file" member for filesystem operations (e.g. the user can use a relative path to avoid
// being exposed to symlinks and renames in the parent directories to the working directory).
// (But in general, we make no attempt to be completely safe against concurrent hostile filesystem modifications.)
file string // As specified by the user. May be relative, contain symlinks, etc.
resolvedFile string // Absolute file path with no symlinks, at least at the time of its creation. Primarily used for policy namespaces.
}
// There is no sif.ParseReference because it is rather pointless.
// Callers who need a transport-independent interface will go through
// sifTransport.ParseReference; callers who intentionally deal with SIF files
// can use sif.NewReference.
// NewReference returns an image file reference for a specified path.
func NewReference(file string) (types.ImageReference, error) {
// We do not expose an API supplying the resolvedFile; we could, but recomputing it
// is generally cheap enough that we prefer being confident about the properties of resolvedFile.
resolved, err := explicitfilepath.ResolvePathToFullyExplicit(file)
if err != nil {
return nil, err
}
return sifReference{file: file, resolvedFile: resolved}, nil
}
func (ref sifReference) Transport() types.ImageTransport {
return Transport
}
// StringWithinTransport returns a string representation of the reference, which MUST be such that
// reference.Transport().ParseReference(reference.StringWithinTransport()) returns an equivalent reference.
// NOTE: The returned string is not promised to be equal to the original input to ParseReference;
// e.g. default attribute values omitted by the user may be filled in in the return value, or vice versa.
// WARNING: Do not use the return value in the UI to describe an image, it does not contain the Transport().Name() prefix;
// instead, see transports.ImageName().
func (ref sifReference) StringWithinTransport() string {
return ref.file
}
// DockerReference returns a Docker reference associated with this reference
// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent,
// not e.g. after redirect or alias processing), or nil if unknown/not applicable.
func (ref sifReference) DockerReference() reference.Named {
return nil
}
// PolicyConfigurationIdentity returns a string representation of the reference, suitable for policy lookup.
// This MUST reflect user intent, not e.g. after processing of third-party redirects or aliases;
// The value SHOULD be fully explicit about its semantics, with no hidden defaults, AND canonical
// (i.e. various references with exactly the same semantics should return the same configuration identity)
// It is fine for the return value to be equal to StringWithinTransport(), and it is desirable but
// not required/guaranteed that it will be a valid input to Transport().ParseReference().
// Returns "" if configuration identities for these references are not supported.
func (ref sifReference) PolicyConfigurationIdentity() string {
return ref.resolvedFile
}
// PolicyConfigurationNamespaces returns a list of other policy configuration namespaces to search
// for if explicit configuration for PolicyConfigurationIdentity() is not set. The list will be processed
// in order, terminating on first match, and an implicit "" is always checked at the end.
// It is STRONGLY recommended for the first element, if any, to be a prefix of PolicyConfigurationIdentity(),
// and each following element to be a prefix of the element preceding it.
func (ref sifReference) PolicyConfigurationNamespaces() []string {
res := []string{}
path := ref.resolvedFile
for {
lastSlash := strings.LastIndex(path, "/")
if lastSlash == -1 || lastSlash == 0 {
break
}
path = path[:lastSlash]
res = append(res, path)
}
// Note that we do not include "/"; it is redundant with the default "" global default,
// and rejected by sifTransport.ValidatePolicyConfigurationScope above.
return res
}
// NewImage returns a types.ImageCloser for this reference, possibly specialized for this ImageTransport.
// The caller must call .Close() on the returned ImageCloser.
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
func (ref sifReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
src, err := newImageSource(ctx, sys, ref)
if err != nil {
return nil, err
}
return image.FromSource(ctx, sys, src)
}
// NewImageSource returns a types.ImageSource for this reference.
// The caller must call .Close() on the returned ImageSource.
func (ref sifReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
return newImageSource(ctx, sys, ref)
}
// NewImageDestination returns a types.ImageDestination for this reference.
// The caller must call .Close() on the returned ImageDestination.
func (ref sifReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
return nil, errors.New(`"sif:" locations can only be read from, not written to`)
}
// DeleteImage deletes the named image from the registry, if supported.
func (ref sifReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
return errors.New("Deleting images not implemented for sif: images")
}

View File

@ -3,22 +3,46 @@
package signature
import (
"errors"
"fmt"
"strings"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
"github.com/opencontainers/go-digest"
)
// SignOptions includes optional parameters for signing container images.
type SignOptions struct {
// Passphare to use when signing with the key identity.
Passphrase string
}
// SignDockerManifest returns a signature for manifest as the specified dockerReference,
// using mech and keyIdentity.
func SignDockerManifest(m []byte, dockerReference string, mech SigningMechanism, keyIdentity string) ([]byte, error) {
// using mech and keyIdentity, and the specified options.
func SignDockerManifestWithOptions(m []byte, dockerReference string, mech SigningMechanism, keyIdentity string, options *SignOptions) ([]byte, error) {
manifestDigest, err := manifest.Digest(m)
if err != nil {
return nil, err
}
sig := newUntrustedSignature(manifestDigest, dockerReference)
return sig.sign(mech, keyIdentity)
var passphrase string
if options != nil {
passphrase = options.Passphrase
// The gpgme implementation cant use passphrase with \n; reject it here for consistent behavior.
if strings.Contains(passphrase, "\n") {
return nil, errors.New("invalid passphrase: must not contain a line break")
}
}
return sig.sign(mech, keyIdentity, passphrase)
}
// SignDockerManifest returns a signature for manifest as the specified dockerReference,
// using mech and keyIdentity.
func SignDockerManifest(m []byte, dockerReference string, mech SigningMechanism, keyIdentity string) ([]byte, error) {
return SignDockerManifestWithOptions(m, dockerReference, mech, keyIdentity, nil)
}
// VerifyDockerManifestSignature checks that unverifiedSignature uses expectedKeyIdentity to sign unverifiedManifest as expectedDockerReference,

View File

@ -18,8 +18,6 @@ import (
// SigningMechanism abstracts a way to sign binary blobs and verify their signatures.
// Each mechanism should eventually be closed by calling Close().
// FIXME: Eventually expand on keyIdentity (namespace them between mechanisms to
// eliminate ambiguities, support CA signatures and perhaps other key properties)
type SigningMechanism interface {
// Close removes resources associated with the mechanism, if any.
Close() error
@ -38,6 +36,15 @@ type SigningMechanism interface {
UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error)
}
// signingMechanismWithPassphrase is an internal extension of SigningMechanism.
type signingMechanismWithPassphrase interface {
SigningMechanism
// Sign creates a (non-detached) signature of input using keyIdentity and passphrase.
// Fails with a SigningNotSupportedError if the mechanism does not support signing.
SignWithPassphrase(input []byte, keyIdentity string, passphrase string) ([]byte, error)
}
// SigningNotSupportedError is returned when trying to sign using a mechanism which does not support that.
type SigningNotSupportedError string

View File

@ -5,11 +5,12 @@ package signature
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"github.com/mtrmac/gpgme"
"github.com/proglottis/gpgme"
)
// A GPG/OpenPGP signing mechanism, implemented using gpgme.
@ -20,7 +21,7 @@ type gpgmeSigningMechanism struct {
// newGPGSigningMechanismInDirectory returns a new GPG/OpenPGP signing mechanism, using optionalDir if not empty.
// The caller must call .Close() on the returned SigningMechanism.
func newGPGSigningMechanismInDirectory(optionalDir string) (SigningMechanism, error) {
func newGPGSigningMechanismInDirectory(optionalDir string) (signingMechanismWithPassphrase, error) {
ctx, err := newGPGMEContext(optionalDir)
if err != nil {
return nil, err
@ -35,7 +36,7 @@ func newGPGSigningMechanismInDirectory(optionalDir string) (SigningMechanism, er
// recognizes _only_ public keys from the supplied blob, and returns the identities
// of these keys.
// The caller must call .Close() on the returned SigningMechanism.
func newEphemeralGPGSigningMechanism(blob []byte) (SigningMechanism, []string, error) {
func newEphemeralGPGSigningMechanism(blob []byte) (signingMechanismWithPassphrase, []string, error) {
dir, err := ioutil.TempDir("", "containers-ephemeral-gpg-")
if err != nil {
return nil, nil, err
@ -117,9 +118,9 @@ func (m *gpgmeSigningMechanism) SupportsSigning() error {
return nil
}
// Sign creates a (non-detached) signature of input using keyIdentity.
// Sign creates a (non-detached) signature of input using keyIdentity and passphrase.
// Fails with a SigningNotSupportedError if the mechanism does not support signing.
func (m *gpgmeSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte, error) {
func (m *gpgmeSigningMechanism) SignWithPassphrase(input []byte, keyIdentity string, passphrase string) ([]byte, error) {
key, err := m.ctx.GetKey(keyIdentity, true)
if err != nil {
return nil, err
@ -133,12 +134,38 @@ func (m *gpgmeSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte,
if err != nil {
return nil, err
}
if passphrase != "" {
// Callback to write the passphrase to the specified file descriptor.
callback := func(uidHint string, prevWasBad bool, gpgmeFD *os.File) error {
if prevWasBad {
return errors.New("bad passphrase")
}
_, err := gpgmeFD.WriteString(passphrase + "\n")
return err
}
if err := m.ctx.SetCallback(callback); err != nil {
return nil, fmt.Errorf("setting gpgme passphrase callback: %w", err)
}
// Loopback mode will use the callback instead of prompting the user.
if err := m.ctx.SetPinEntryMode(gpgme.PinEntryLoopback); err != nil {
return nil, fmt.Errorf("setting gpgme pinentry mode: %w", err)
}
}
if err = m.ctx.Sign([]*gpgme.Key{key}, inputData, sigData, gpgme.SigModeNormal); err != nil {
return nil, err
}
return sigBuffer.Bytes(), nil
}
// Sign creates a (non-detached) signature of input using keyIdentity.
// Fails with a SigningNotSupportedError if the mechanism does not support signing.
func (m *gpgmeSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte, error) {
return m.SignWithPassphrase(input, keyIdentity, "")
}
// Verify parses unverifiedSignature and returns the content and the signer's identity
func (m *gpgmeSigningMechanism) Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error) {
signedBuffer := bytes.Buffer{}

View File

@ -30,7 +30,7 @@ type openpgpSigningMechanism struct {
// newGPGSigningMechanismInDirectory returns a new GPG/OpenPGP signing mechanism, using optionalDir if not empty.
// The caller must call .Close() on the returned SigningMechanism.
func newGPGSigningMechanismInDirectory(optionalDir string) (SigningMechanism, error) {
func newGPGSigningMechanismInDirectory(optionalDir string) (signingMechanismWithPassphrase, error) {
m := &openpgpSigningMechanism{
keyring: openpgp.EntityList{},
}
@ -61,7 +61,7 @@ func newGPGSigningMechanismInDirectory(optionalDir string) (SigningMechanism, er
// recognizes _only_ public keys from the supplied blob, and returns the identities
// of these keys.
// The caller must call .Close() on the returned SigningMechanism.
func newEphemeralGPGSigningMechanism(blob []byte) (SigningMechanism, []string, error) {
func newEphemeralGPGSigningMechanism(blob []byte) (signingMechanismWithPassphrase, []string, error) {
m := &openpgpSigningMechanism{
keyring: openpgp.EntityList{},
}
@ -110,10 +110,16 @@ func (m *openpgpSigningMechanism) SupportsSigning() error {
// Sign creates a (non-detached) signature of input using keyIdentity.
// Fails with a SigningNotSupportedError if the mechanism does not support signing.
func (m *openpgpSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte, error) {
func (m *openpgpSigningMechanism) SignWithPassphrase(input []byte, keyIdentity string, passphrase string) ([]byte, error) {
return nil, SigningNotSupportedError("signing is not supported in github.com/containers/image built with the containers_image_openpgp build tag")
}
// Sign creates a (non-detached) signature of input using keyIdentity.
// Fails with a SigningNotSupportedError if the mechanism does not support signing.
func (m *openpgpSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte, error) {
return m.SignWithPassphrase(input, keyIdentity, "")
}
// Verify parses unverifiedSignature and returns the content and the signer's identity
func (m *openpgpSigningMechanism) Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error) {
md, err := openpgp.ReadMessage(bytes.NewReader(unverifiedSignature), m.keyring, nil, nil)

View File

@ -190,12 +190,20 @@ func (s *untrustedSignature) strictUnmarshalJSON(data []byte) error {
// of the system just because it is a private key — actually the presence of a private key
// on the system increases the likelihood of an a successful attack on that private key
// on that particular system.)
func (s untrustedSignature) sign(mech SigningMechanism, keyIdentity string) ([]byte, error) {
func (s untrustedSignature) sign(mech SigningMechanism, keyIdentity string, passphrase string) ([]byte, error) {
json, err := json.Marshal(s)
if err != nil {
return nil, err
}
if newMech, ok := mech.(signingMechanismWithPassphrase); ok {
return newMech.SignWithPassphrase(json, keyIdentity, passphrase)
}
if passphrase != "" {
return nil, errors.New("signing mechanism does not support passphrases")
}
return mech.Sign(json, keyIdentity)
}

View File

@ -12,7 +12,9 @@ import (
_ "github.com/containers/image/v5/oci/archive"
_ "github.com/containers/image/v5/oci/layout"
_ "github.com/containers/image/v5/openshift"
_ "github.com/containers/image/v5/sif"
_ "github.com/containers/image/v5/tarball"
// The ostree transport is registered by ostree*.go
// The storage transport is registered by storage*.go
"github.com/containers/image/v5/transports"

View File

@ -6,7 +6,7 @@ const (
// VersionMajor is for an API incompatible changes
VersionMajor = 5
// VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 18
VersionMinor = 19
// VersionPatch is for backwards-compatible bug fixes
VersionPatch = 0