@@ -26,6 +26,7 @@ import (
2626 "github.com/google/go-containerregistry/pkg/v1/remote"
2727 "github.com/google/go-containerregistry/pkg/v1/types"
2828 intoto "github.com/in-toto/attestation/go/v1"
29+ ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
2930 "github.com/tektoncd/chains/pkg/chains/signing"
3031 "github.com/tektoncd/chains/pkg/chains/storage/api"
3132 logtesting "knative.dev/pkg/logging/testing"
@@ -109,3 +110,127 @@ func TestAttestationStorer_Store(t *testing.T) {
109110 })
110111 }
111112}
113+
114+ func TestAttestationStorer_Store_Dedup (t * testing.T ) {
115+ s := httptest .NewServer (registry .New ())
116+ defer s .Close ()
117+ registryName := strings .TrimPrefix (s .URL , "http://" )
118+
119+ img , err := random .Image (1024 , 2 )
120+ if err != nil {
121+ t .Fatalf ("failed to create random image: %s" , err )
122+ }
123+ imgDigest , err := img .Digest ()
124+ if err != nil {
125+ t .Fatalf ("failed to get image digest: %v" , err )
126+ }
127+ ref , err := name .NewDigest (fmt .Sprintf ("%s/test/img@%s" , registryName , imgDigest ))
128+ if err != nil {
129+ t .Fatalf ("failed to parse digest: %v" , err )
130+ }
131+ if err := remote .Write (ref , img ); err != nil {
132+ t .Fatalf ("failed to write image to mock registry: %v" , err )
133+ }
134+
135+ storer , err := NewAttestationStorer (WithTargetRepository (ref .Repository ))
136+ if err != nil {
137+ t .Fatalf ("failed to create storer: %v" , err )
138+ }
139+
140+ ctx := logtesting .TestContextWithLogger (t )
141+ req := & api.StoreRequest [name.Digest , * intoto.Statement ]{
142+ Artifact : ref ,
143+ Payload : & intoto.Statement {},
144+ Bundle : & signing.Bundle {Signature : []byte ("sig1" )},
145+ }
146+
147+ // Store the same attestation twice.
148+ if _ , err := storer .Store (ctx , req ); err != nil {
149+ t .Fatalf ("first Store() failed: %s" , err )
150+ }
151+ if _ , err := storer .Store (ctx , req ); err != nil {
152+ t .Fatalf ("second Store() failed: %s" , err )
153+ }
154+
155+ // Verify only one attestation layer exists.
156+ se , err := ociremote .SignedEntity (ref )
157+ if err != nil {
158+ t .Fatalf ("failed to get signed entity: %v" , err )
159+ }
160+ atts , err := se .Attestations ()
161+ if err != nil {
162+ t .Fatalf ("failed to get attestations: %v" , err )
163+ }
164+ layers , err := atts .Get ()
165+ if err != nil {
166+ t .Fatalf ("failed to get attestation layers: %v" , err )
167+ }
168+ if got := len (layers ); got != 1 {
169+ t .Errorf ("expected 1 attestation layer, got %d" , got )
170+ }
171+ }
172+
173+ func TestAttestationStorer_Store_DistinctNotDeduped (t * testing.T ) {
174+ s := httptest .NewServer (registry .New ())
175+ defer s .Close ()
176+ registryName := strings .TrimPrefix (s .URL , "http://" )
177+
178+ img , err := random .Image (1024 , 2 )
179+ if err != nil {
180+ t .Fatalf ("failed to create random image: %s" , err )
181+ }
182+ imgDigest , err := img .Digest ()
183+ if err != nil {
184+ t .Fatalf ("failed to get image digest: %v" , err )
185+ }
186+ ref , err := name .NewDigest (fmt .Sprintf ("%s/test/img@%s" , registryName , imgDigest ))
187+ if err != nil {
188+ t .Fatalf ("failed to parse digest: %v" , err )
189+ }
190+ if err := remote .Write (ref , img ); err != nil {
191+ t .Fatalf ("failed to write image to mock registry: %v" , err )
192+ }
193+
194+ storer , err := NewAttestationStorer (WithTargetRepository (ref .Repository ))
195+ if err != nil {
196+ t .Fatalf ("failed to create storer: %v" , err )
197+ }
198+
199+ ctx := logtesting .TestContextWithLogger (t )
200+
201+ // Store two attestations with different signatures (different layer content).
202+ req1 := & api.StoreRequest [name.Digest , * intoto.Statement ]{
203+ Artifact : ref ,
204+ Payload : & intoto.Statement {},
205+ Bundle : & signing.Bundle {Signature : []byte ("sig1" )},
206+ }
207+ req2 := & api.StoreRequest [name.Digest , * intoto.Statement ]{
208+ Artifact : ref ,
209+ Payload : & intoto.Statement {},
210+ Bundle : & signing.Bundle {Signature : []byte ("sig2" )},
211+ }
212+
213+ if _ , err := storer .Store (ctx , req1 ); err != nil {
214+ t .Fatalf ("first Store() failed: %s" , err )
215+ }
216+ if _ , err := storer .Store (ctx , req2 ); err != nil {
217+ t .Fatalf ("second Store() failed: %s" , err )
218+ }
219+
220+ // Verify both attestation layers are kept.
221+ se , err := ociremote .SignedEntity (ref )
222+ if err != nil {
223+ t .Fatalf ("failed to get signed entity: %v" , err )
224+ }
225+ atts , err := se .Attestations ()
226+ if err != nil {
227+ t .Fatalf ("failed to get attestations: %v" , err )
228+ }
229+ layers , err := atts .Get ()
230+ if err != nil {
231+ t .Fatalf ("failed to get attestation layers: %v" , err )
232+ }
233+ if got := len (layers ); got != 2 {
234+ t .Errorf ("expected 2 distinct attestation layers, got %d" , got )
235+ }
236+ }
0 commit comments