chunked: validate converted images
validate that the retrieved data for converted images matches the expected digest. Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
This commit is contained in:
parent
7ca28f6713
commit
fa794cb997
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/containers/storage/pkg/chunked/compressor"
|
||||
"github.com/containers/storage/pkg/ioutils"
|
||||
"github.com/containers/storage/pkg/mflag"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -155,7 +156,10 @@ func applyDiffUsingStagingDirectory(flags *mflag.FlagSet, action string, m stora
|
|||
return 1, err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(compressor, tr); err != nil {
|
||||
digesterCompressed := digest.Canonical.Digester()
|
||||
r := io.TeeReader(tr, digesterCompressed.Hash())
|
||||
|
||||
if _, err := io.Copy(compressor, r); err != nil {
|
||||
return 1, err
|
||||
}
|
||||
if err := compressor.Close(); err != nil {
|
||||
|
|
@ -171,7 +175,7 @@ func applyDiffUsingStagingDirectory(flags *mflag.FlagSet, action string, m stora
|
|||
file: tar,
|
||||
}
|
||||
|
||||
differ, err := chunked.GetDiffer(context.Background(), m, size, metadata, &fetcher)
|
||||
differ, err := chunked.GetDiffer(context.Background(), m, digesterCompressed.Digest(), size, metadata, &fetcher)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,11 @@ type chunkedDiffer struct {
|
|||
// the layer are trusted and should not be validated.
|
||||
skipValidation bool
|
||||
|
||||
// blobDigest is the digest of the whole compressed layer. It is used if
|
||||
// convertToZstdChunked to validate a layer when it is converted since there
|
||||
// is no TOC referenced by the manifest.
|
||||
blobDigest digest.Digest
|
||||
|
||||
blobSize int64
|
||||
|
||||
storeOpts *types.StoreOptions
|
||||
|
|
@ -188,33 +193,7 @@ func (f *seekableFile) GetBlobAt(chunks []ImageSourceChunk) (chan io.ReadCloser,
|
|||
return streams, errs, nil
|
||||
}
|
||||
|
||||
func convertTarToZstdChunked(destDirectory string, blobSize int64, iss ImageSourceSeekable) (*seekableFile, digest.Digest, map[string]string, error) {
|
||||
var payload io.ReadCloser
|
||||
var streams chan io.ReadCloser
|
||||
var errs chan error
|
||||
var err error
|
||||
|
||||
chunksToRequest := []ImageSourceChunk{
|
||||
{
|
||||
Offset: 0,
|
||||
Length: uint64(blobSize),
|
||||
},
|
||||
}
|
||||
|
||||
streams, errs, err = iss.GetBlobAt(chunksToRequest)
|
||||
if err != nil {
|
||||
return nil, "", nil, err
|
||||
}
|
||||
select {
|
||||
case p := <-streams:
|
||||
payload = p
|
||||
case err := <-errs:
|
||||
return nil, "", nil, err
|
||||
}
|
||||
if payload == nil {
|
||||
return nil, "", nil, errors.New("invalid stream returned")
|
||||
}
|
||||
|
||||
func convertTarToZstdChunked(destDirectory string, payload *os.File) (*seekableFile, digest.Digest, map[string]string, error) {
|
||||
diff, err := archive.DecompressStream(payload)
|
||||
if err != nil {
|
||||
return nil, "", nil, err
|
||||
|
|
@ -235,10 +214,8 @@ func convertTarToZstdChunked(destDirectory string, blobSize int64, iss ImageSour
|
|||
return nil, "", nil, err
|
||||
}
|
||||
|
||||
digester := digest.Canonical.Digester()
|
||||
hash := digester.Hash()
|
||||
|
||||
if _, err := io.Copy(io.MultiWriter(chunked, hash), diff); err != nil {
|
||||
convertedOutputDigester := digest.Canonical.Digester()
|
||||
if _, err := io.Copy(io.MultiWriter(chunked, convertedOutputDigester.Hash()), diff); err != nil {
|
||||
f.Close()
|
||||
return nil, "", nil, err
|
||||
}
|
||||
|
|
@ -249,11 +226,12 @@ func convertTarToZstdChunked(destDirectory string, blobSize int64, iss ImageSour
|
|||
is := seekableFile{
|
||||
file: f,
|
||||
}
|
||||
return &is, digester.Digest(), newAnnotations, nil
|
||||
|
||||
return &is, convertedOutputDigester.Digest(), newAnnotations, nil
|
||||
}
|
||||
|
||||
// GetDiffer returns a differ than can be used with ApplyDiffWithDiffer.
|
||||
func GetDiffer(ctx context.Context, store storage.Store, blobSize int64, annotations map[string]string, iss ImageSourceSeekable) (graphdriver.Differ, error) {
|
||||
func GetDiffer(ctx context.Context, store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable) (graphdriver.Differ, error) {
|
||||
storeOpts, err := types.DefaultStoreOptions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -273,10 +251,10 @@ func GetDiffer(ctx context.Context, store storage.Store, blobSize int64, annotat
|
|||
return makeEstargzChunkedDiffer(ctx, store, blobSize, annotations, iss, &storeOpts)
|
||||
}
|
||||
|
||||
return makeConvertFromRawDiffer(ctx, store, blobSize, annotations, iss, &storeOpts)
|
||||
return makeConvertFromRawDiffer(ctx, store, blobDigest, blobSize, annotations, iss, &storeOpts)
|
||||
}
|
||||
|
||||
func makeConvertFromRawDiffer(ctx context.Context, store storage.Store, blobSize int64, annotations map[string]string, iss ImageSourceSeekable, storeOpts *types.StoreOptions) (*chunkedDiffer, error) {
|
||||
func makeConvertFromRawDiffer(ctx context.Context, store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable, storeOpts *types.StoreOptions) (*chunkedDiffer, error) {
|
||||
if !parseBooleanPullOption(storeOpts, "convert_images", false) {
|
||||
return nil, errors.New("convert_images not configured")
|
||||
}
|
||||
|
|
@ -287,6 +265,7 @@ func makeConvertFromRawDiffer(ctx context.Context, store storage.Store, blobSize
|
|||
}
|
||||
|
||||
return &chunkedDiffer{
|
||||
blobDigest: blobDigest,
|
||||
blobSize: blobSize,
|
||||
convertToZstdChunked: true,
|
||||
copyBuffer: makeCopyBuffer(),
|
||||
|
|
@ -1498,6 +1477,43 @@ func makeEntriesFlat(mergedEntries []internal.FileMetadata) ([]internal.FileMeta
|
|||
return new, nil
|
||||
}
|
||||
|
||||
func (c *chunkedDiffer) copyAllBlobToFile(destination *os.File) (digest.Digest, error) {
|
||||
var payload io.ReadCloser
|
||||
var streams chan io.ReadCloser
|
||||
var errs chan error
|
||||
var err error
|
||||
|
||||
chunksToRequest := []ImageSourceChunk{
|
||||
{
|
||||
Offset: 0,
|
||||
Length: uint64(c.blobSize),
|
||||
},
|
||||
}
|
||||
|
||||
streams, errs, err = c.stream.GetBlobAt(chunksToRequest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
select {
|
||||
case p := <-streams:
|
||||
payload = p
|
||||
case err := <-errs:
|
||||
return "", err
|
||||
}
|
||||
if payload == nil {
|
||||
return "", errors.New("invalid stream returned")
|
||||
}
|
||||
|
||||
originalRawDigester := digest.Canonical.Digester()
|
||||
|
||||
r := io.TeeReader(payload, originalRawDigester.Hash())
|
||||
|
||||
// copy the entire tarball and compute its digest
|
||||
_, err = io.Copy(destination, r)
|
||||
|
||||
return originalRawDigester.Digest(), err
|
||||
}
|
||||
|
||||
func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, differOpts *graphdriver.DifferOptions) (graphdriver.DriverWithDifferOutput, error) {
|
||||
defer c.layersCache.release()
|
||||
defer func() {
|
||||
|
|
@ -1510,7 +1526,32 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
|
|||
stream := c.stream
|
||||
|
||||
if c.convertToZstdChunked {
|
||||
fileSource, diffID, annotations, err := convertTarToZstdChunked(dest, c.blobSize, c.stream)
|
||||
fd, err := unix.Open(dest, unix.O_TMPFILE|unix.O_RDWR|unix.O_CLOEXEC, 0o600)
|
||||
if err != nil {
|
||||
return graphdriver.DriverWithDifferOutput{}, err
|
||||
}
|
||||
blobFile := os.NewFile(uintptr(fd), "blob-file")
|
||||
defer func() {
|
||||
if blobFile != nil {
|
||||
blobFile.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// calculate the checksum before accessing the file.
|
||||
compressedDigest, err := c.copyAllBlobToFile(blobFile)
|
||||
if err != nil {
|
||||
return graphdriver.DriverWithDifferOutput{}, err
|
||||
}
|
||||
|
||||
if compressedDigest != c.blobDigest {
|
||||
return graphdriver.DriverWithDifferOutput{}, fmt.Errorf("invalid digest to convert: expected %q, got %q", c.blobDigest, compressedDigest)
|
||||
}
|
||||
|
||||
if _, err := blobFile.Seek(0, io.SeekStart); err != nil {
|
||||
return graphdriver.DriverWithDifferOutput{}, err
|
||||
}
|
||||
|
||||
fileSource, diffID, annotations, err := convertTarToZstdChunked(dest, blobFile)
|
||||
if err != nil {
|
||||
return graphdriver.DriverWithDifferOutput{}, err
|
||||
}
|
||||
|
|
@ -1518,6 +1559,10 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
|
|||
// need to keep it open until the entire file is processed.
|
||||
defer fileSource.Close()
|
||||
|
||||
// Close the file so that the file descriptor is released and the file is deleted.
|
||||
blobFile.Close()
|
||||
blobFile = nil
|
||||
|
||||
manifest, tarSplit, tocOffset, err := readZstdChunkedManifest(fileSource, c.blobSize, annotations)
|
||||
if err != nil {
|
||||
return graphdriver.DriverWithDifferOutput{}, fmt.Errorf("read zstd:chunked manifest: %w", err)
|
||||
|
|
@ -1530,6 +1575,7 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
|
|||
c.fileType = fileTypeZstdChunked
|
||||
c.manifest = manifest
|
||||
c.tarSplit = tarSplit
|
||||
|
||||
// since we retrieved the whole file and it was validated, use the diffID instead of the TOC digest.
|
||||
c.contentDigest = diffID
|
||||
c.tocOffset = tocOffset
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ import (
|
|||
|
||||
storage "github.com/containers/storage"
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// GetDiffer returns a differ than can be used with ApplyDiffWithDiffer.
|
||||
func GetDiffer(ctx context.Context, store storage.Store, blobSize int64, annotations map[string]string, iss ImageSourceSeekable) (graphdriver.Differ, error) {
|
||||
func GetDiffer(ctx context.Context, store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable) (graphdriver.Differ, error) {
|
||||
return nil, errors.New("format not supported on this system")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue