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 `