github.com/pdmccormick/importable-docker-buildx@v0.0.0-20240426161518-e47091289030/util/imagetools/imagetools_helpers_test.go (about)

     1  package imagetools
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"strings"
    10  
    11  	"github.com/containerd/containerd/remotes"
    12  	intoto "github.com/in-toto/in-toto-golang/in_toto"
    13  	slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
    14  	"github.com/opencontainers/go-digest"
    15  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    16  	v1 "github.com/opencontainers/image-spec/specs-go/v1"
    17  )
    18  
    19  type attestationType int
    20  
    21  const (
    22  	plainSpdx             attestationType = 0
    23  	dsseEmbeded           attestationType = 1
    24  	plainSpdxAndDSSEEmbed attestationType = 2
    25  )
    26  
    27  type mockFetcher struct {
    28  }
    29  
    30  type mockResolver struct {
    31  	fetcher remotes.Fetcher
    32  	pusher  remotes.Pusher
    33  }
    34  
    35  var manifests = make(map[digest.Digest]manifest)
    36  var indexes = make(map[digest.Digest]index)
    37  
    38  func (f mockFetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) {
    39  	switch desc.MediaType {
    40  	case ocispec.MediaTypeImageIndex:
    41  		reader := io.NopCloser(strings.NewReader(indexes[desc.Digest].desc.Annotations["test_content"]))
    42  		return reader, nil
    43  	case ocispec.MediaTypeImageManifest:
    44  		reader := io.NopCloser(strings.NewReader(manifests[desc.Digest].desc.Annotations["test_content"]))
    45  		return reader, nil
    46  	default:
    47  		reader := io.NopCloser(strings.NewReader(desc.Annotations["test_content"]))
    48  		return reader, nil
    49  	}
    50  
    51  }
    52  
    53  func (r mockResolver) Resolve(ctx context.Context, ref string) (name string, desc ocispec.Descriptor, err error) {
    54  	d := digest.Digest(strings.ReplaceAll(ref, "docker.io/library/test@", ""))
    55  	return string(d), indexes[d].desc, nil
    56  }
    57  
    58  func (r mockResolver) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) {
    59  	return r.fetcher, nil
    60  }
    61  
    62  func (r mockResolver) Pusher(ctx context.Context, ref string) (remotes.Pusher, error) {
    63  	return r.pusher, nil
    64  }
    65  
    66  func getMockResolver() remotes.Resolver {
    67  	resolver := mockResolver{
    68  		fetcher: mockFetcher{},
    69  	}
    70  
    71  	return resolver
    72  }
    73  
    74  func getImageNoAttestation() *result {
    75  	return getImageFromManifests(getBaseManifests())
    76  }
    77  
    78  func getImageWithAttestation(t attestationType) *result {
    79  	manifestList := getBaseManifests()
    80  
    81  	objManifest := ocispec.Manifest{
    82  		MediaType: v1.MediaTypeImageManifest,
    83  		Layers:    getAttestationLayers(t),
    84  		Annotations: map[string]string{
    85  			"platform": "linux/amd64",
    86  		},
    87  	}
    88  	jsonContent, _ := json.Marshal(objManifest)
    89  	jsonString := string(jsonContent)
    90  	d := digest.FromString(jsonString)
    91  
    92  	manifestList[d] = manifest{
    93  		desc: ocispec.Descriptor{
    94  			MediaType: v1.MediaTypeImageManifest,
    95  			Digest:    d,
    96  			Size:      int64(len(jsonString)),
    97  			Annotations: map[string]string{
    98  				"vnd.docker.reference.digest": string(getManifestDigestForArch(manifestList, "linux", "amd64")),
    99  				"vnd.docker.reference.type":   "attestation-manifest",
   100  				"test_content":                jsonString,
   101  			},
   102  			Platform: &v1.Platform{
   103  				Architecture: "unknown",
   104  				OS:           "unknown",
   105  			},
   106  		},
   107  		manifest: objManifest,
   108  	}
   109  
   110  	objManifest = ocispec.Manifest{
   111  		MediaType: v1.MediaTypeImageManifest,
   112  		Layers:    getAttestationLayers(t),
   113  		Annotations: map[string]string{
   114  			"platform": "linux/arm64",
   115  		},
   116  	}
   117  	jsonContent, _ = json.Marshal(objManifest)
   118  	jsonString = string(jsonContent)
   119  	d = digest.FromString(jsonString)
   120  	manifestList[d] = manifest{
   121  		desc: ocispec.Descriptor{
   122  			MediaType: v1.MediaTypeImageManifest,
   123  			Digest:    d,
   124  			Size:      int64(len(jsonString)),
   125  			Annotations: map[string]string{
   126  				"vnd.docker.reference.digest": string(getManifestDigestForArch(manifestList, "linux", "arm64")),
   127  				"vnd.docker.reference.type":   "attestation-manifest",
   128  				"test_content":                jsonString,
   129  			},
   130  			Platform: &v1.Platform{
   131  				Architecture: "unknown",
   132  				OS:           "unknown",
   133  			},
   134  		},
   135  	}
   136  
   137  	return getImageFromManifests(manifestList)
   138  }
   139  
   140  func getImageFromManifests(manifests map[digest.Digest]manifest) *result {
   141  	r := &result{
   142  		indexes:   make(map[digest.Digest]index),
   143  		manifests: manifests,
   144  		images:    make(map[string]digest.Digest),
   145  		refs:      make(map[digest.Digest][]digest.Digest),
   146  		assets:    make(map[string]asset),
   147  	}
   148  
   149  	r.images["linux/amd64"] = getManifestDigestForArch(manifests, "linux", "amd64")
   150  	r.images["linux/arm64"] = getManifestDigestForArch(manifests, "linux", "arm64")
   151  
   152  	manifestsDesc := []v1.Descriptor{}
   153  	for _, val := range manifests {
   154  		manifestsDesc = append(manifestsDesc, val.desc)
   155  	}
   156  
   157  	objIndex := v1.Index{
   158  		MediaType: v1.MediaTypeImageIndex,
   159  		Manifests: manifestsDesc,
   160  	}
   161  	jsonContent, _ := json.Marshal(objIndex)
   162  	jsonString := string(jsonContent)
   163  	d := digest.FromString(jsonString)
   164  
   165  	if _, ok := indexes[d]; !ok {
   166  		indexes[d] = index{
   167  			desc: ocispec.Descriptor{
   168  				MediaType: v1.MediaTypeImageIndex,
   169  				Digest:    d,
   170  				Size:      int64(len(jsonString)),
   171  				Annotations: map[string]string{
   172  					"test_content": jsonString,
   173  				},
   174  			},
   175  			index: objIndex,
   176  		}
   177  	}
   178  
   179  	r.indexes[d] = indexes[d]
   180  	return r
   181  }
   182  
   183  func getManifestDigestForArch(manifests map[digest.Digest]manifest, os string, arch string) digest.Digest {
   184  	for d, m := range manifests {
   185  		if m.desc.Platform.OS == os && m.desc.Platform.Architecture == arch {
   186  			return d
   187  		}
   188  	}
   189  
   190  	return digest.Digest("")
   191  }
   192  
   193  func getBaseManifests() map[digest.Digest]manifest {
   194  	if len(manifests) == 0 {
   195  		config := getConfig()
   196  		content := "amd64-content"
   197  		objManifest := ocispec.Manifest{
   198  			MediaType: v1.MediaTypeImageManifest,
   199  			Config:    config,
   200  			Layers: []v1.Descriptor{
   201  				{
   202  					MediaType: v1.MediaTypeImageLayerGzip,
   203  					Digest:    digest.FromString(content),
   204  					Size:      int64(len(content)),
   205  				},
   206  			},
   207  		}
   208  		jsonContent, _ := json.Marshal(objManifest)
   209  		jsonString := string(jsonContent)
   210  		d := digest.FromString(jsonString)
   211  
   212  		manifests[d] = manifest{
   213  			desc: ocispec.Descriptor{
   214  				MediaType: v1.MediaTypeImageManifest,
   215  				Digest:    d,
   216  				Size:      int64(len(jsonString)),
   217  				Platform: &v1.Platform{
   218  					Architecture: "amd64",
   219  					OS:           "linux",
   220  				},
   221  				Annotations: map[string]string{
   222  					"test_content": jsonString,
   223  				},
   224  			},
   225  			manifest: objManifest,
   226  		}
   227  
   228  		content = "arm64-content"
   229  		objManifest = ocispec.Manifest{
   230  			MediaType: v1.MediaTypeImageManifest,
   231  			Config:    config,
   232  			Layers: []v1.Descriptor{
   233  				{
   234  					MediaType: v1.MediaTypeImageLayerGzip,
   235  					Digest:    digest.FromString(content),
   236  					Size:      int64(len(content)),
   237  				},
   238  			},
   239  		}
   240  		jsonContent, _ = json.Marshal(objManifest)
   241  		jsonString = string(jsonContent)
   242  		d = digest.FromString(jsonString)
   243  
   244  		manifests[d] = manifest{
   245  			desc: ocispec.Descriptor{
   246  				MediaType: v1.MediaTypeImageManifest,
   247  				Digest:    d,
   248  				Size:      int64(len(jsonString)),
   249  				Platform: &v1.Platform{
   250  					Architecture: "arm64",
   251  					OS:           "linux",
   252  				},
   253  				Annotations: map[string]string{
   254  					"test_content": jsonString,
   255  				},
   256  			},
   257  			manifest: objManifest,
   258  		}
   259  	}
   260  
   261  	return manifests
   262  }
   263  
   264  func getConfig() v1.Descriptor {
   265  	config := v1.ImageConfig{
   266  		Env: []string{
   267  			"config",
   268  		},
   269  	}
   270  	jsonContent, _ := json.Marshal(config)
   271  	jsonString := string(jsonContent)
   272  	d := digest.FromString(jsonString)
   273  
   274  	return v1.Descriptor{
   275  		MediaType: ocispec.MediaTypeImageConfig,
   276  		Digest:    d,
   277  		Size:      int64(len(jsonString)),
   278  		Annotations: map[string]string{
   279  			"test_content": jsonString,
   280  		},
   281  	}
   282  }
   283  
   284  func getAttestationLayers(t attestationType) []v1.Descriptor {
   285  	layers := []v1.Descriptor{}
   286  
   287  	if t == plainSpdx || t == plainSpdxAndDSSEEmbed {
   288  		layers = append(layers, v1.Descriptor{
   289  			MediaType: inTotoGenericMime,
   290  			Digest:    digest.FromString(attestationContent),
   291  			Size:      int64(len(attestationContent)),
   292  			Annotations: map[string]string{
   293  				"in-toto.io/predicate-type": intoto.PredicateSPDX,
   294  				"test_content":              attestationContent,
   295  			},
   296  		})
   297  		layers = append(layers, v1.Descriptor{
   298  			MediaType: inTotoGenericMime,
   299  			Digest:    digest.FromString(provenanceContent),
   300  			Size:      int64(len(provenanceContent)),
   301  			Annotations: map[string]string{
   302  				"in-toto.io/predicate-type": slsa02.PredicateSLSAProvenance,
   303  				"test_content":              provenanceContent,
   304  			},
   305  		})
   306  	}
   307  
   308  	if t == dsseEmbeded || t == plainSpdxAndDSSEEmbed {
   309  		dsseAttestation := fmt.Sprintf("{\"payload\":\"%s\"}", base64.StdEncoding.EncodeToString([]byte(attestationContent)))
   310  		dsseProvenance := fmt.Sprintf("{\"payload\":\"%s\"}", base64.StdEncoding.EncodeToString([]byte(provenanceContent)))
   311  		layers = append(layers, v1.Descriptor{
   312  			MediaType: inTotoSPDXDSSEMime,
   313  			Digest:    digest.FromString(dsseAttestation),
   314  			Size:      int64(len(dsseAttestation)),
   315  			Annotations: map[string]string{
   316  				"in-toto.io/predicate-type": intoto.PredicateSPDX,
   317  				"test_content":              dsseAttestation,
   318  			},
   319  		})
   320  		layers = append(layers, v1.Descriptor{
   321  			MediaType: inTotoProvenanceDSSEMime,
   322  			Digest:    digest.FromString(dsseProvenance),
   323  			Size:      int64(len(dsseProvenance)),
   324  			Annotations: map[string]string{
   325  				"in-toto.io/predicate-type": slsa02.PredicateSLSAProvenance,
   326  				"test_content":              dsseProvenance,
   327  			},
   328  		})
   329  	}
   330  
   331  	return layers
   332  }
   333  
   334  const attestationContent = `
   335  {
   336      "_type": "https://in-toto.io/Statement/v0.1",
   337      "predicateType": "https://spdx.dev/Document",
   338      "predicate": {
   339  		"name": "sbom",
   340  		"spdxVersion": "SPDX-2.3",
   341  		"SPDXID": "SPDXRef-DOCUMENT",
   342  		"creationInfo": {
   343  			"created": "2024-01-31T16:09:05Z",
   344  			"creators": [
   345  				"Tool: buildkit-v0.11.0"
   346  			],
   347  			"licenseListVersion": "3.22"
   348  		},
   349  		"dataLicense": "CC0-1.0",
   350  		"documentNamespace": "https://example.com",
   351  		"packages": [
   352  			{
   353  				"name": "sbom",
   354  				"SPDXID": "SPDXRef-DocumentRoot-Directory-sbom",
   355  				"copyrightText": "",
   356  				"downloadLocation": "NOASSERTION",
   357  				"primaryPackagePurpose": "FILE",
   358  				"supplier": "NOASSERTION"
   359  			}
   360  		],
   361  		"relationships": [
   362  			{
   363  				"relatedSpdxElement": "SPDXRef-DocumentRoot-Directory-sbom",
   364  				"relationshipType": "DESCRIBES",
   365  				"spdxElementId": "SPDXRef-DOCUMENT"
   366  			}
   367  		]
   368  	}
   369  }
   370  `
   371  
   372  const provenanceContent = `
   373  {
   374  	"_type": "https://in-toto.io/Statement/v0.1",
   375  	"predicateType": "https://slsa.dev/provenance/v0.2",
   376  	"predicate": {
   377  		"buildType": "https://example.com/Makefile",
   378  		"builder": { 
   379  			"id": "mailto:person@example.com"
   380  		},
   381  		"invocation": {
   382  			"configSource": {
   383  				"uri": "https://example.com/example-1.2.3.tar.gz",
   384  				"digest": {"sha256": ""},
   385  				"entryPoint": "src:foo"
   386  			},
   387  			"parameters": {
   388  				"CFLAGS": "-O3"
   389  			},
   390  			"materials": [
   391  				{
   392  					"uri": "https://example.com/example-1.2.3.tar.gz",
   393  					"digest": {"sha256": ""}
   394  				}
   395  			]
   396  		}
   397  	}
   398  }
   399  `