diff --git a/attest/registry.go b/attest/registry.go index 40111b2..af3d808 100644 --- a/attest/registry.go +++ b/attest/registry.go @@ -14,16 +14,12 @@ import ( "github.com/fxamacker/cbor/v2" ) -type PathCheckerRegistryKey struct { - Path string - Digest digest.SHA256 -} - type PathCheckerRegistry struct { newPathChecker func(string, digest.SHA256) types.PathChecker - registry map[PathCheckerRegistryKey]types.PathChecker - statements types.Statements + registry map[types.PathCheckerRegistryKey]types.PathChecker + mutatedPaths types.Mutations + statements types.Statements baseDir } @@ -39,7 +35,7 @@ func NewPathCheckerRegistry(dir string, newPathChecker func(string, digest.SHA25 return &PathCheckerRegistry{ baseDir: baseDir{fromWorkDir: dir}, newPathChecker: newPathChecker, - registry: map[PathCheckerRegistryKey]types.PathChecker{}, + registry: map[types.PathCheckerRegistryKey]types.PathChecker{}, statements: types.Statements{}, } } @@ -71,13 +67,28 @@ func (r *PathCheckerRegistry) Register(path string, digest digest.SHA256) error return nil } +func (r *PathCheckerRegistry) RegisterMutated(mutatedPaths types.Mutations) { + r.mutatedPaths = make(types.Mutations, len(mutatedPaths)) // avoid stale entries + for k := range mutatedPaths { + oldDigest := mutatedPaths[k] + k.Path = r.pathFromRepoRoot(k.Path) + r.mutatedPaths[k] = oldDigest + } +} + func (r *PathCheckerRegistry) AssociateStatements(statements ...types.Statement) error { for i := range statements { if err := statements[i].SetSubjects(func(subject *types.Subject) error { path := r.pathFromRepoRoot(subject.Name) key := r.makeKey(path, subject.Digest) if _, ok := r.registry[key]; !ok { - return fmt.Errorf("statement with subject %#v is not relevant (path resoved to %q)", subject, path) + err := fmt.Errorf("statement with subject %#v is not relevant (path resoved to %q)", subject, path) + if r.mutatedPaths == nil { + return err + } + if _, ok := r.mutatedPaths[key]; !ok { + return err + } } subject.Name = path return nil @@ -86,7 +97,6 @@ func (r *PathCheckerRegistry) AssociateStatements(statements ...types.Statement) } } r.statements = append(r.statements, statements...) - return nil } @@ -178,6 +188,6 @@ func (r *PathCheckerRegistry) pathFromWorkDir(path string) string { return filepath.Join(r.fromWorkDir, path) } -func (r *PathCheckerRegistry) makeKey(path string, digest digest.SHA256) PathCheckerRegistryKey { - return PathCheckerRegistryKey{Path: path, Digest: digest} +func (r *PathCheckerRegistry) makeKey(path string, digest digest.SHA256) types.PathCheckerRegistryKey { + return types.PathCheckerRegistryKey{Path: path, Digest: digest} } diff --git a/attest/types/types.go b/attest/types/types.go index c7a3aad..a5e50e4 100644 --- a/attest/types/types.go +++ b/attest/types/types.go @@ -19,6 +19,7 @@ type ( Path string Digest digest.SHA256 } + Mutations = map[PathCheckerRegistryKey]digest.SHA256 PathChecker interface { ProviderName() string diff --git a/manifest/imagescanner/imagescanner.go b/manifest/imagescanner/imagescanner.go index 43e899b..124c90f 100644 --- a/manifest/imagescanner/imagescanner.go +++ b/manifest/imagescanner/imagescanner.go @@ -56,7 +56,10 @@ func (s *DefaultImageScanner) Scan(dir string, manifests []string) error { pipeline := kio.Pipeline{ Inputs: []kio.Reader{ - &kio.ByteReader{Reader: io.TeeReader(manifest, s.hash)}, + &kio.ByteReader{ + Reader: io.TeeReader(manifest, s.hash), + OmitReaderAnnotations: true, + }, }, Filters: []kio.Filter{filter}, } diff --git a/manifest/updater/updater.go b/manifest/updater/updater.go index a84aabb..0ccaf74 100644 --- a/manifest/updater/updater.go +++ b/manifest/updater/updater.go @@ -1,53 +1,85 @@ package updater import ( + "crypto/sha256" + "fmt" + "hash" + "sigs.k8s.io/kustomize/api/filters/imagetag" kustomize "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/kio/kioutil" + "github.com/docker/labs-brown-tape/attest/digest" + attestTypes "github.com/docker/labs-brown-tape/attest/types" "github.com/docker/labs-brown-tape/manifest/types" + manifestTypes "github.com/docker/labs-brown-tape/manifest/types" ) type Updater interface { - Update(*types.ImageList) error + Update(*manifestTypes.ImageList) error + Mutations() attestTypes.Mutations } -func NewFileUpdater() Updater { return &FileUpdater{} } +func NewFileUpdater() Updater { + return &FileUpdater{ + hash: sha256.New(), + mutations: attestTypes.Mutations{}, + } +} -type FileUpdater struct{} +type FileUpdater struct { + hash hash.Hash + mutations attestTypes.Mutations +} -func (u *FileUpdater) Update(images *types.ImageList) error { - for manifest, images := range images.GroupByManifest() { - if err := u.doUpdate(manifest, images.Items()); err != nil { +func (u *FileUpdater) Update(images *manifestTypes.ImageList) error { + groups := images.GroupByManifest() + for manifestPath := range groups { + if err := u.doUpdate(manifestPath, groups[manifestPath].Items()); err != nil { return err } } return nil } -func (u *FileUpdater) doUpdate(manifest string, images []types.Image) error { +func (u *FileUpdater) doUpdate(manifestPath string, images []types.Image) error { + if len(images) == 0 { + return fmt.Errorf("no images to update") + } + + u.hash.Reset() + pipeline := kio.Pipeline{ Inputs: []kio.Reader{ kio.LocalPackageReader{ - PackagePath: manifest, + PackagePath: manifestPath, }, }, Filters: make([]kio.Filter, len(images)), Outputs: []kio.Writer{ kio.LocalPackageWriter{ - PackagePath: manifest, + PackagePath: manifestPath, + }, + kio.ByteWriter{ + Writer: u.hash, + KeepReaderAnnotations: false, + ClearAnnotations: []string{ + kioutil.PathAnnotation, + kioutil.LegacyPathAnnotation, + }, }, }, } - for i, image := range images { + for i := range images { pipeline.Filters[i] = imagetag.Filter{ ImageTag: kustomize.Image{ - Name: image.OriginalName, - NewName: image.NewName, + Name: images[i].OriginalName, + NewName: images[i].NewName, // NB: docs say NewTag is ignored when digest is set, but it's not true - NewTag: image.NewTag, - Digest: image.Digest, + NewTag: images[i].NewTag, + Digest: images[i].Digest, }, // this is not optimal, however `(*yaml.RNode).FieldPath()` only returns a flat slice // where `contianers[]` is presented as `containers` for some reason; but having @@ -60,5 +92,17 @@ func (u *FileUpdater) doUpdate(manifest string, images []types.Image) error { if err := pipeline.Execute(); err != nil { return err } + + key := attestTypes.PathCheckerRegistryKey{ + Path: images[0].Manifest(), + Digest: digest.MakeSHA256(u.hash), + } + if _, ok := u.mutations[key]; ok { + return fmt.Errorf("mutations with key %#v is already registered", key) + } + u.mutations[key] = images[0].ManifestDigest() + return nil } + +func (u *FileUpdater) Mutations() attestTypes.Mutations { return u.mutations } diff --git a/tape/app/package.go b/tape/app/package.go index 9b3e7c6..256ca01 100644 --- a/tape/app/package.go +++ b/tape/app/package.go @@ -155,7 +155,7 @@ func (c *TapePackageCommand) Execute(args []string) error { if err := updater.Update(images); err != nil { return fmt.Errorf("failed to update manifest files: %w", err) } - + attreg.RegisterMutated(updater.Mutations()) scanner.Reset() if err := scanner.Scan(loader.RelPaths()); err != nil { return fmt.Errorf("failed to scan updated manifest files: %w", err)