* Single attestation when creating VSA * Create single layer images for referrers attestations * Move mock to test package. Add artifacts test * Add test for envelope detection * Add tests for image/index saving * Add mirror tests * Remove AttestationImage field from AttestationManifest * Update naming. strictReferers != laxReferrers * Add specific test for SaveReferrers
158 lines
4.6 KiB
Go
158 lines
4.6 KiB
Go
package mirror
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/docker/attest/internal/embed"
|
|
"github.com/docker/attest/pkg/attestation"
|
|
"github.com/docker/attest/pkg/oci"
|
|
"github.com/docker/attest/pkg/tuf"
|
|
"github.com/google/go-containerregistry/pkg/name"
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
"github.com/google/go-containerregistry/pkg/v1/empty"
|
|
"github.com/google/go-containerregistry/pkg/v1/layout"
|
|
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
|
)
|
|
|
|
func NewTufMirror(root []byte, tufPath, metadataURL, targetsURL string, versionChecker tuf.VersionChecker) (*TufMirror, error) {
|
|
if root == nil {
|
|
root = embed.RootDefault.Data
|
|
}
|
|
tufClient, err := tuf.NewTufClient(root, tufPath, metadataURL, targetsURL, versionChecker)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create TUF client: %w", err)
|
|
}
|
|
return &TufMirror{TufClient: tufClient, tufPath: tufPath, metadataURL: metadataURL, targetsURL: targetsURL}, nil
|
|
}
|
|
|
|
func PushImageToRegistry(image v1.Image, imageName string) error {
|
|
ref, err := name.ParseReference(imageName)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse image name '%s': %w", imageName, err)
|
|
}
|
|
|
|
// Push the image to the registry
|
|
return remote.Write(ref, image, oci.MultiKeychainOption())
|
|
}
|
|
|
|
func PushIndexToRegistry(index v1.ImageIndex, imageName string) error {
|
|
// Parse the index name
|
|
ref, err := name.ParseReference(imageName)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse image name: %w", err)
|
|
}
|
|
|
|
// Push the index to the registry
|
|
return remote.WriteIndex(ref, index, oci.MultiKeychainOption())
|
|
}
|
|
|
|
func SaveImageAsOCILayout(image v1.Image, path string) error {
|
|
// Save the image to the local filesystem
|
|
err := os.MkdirAll(path, os.ModePerm)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create directory: %w", err)
|
|
}
|
|
index := empty.Index
|
|
l, err := layout.Write(path, index)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create index: %w", err)
|
|
}
|
|
return l.AppendImage(image)
|
|
}
|
|
|
|
func SaveIndexAsOCILayout(image v1.ImageIndex, path string) error {
|
|
// Save the index to the local filesystem
|
|
err := os.MkdirAll(path, os.ModePerm)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create directory: %w", err)
|
|
}
|
|
|
|
_, err = layout.Write(path, image)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create index: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func SaveIndex(outputs []*oci.ImageSpec, index v1.ImageIndex, indexName string) error {
|
|
// split output by comma and write or push each one
|
|
for _, output := range outputs {
|
|
if output.Type == oci.OCI {
|
|
idx := v1.ImageIndex(empty.Index)
|
|
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
|
Add: index,
|
|
Descriptor: v1.Descriptor{
|
|
Annotations: map[string]string{
|
|
oci.OciReferenceTarget: indexName,
|
|
},
|
|
},
|
|
})
|
|
err := SaveIndexAsOCILayout(idx, output.Identifier)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write signed image: %w", err)
|
|
}
|
|
} else {
|
|
err := PushIndexToRegistry(index, output.Identifier)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to push signed image: %w", err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func SaveImage(output *oci.ImageSpec, image v1.Image, imageName string) error {
|
|
if output.Type == oci.OCI {
|
|
idx := v1.ImageIndex(empty.Index)
|
|
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
|
Add: image,
|
|
Descriptor: v1.Descriptor{
|
|
Annotations: map[string]string{
|
|
oci.OciReferenceTarget: imageName,
|
|
},
|
|
},
|
|
})
|
|
err := SaveIndexAsOCILayout(idx, output.Identifier)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write signed image: %w", err)
|
|
}
|
|
} else {
|
|
err := PushImageToRegistry(image, output.Identifier)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to push signed image: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func SaveReferrers(manifest *attestation.AttestationManifest, outputs []*oci.ImageSpec) error {
|
|
for _, output := range outputs {
|
|
if output.Type == oci.OCI {
|
|
continue
|
|
}
|
|
// so that we use the same tag each time to reduce number of tags (tags aren't needed for referrers but we must push one)
|
|
attOut, err := oci.ReplaceTagInSpec(output, manifest.SubjectDescriptor.Digest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
//otherwise we end up with the detected platform, though I'm not sure it matters
|
|
attOut.Platform = &v1.Platform{
|
|
OS: "unknown",
|
|
Architecture: "unknown",
|
|
}
|
|
images, err := manifest.BuildReferringArtifacts()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to build image: %w", err)
|
|
}
|
|
for _, image := range images {
|
|
err = SaveImage(attOut, image, "")
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("failed to push image: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|