mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-07-09 21:17:09 +08:00
Add unit test for SBOM and Provenance scanning
Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>
This commit is contained in:
255
util/imagetools/imagetools_helpers_test.go
Normal file
255
util/imagetools/imagetools_helpers_test.go
Normal file
@ -0,0 +1,255 @@
|
||||
package imagetools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/remotes"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
type attestationType int
|
||||
|
||||
const (
|
||||
plainSpdx attestationType = 0
|
||||
dsseEmbeded attestationType = 1
|
||||
plainSpdxAndDSSEEmbed attestationType = 2
|
||||
)
|
||||
|
||||
type mockFetcher struct {
|
||||
}
|
||||
|
||||
type mockResolver struct {
|
||||
fetcher remotes.Fetcher
|
||||
pusher remotes.Pusher
|
||||
}
|
||||
|
||||
func (f mockFetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) {
|
||||
reader := io.NopCloser(strings.NewReader(desc.Annotations["test_content"]))
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
func (r mockResolver) Resolve(ctx context.Context, ref string) (name string, desc ocispec.Descriptor, err error) {
|
||||
return "", ocispec.Descriptor{}, nil
|
||||
}
|
||||
|
||||
func (r mockResolver) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) {
|
||||
return r.fetcher, nil
|
||||
}
|
||||
|
||||
func (r mockResolver) Pusher(ctx context.Context, ref string) (remotes.Pusher, error) {
|
||||
return r.pusher, nil
|
||||
}
|
||||
|
||||
func getMockResolver() remotes.Resolver {
|
||||
resolver := mockResolver{
|
||||
fetcher: mockFetcher{},
|
||||
}
|
||||
|
||||
return resolver
|
||||
}
|
||||
|
||||
func getImageNoAttestation() *result {
|
||||
r := &result{
|
||||
indexes: make(map[digest.Digest]index),
|
||||
manifests: make(map[digest.Digest]manifest),
|
||||
images: make(map[string]digest.Digest),
|
||||
refs: make(map[digest.Digest][]digest.Digest),
|
||||
assets: make(map[string]asset),
|
||||
}
|
||||
|
||||
r.images["linux/amd64"] = "sha256:linux/amd64"
|
||||
r.images["linux/arm64"] = "sha256:linux/arm64"
|
||||
|
||||
r.manifests["sha256:linux/amd64-manifest"] = manifest{
|
||||
desc: ocispec.Descriptor{
|
||||
MediaType: v1.MediaTypeImageManifest,
|
||||
Digest: "sha256:linux/amd64-manifest",
|
||||
Platform: &v1.Platform{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
},
|
||||
},
|
||||
manifest: ocispec.Manifest{
|
||||
MediaType: v1.MediaTypeImageManifest,
|
||||
Layers: []v1.Descriptor{
|
||||
{
|
||||
MediaType: v1.MediaTypeImageLayerGzip,
|
||||
Digest: "sha256:linux/amd64-content",
|
||||
Size: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
r.manifests["sha256:linux/arm64-manifest"] = manifest{
|
||||
desc: ocispec.Descriptor{
|
||||
MediaType: v1.MediaTypeImageManifest,
|
||||
Digest: "sha256:linux/arm64-manifest",
|
||||
Platform: &v1.Platform{
|
||||
Architecture: "arm64",
|
||||
OS: "linux",
|
||||
},
|
||||
},
|
||||
manifest: ocispec.Manifest{
|
||||
MediaType: v1.MediaTypeImageManifest,
|
||||
Layers: []v1.Descriptor{
|
||||
{
|
||||
MediaType: v1.MediaTypeImageLayerGzip,
|
||||
Digest: "sha256:linux/arm64-content",
|
||||
Size: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func getImageWithAttestation(t attestationType) *result {
|
||||
r := getImageNoAttestation()
|
||||
|
||||
r.manifests["sha256:linux/amd64-attestation"] = manifest{
|
||||
desc: ocispec.Descriptor{
|
||||
MediaType: v1.MediaTypeImageManifest,
|
||||
Digest: "sha256:linux/amd64-attestation",
|
||||
Annotations: map[string]string{
|
||||
"vnd.docker.reference.digest": "sha256:linux/amd64",
|
||||
"vnd.docker.reference.type": "attestation-manifest",
|
||||
},
|
||||
Platform: &v1.Platform{
|
||||
Architecture: "unknown",
|
||||
OS: "unknown",
|
||||
},
|
||||
},
|
||||
manifest: ocispec.Manifest{
|
||||
MediaType: v1.MediaTypeImageManifest,
|
||||
Layers: getAttestationLayers(t),
|
||||
},
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func getAttestationLayers(t attestationType) []v1.Descriptor {
|
||||
layers := []v1.Descriptor{}
|
||||
|
||||
if t == plainSpdx || t == plainSpdxAndDSSEEmbed {
|
||||
layers = append(layers, v1.Descriptor{
|
||||
MediaType: inTotoGenericMime,
|
||||
Digest: digest.FromString(attestationContent),
|
||||
Size: int64(len(attestationContent)),
|
||||
Annotations: map[string]string{
|
||||
"in-toto.io/predicate-type": intoto.PredicateSPDX,
|
||||
"test_content": attestationContent,
|
||||
},
|
||||
})
|
||||
layers = append(layers, v1.Descriptor{
|
||||
MediaType: inTotoGenericMime,
|
||||
Digest: digest.FromString(provenanceContent),
|
||||
Size: int64(len(provenanceContent)),
|
||||
Annotations: map[string]string{
|
||||
"in-toto.io/predicate-type": slsa02.PredicateSLSAProvenance,
|
||||
"test_content": provenanceContent,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if t == dsseEmbeded || t == plainSpdxAndDSSEEmbed {
|
||||
dsseAttestation := fmt.Sprintf("{\"payload\":\"%s\"}", base64.StdEncoding.EncodeToString([]byte(attestationContent)))
|
||||
dsseProvenance := fmt.Sprintf("{\"payload\":\"%s\"}", base64.StdEncoding.EncodeToString([]byte(provenanceContent)))
|
||||
layers = append(layers, v1.Descriptor{
|
||||
MediaType: inTotoSPDXDSSEMime,
|
||||
Digest: digest.FromString(dsseAttestation),
|
||||
Size: int64(len(dsseAttestation)),
|
||||
Annotations: map[string]string{
|
||||
"in-toto.io/predicate-type": intoto.PredicateSPDX,
|
||||
"test_content": dsseAttestation,
|
||||
},
|
||||
})
|
||||
layers = append(layers, v1.Descriptor{
|
||||
MediaType: inTotoProvenanceDSSEMime,
|
||||
Digest: digest.FromString(dsseProvenance),
|
||||
Size: int64(len(dsseProvenance)),
|
||||
Annotations: map[string]string{
|
||||
"in-toto.io/predicate-type": slsa02.PredicateSLSAProvenance,
|
||||
"test_content": dsseProvenance,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return layers
|
||||
}
|
||||
|
||||
const attestationContent = `
|
||||
{
|
||||
"_type": "https://in-toto.io/Statement/v0.1",
|
||||
"predicateType": "https://spdx.dev/Document",
|
||||
"predicate": {
|
||||
"name": "sbom",
|
||||
"spdxVersion": "SPDX-2.3",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"creationInfo": {
|
||||
"created": "2024-01-31T16:09:05Z",
|
||||
"creators": [
|
||||
"Tool: buildkit-v0.11.0"
|
||||
],
|
||||
"licenseListVersion": "3.22"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https://example.com",
|
||||
"packages": [
|
||||
{
|
||||
"name": "sbom",
|
||||
"SPDXID": "SPDXRef-DocumentRoot-Directory-sbom",
|
||||
"copyrightText": "",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"primaryPackagePurpose": "FILE",
|
||||
"supplier": "NOASSERTION"
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"relatedSpdxElement": "SPDXRef-DocumentRoot-Directory-sbom",
|
||||
"relationshipType": "DESCRIBES",
|
||||
"spdxElementId": "SPDXRef-DOCUMENT"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const provenanceContent = `
|
||||
{
|
||||
"_type": "https://in-toto.io/Statement/v0.1",
|
||||
"predicateType": "https://slsa.dev/provenance/v0.2",
|
||||
"predicate": {
|
||||
"buildType": "https://example.com/Makefile",
|
||||
"builder": {
|
||||
"id": "mailto:person@example.com"
|
||||
},
|
||||
"invocation": {
|
||||
"configSource": {
|
||||
"uri": "https://example.com/example-1.2.3.tar.gz",
|
||||
"digest": {"sha256": ""},
|
||||
"entryPoint": "src:foo"
|
||||
},
|
||||
"parameters": {
|
||||
"CFLAGS": "-O3"
|
||||
},
|
||||
"materials": [
|
||||
{
|
||||
"uri": "https://example.com/example-1.2.3.tar.gz",
|
||||
"digest": {"sha256": ""}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
Reference in New Issue
Block a user