...

Source file src/github.com/google/go-containerregistry/pkg/v1/validate/image.go

Documentation: github.com/google/go-containerregistry/pkg/v1/validate

     1  // Copyright 2018 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package validate
    16  
    17  import (
    18  	"bytes"
    19  	"errors"
    20  	"fmt"
    21  	"io"
    22  	"strings"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	v1 "github.com/google/go-containerregistry/pkg/v1"
    26  	"github.com/google/go-containerregistry/pkg/v1/partial"
    27  )
    28  
    29  // Image validates that img does not violate any invariants of the image format.
    30  func Image(img v1.Image, opt ...Option) error {
    31  	errs := []string{}
    32  	if err := validateLayers(img, opt...); err != nil {
    33  		errs = append(errs, fmt.Sprintf("validating layers: %v", err))
    34  	}
    35  
    36  	if err := validateConfig(img); err != nil {
    37  		errs = append(errs, fmt.Sprintf("validating config: %v", err))
    38  	}
    39  
    40  	if err := validateManifest(img); err != nil {
    41  		errs = append(errs, fmt.Sprintf("validating manifest: %v", err))
    42  	}
    43  
    44  	if len(errs) != 0 {
    45  		return errors.New(strings.Join(errs, "\n\n"))
    46  	}
    47  	return nil
    48  }
    49  
    50  func validateConfig(img v1.Image) error {
    51  	cn, err := img.ConfigName()
    52  	if err != nil {
    53  		return err
    54  	}
    55  
    56  	rc, err := img.RawConfigFile()
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	hash, size, err := v1.SHA256(bytes.NewReader(rc))
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	m, err := img.Manifest()
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	cf, err := img.ConfigFile()
    72  	if err != nil {
    73  		return err
    74  	}
    75  
    76  	pcf, err := v1.ParseConfigFile(bytes.NewReader(rc))
    77  	if err != nil {
    78  		return err
    79  	}
    80  
    81  	errs := []string{}
    82  	if cn != hash {
    83  		errs = append(errs, fmt.Sprintf("mismatched config digest: ConfigName()=%s, SHA256(RawConfigFile())=%s", cn, hash))
    84  	}
    85  
    86  	if want, got := m.Config.Size, size; want != got {
    87  		errs = append(errs, fmt.Sprintf("mismatched config size: Manifest.Config.Size()=%d, len(RawConfigFile())=%d", want, got))
    88  	}
    89  
    90  	if diff := cmp.Diff(pcf, cf); diff != "" {
    91  		errs = append(errs, fmt.Sprintf("mismatched config content: (-ParseConfigFile(RawConfigFile()) +ConfigFile()) %s", diff))
    92  	}
    93  
    94  	if cf.RootFS.Type != "layers" {
    95  		errs = append(errs, fmt.Sprintf("invalid ConfigFile.RootFS.Type: %q != %q", cf.RootFS.Type, "layers"))
    96  	}
    97  
    98  	if len(errs) != 0 {
    99  		return errors.New(strings.Join(errs, "\n"))
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  func validateLayers(img v1.Image, opt ...Option) error {
   106  	o := makeOptions(opt...)
   107  
   108  	layers, err := img.Layers()
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	if o.fast {
   114  		return layersExist(layers)
   115  	}
   116  
   117  	digests := []v1.Hash{}
   118  	diffids := []v1.Hash{}
   119  	udiffids := []v1.Hash{}
   120  	sizes := []int64{}
   121  	for i, layer := range layers {
   122  		cl, err := computeLayer(layer)
   123  		if errors.Is(err, io.ErrUnexpectedEOF) {
   124  			// Errored while reading tar content of layer because a header or
   125  			// content section was not the correct length. This is most likely
   126  			// due to an incomplete download or otherwise interrupted process.
   127  			m, err := img.Manifest()
   128  			if err != nil {
   129  				return fmt.Errorf("undersized layer[%d] content", i)
   130  			}
   131  			return fmt.Errorf("undersized layer[%d] content: Manifest.Layers[%d].Size=%d", i, i, m.Layers[i].Size)
   132  		}
   133  		if err != nil {
   134  			return err
   135  		}
   136  		// Compute all of these first before we call Config() and Manifest() to allow
   137  		// for lazy access e.g. for stream.Layer.
   138  		digests = append(digests, cl.digest)
   139  		diffids = append(diffids, cl.diffid)
   140  		udiffids = append(udiffids, cl.uncompressedDiffid)
   141  		sizes = append(sizes, cl.size)
   142  	}
   143  
   144  	cf, err := img.ConfigFile()
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	m, err := img.Manifest()
   150  	if err != nil {
   151  		return err
   152  	}
   153  
   154  	errs := []string{}
   155  	for i, layer := range layers {
   156  		digest, err := layer.Digest()
   157  		if err != nil {
   158  			return err
   159  		}
   160  		diffid, err := layer.DiffID()
   161  		if err != nil {
   162  			return err
   163  		}
   164  		size, err := layer.Size()
   165  		if err != nil {
   166  			return err
   167  		}
   168  		mediaType, err := layer.MediaType()
   169  		if err != nil {
   170  			return err
   171  		}
   172  
   173  		if _, err := img.LayerByDigest(digest); err != nil {
   174  			return err
   175  		}
   176  
   177  		if _, err := img.LayerByDiffID(diffid); err != nil {
   178  			return err
   179  		}
   180  
   181  		if digest != digests[i] {
   182  			errs = append(errs, fmt.Sprintf("mismatched layer[%d] digest: Digest()=%s, SHA256(Compressed())=%s", i, digest, digests[i]))
   183  		}
   184  
   185  		if m.Layers[i].Digest != digests[i] {
   186  			errs = append(errs, fmt.Sprintf("mismatched layer[%d] digest: Manifest.Layers[%d].Digest=%s, SHA256(Compressed())=%s", i, i, m.Layers[i].Digest, digests[i]))
   187  		}
   188  
   189  		if diffid != diffids[i] {
   190  			errs = append(errs, fmt.Sprintf("mismatched layer[%d] diffid: DiffID()=%s, SHA256(Gunzip(Compressed()))=%s", i, diffid, diffids[i]))
   191  		}
   192  
   193  		if diffid != udiffids[i] {
   194  			errs = append(errs, fmt.Sprintf("mismatched layer[%d] diffid: DiffID()=%s, SHA256(Uncompressed())=%s", i, diffid, udiffids[i]))
   195  		}
   196  
   197  		if cf.RootFS.DiffIDs[i] != diffids[i] {
   198  			errs = append(errs, fmt.Sprintf("mismatched layer[%d] diffid: ConfigFile.RootFS.DiffIDs[%d]=%s, SHA256(Gunzip(Compressed()))=%s", i, i, cf.RootFS.DiffIDs[i], diffids[i]))
   199  		}
   200  
   201  		if size != sizes[i] {
   202  			errs = append(errs, fmt.Sprintf("mismatched layer[%d] size: Size()=%d, len(Compressed())=%d", i, size, sizes[i]))
   203  		}
   204  
   205  		if m.Layers[i].Size != sizes[i] {
   206  			errs = append(errs, fmt.Sprintf("mismatched layer[%d] size: Manifest.Layers[%d].Size=%d, len(Compressed())=%d", i, i, m.Layers[i].Size, sizes[i]))
   207  		}
   208  
   209  		if m.Layers[i].MediaType != mediaType {
   210  			errs = append(errs, fmt.Sprintf("mismatched layer[%d] mediaType: Manifest.Layers[%d].MediaType=%s, layer.MediaType()=%s", i, i, m.Layers[i].MediaType, mediaType))
   211  		}
   212  	}
   213  	if len(errs) != 0 {
   214  		return errors.New(strings.Join(errs, "\n"))
   215  	}
   216  
   217  	return nil
   218  }
   219  
   220  func validateManifest(img v1.Image) error {
   221  	digest, err := img.Digest()
   222  	if err != nil {
   223  		return err
   224  	}
   225  
   226  	size, err := img.Size()
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	rm, err := img.RawManifest()
   232  	if err != nil {
   233  		return err
   234  	}
   235  
   236  	hash, _, err := v1.SHA256(bytes.NewReader(rm))
   237  	if err != nil {
   238  		return err
   239  	}
   240  
   241  	m, err := img.Manifest()
   242  	if err != nil {
   243  		return err
   244  	}
   245  
   246  	pm, err := v1.ParseManifest(bytes.NewReader(rm))
   247  	if err != nil {
   248  		return err
   249  	}
   250  
   251  	errs := []string{}
   252  	if digest != hash {
   253  		errs = append(errs, fmt.Sprintf("mismatched manifest digest: Digest()=%s, SHA256(RawManifest())=%s", digest, hash))
   254  	}
   255  
   256  	if diff := cmp.Diff(pm, m); diff != "" {
   257  		errs = append(errs, fmt.Sprintf("mismatched manifest content: (-ParseManifest(RawManifest()) +Manifest()) %s", diff))
   258  	}
   259  
   260  	if size != int64(len(rm)) {
   261  		errs = append(errs, fmt.Sprintf("mismatched manifest size: Size()=%d, len(RawManifest())=%d", size, len(rm)))
   262  	}
   263  
   264  	if len(errs) != 0 {
   265  		return errors.New(strings.Join(errs, "\n"))
   266  	}
   267  
   268  	return nil
   269  }
   270  
   271  func layersExist(layers []v1.Layer) error {
   272  	errs := []string{}
   273  	for _, layer := range layers {
   274  		ok, err := partial.Exists(layer)
   275  		if err != nil {
   276  			errs = append(errs, err.Error())
   277  		}
   278  		if !ok {
   279  			errs = append(errs, "layer does not exist")
   280  		}
   281  	}
   282  
   283  	if len(errs) != 0 {
   284  		return errors.New(strings.Join(errs, "\n"))
   285  	}
   286  
   287  	return nil
   288  }
   289  

View as plain text