package mirror import ( "fmt" "path/filepath" "strings" "github.com/docker/attest/oci" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/static" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/theupdateframework/go-tuf/v2/metadata" ) // GetTUFTargetMirrors returns a list of top-level target files as MirrorImages (image with tag). func (m *TUFMirror) GetTUFTargetMirrors() ([]*Image, error) { targetMirrors := []*Image{} md := m.TUFClient.GetMetadata() // for each top-level target file, create an image with the target file as a layer targets := md.Targets[metadata.TARGETS].Signed.Targets for _, t := range targets { // download target file file, err := m.TUFClient.DownloadTarget(t.Path, filepath.Join(m.tufPath, "download")) if err != nil { return nil, fmt.Errorf("failed to download target %s: %w", t.Path, err) } // create image with target file as layer img := empty.Image img = mutate.MediaType(img, types.OCIManifestSchema1) img = mutate.ConfigMediaType(img, types.OCIConfigJSON) // annotate layer hash, ok := t.Hashes["sha256"] if !ok { return nil, fmt.Errorf("missing sha256 hash for target %s", t.Path) } name := hash.String() + "." + t.Path ann := map[string]string{tufFileAnnotation: name} layer := mutate.Addendum{Layer: static.NewLayer(file.Data, tufTargetMediaType), Annotations: ann} img, err = mutate.Append(img, layer) if err != nil { return nil, fmt.Errorf("failed to append role layer to image: %w", err) } targetMirrors = append(targetMirrors, &Image{Image: &oci.EmptyConfigImage{Image: img}, Tag: name}) } return targetMirrors, nil } // GetDelegatedTargetMirrors returns a list of delegated target files as MirrorIndexes (image index with tag) // each image in the index contains a delegated target file. func (m *TUFMirror) GetDelegatedTargetMirrors() ([]*Index, error) { mirror := []*Index{} md := m.TUFClient.GetMetadata() // for each delegated role, create an image index with target files as images roles := md.Targets[metadata.TARGETS].Signed.Delegations.Roles for _, role := range roles { // create an image index index := v1.ImageIndex(empty.Index) // get delegated targets metadata for role roleMeta, err := m.TUFClient.LoadDelegatedTargets(role.Name, metadata.TARGETS) if err != nil { return nil, fmt.Errorf("failed to load delegated targets metadata: %w", err) } // for each target file, create an image with the target file as a layer for _, target := range roleMeta.Signed.Targets { // download target file file, err := m.TUFClient.DownloadTarget(target.Path, filepath.Join(m.tufPath, "download")) if err != nil { return nil, fmt.Errorf("failed to download target %s: %w", target.Path, err) } // create image with target file as layer img := empty.Image img = mutate.MediaType(img, types.OCIManifestSchema1) img = mutate.ConfigMediaType(img, types.OCIConfigJSON) // annotate layer hash, ok := target.Hashes["sha256"] if !ok { return nil, fmt.Errorf("missing sha256 hash for target %s", target.Path) } filename := filepath.Base(target.Path) subdir, ok := strings.CutSuffix(target.Path, "/"+filename) if !ok { return nil, fmt.Errorf("failed to find target subdirectory [%s] in path: %s", subdir, target.Path) } name := hash.String() + "." + filename ann := map[string]string{tufFileAnnotation: name} layer := mutate.Addendum{Layer: static.NewLayer(file.Data, tufTargetMediaType), Annotations: ann} img, err = mutate.Append(img, layer) if err != nil { return nil, fmt.Errorf("failed to append role layer to image: %w", err) } emptyConfigImage := &oci.EmptyConfigImage{Image: img} // append image to index with annotation index = mutate.AppendManifests(index, mutate.IndexAddendum{ Add: emptyConfigImage, Descriptor: v1.Descriptor{ Annotations: map[string]string{ tufFileAnnotation: fmt.Sprintf("%s/%s", subdir, name), }, }, }) } mirror = append(mirror, &Index{Index: index, Tag: role.Name}) } return mirror, nil }