Skip to content

Commit fe09265

Browse files
authored
Merge pull request moby#5573 from tonistiigi/oci-aftifact-attestation
add OCI artifact version of attestation manifest
2 parents 5035fb2 + 0251a3d commit fe09265

File tree

5 files changed

+68
-26
lines changed

5 files changed

+68
-26
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ Keys supported by image output:
282282
* `push-by-digest=true`: push unnamed image
283283
* `registry.insecure=true`: push to insecure HTTP registry
284284
* `oci-mediatypes=true`: use OCI mediatypes in configuration JSON instead of Docker's
285+
* `oci-artifact=false`: use OCI artifact format for attestations
285286
* `unpack=true`: unpack image after creation (for use with containerd)
286287
* `dangling-name-prefix=<value>`: name image with `prefix@<digest>`, used for anonymous images
287288
* `name-canonical=true`: add additional canonical name `name@<digest>`

client/client_test.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ var allTests = []func(t *testing.T, sb integration.Sandbox){
192192
testPullWithLayerLimit,
193193
testExportAnnotations,
194194
testExportAnnotationsMediaTypes,
195-
testExportAttestations,
195+
testExportAttestationsOCIArtifact,
196+
testExportAttestationsImageManifest,
196197
testExportedImageLabels,
197198
testAttestationDefaultSubject,
198199
testSourceDateEpochLayerTimestamps,
@@ -8833,7 +8834,15 @@ func testExportAnnotationsMediaTypes(t *testing.T, sb integration.Sandbox) {
88338834
require.Equal(t, ocispecs.MediaTypeImageIndex, imgs2.Index.MediaType)
88348835
}
88358836

8836-
func testExportAttestations(t *testing.T, sb integration.Sandbox) {
8837+
func testExportAttestationsOCIArtifact(t *testing.T, sb integration.Sandbox) {
8838+
testExportAttestations(t, sb, true)
8839+
}
8840+
8841+
func testExportAttestationsImageManifest(t *testing.T, sb integration.Sandbox) {
8842+
testExportAttestations(t, sb, false)
8843+
}
8844+
8845+
func testExportAttestations(t *testing.T, sb integration.Sandbox, ociArtifact bool) {
88378846
workers.CheckFeatureCompat(t, sb, workers.FeatureDirectPush)
88388847
requiresLinux(t)
88398848
c, err := New(sb.Context(), sb.Address())
@@ -8953,8 +8962,9 @@ func testExportAttestations(t *testing.T, sb integration.Sandbox) {
89538962
{
89548963
Type: ExporterImage,
89558964
Attrs: map[string]string{
8956-
"name": strings.Join(targets, ","),
8957-
"push": "true",
8965+
"name": strings.Join(targets, ","),
8966+
"push": "true",
8967+
"oci-artifact": strconv.FormatBool(ociArtifact),
89588968
},
89598969
},
89608970
},
@@ -8984,12 +8994,25 @@ func testExportAttestations(t *testing.T, sb integration.Sandbox) {
89848994
for i, att := range atts.Images {
89858995
require.Equal(t, ocispecs.MediaTypeImageManifest, att.Desc.MediaType)
89868996
require.Equal(t, "unknown/unknown", platforms.Format(*att.Desc.Platform))
8987-
require.Equal(t, "unknown/unknown", att.Img.OS+"/"+att.Img.Architecture)
89888997
require.Equal(t, attestation.DockerAnnotationReferenceTypeDefault, att.Desc.Annotations[attestation.DockerAnnotationReferenceType])
89898998
require.Equal(t, bases[i].Desc.Digest.String(), att.Desc.Annotations[attestation.DockerAnnotationReferenceDigest])
89908999
require.Equal(t, 2, len(att.Layers))
8991-
require.Equal(t, len(att.Layers), len(att.Img.RootFS.DiffIDs))
8992-
require.Equal(t, 0, len(att.Img.History))
9000+
9001+
if ociArtifact {
9002+
subject := att.Manifest.Subject
9003+
require.NotNil(t, subject)
9004+
require.Equal(t, bases[i].Desc, *subject)
9005+
require.Equal(t, "application/vnd.docker.attestation.manifest.v1+json", att.Manifest.ArtifactType)
9006+
require.Equal(t, ocispecs.DescriptorEmptyJSON, att.Manifest.Config)
9007+
} else {
9008+
require.Nil(t, att.Manifest.Subject)
9009+
require.Empty(t, att.Manifest.ArtifactType)
9010+
9011+
// image config is not included in the OCI artifact
9012+
require.Equal(t, "unknown/unknown", att.Img.OS+"/"+att.Img.Architecture)
9013+
require.Equal(t, len(att.Layers), len(att.Img.RootFS.DiffIDs))
9014+
require.Equal(t, 0, len(att.Img.History))
9015+
}
89939016

89949017
var attest intoto.Statement
89959018
require.NoError(t, json.Unmarshal(att.LayersRaw[0], &attest))

exporter/containerimage/exptypes/keys.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ var (
4444
// Value: bool <true|false>
4545
OptKeyOCITypes ImageExporterOptKey = "oci-mediatypes"
4646

47+
// Use OCI artifact format for the attestation manifest.
48+
OptKeyOCIArtifact ImageExporterOptKey = "oci-artifact"
49+
4750
// Force attestation to be attached.
4851
// Value: bool <true|false>
4952
OptKeyForceInlineAttestations ImageExporterOptKey = "attestation-inline"

exporter/containerimage/opts.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type ImageCommitOpts struct {
1717
ImageName string
1818
RefCfg cacheconfig.RefConfig
1919
OCITypes bool
20+
OCIArtifact bool
2021
Annotations AnnotationsGroup
2122
Epoch *time.Time
2223

@@ -49,6 +50,8 @@ func (c *ImageCommitOpts) Load(ctx context.Context, opt map[string]string) (map[
4950
c.ImageName = v
5051
case exptypes.OptKeyOCITypes:
5152
err = parseBoolWithDefault(&c.OCITypes, k, v, true)
53+
case exptypes.OptKeyOCIArtifact:
54+
err = parseBool(&c.OCIArtifact, k, v)
5255
case exptypes.OptKeyForceInlineAttestations:
5356
err = parseBool(&c.ForceInlineAttestations, k, v)
5457
case exptypes.OptKeyPreferNondistLayers:
@@ -67,6 +70,9 @@ func (c *ImageCommitOpts) Load(ctx context.Context, opt map[string]string) (map[
6770
if c.RefCfg.Compression.Type.OnlySupportOCITypes() {
6871
c.EnableOCITypes(ctx, c.RefCfg.Compression.Type.String())
6972
}
73+
if c.OCIArtifact && !c.OCITypes {
74+
c.EnableOCITypes(ctx, "oci-artifact")
75+
}
7076

7177
c.Annotations = c.Annotations.Merge(as)
7278

exporter/containerimage/writer.go

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ import (
4646
"golang.org/x/sync/errgroup"
4747
)
4848

49+
const attestationManifestArtifactType = "application/vnd.docker.attestation.manifest.v1+json"
50+
4951
type WriterOpt struct {
5052
Snapshotter snapshot.Snapshotter
5153
ContentStore content.Store
@@ -312,7 +314,7 @@ func (ic *ImageWriter) Commit(ctx context.Context, inp *exporter.Source, session
312314
return nil, err
313315
}
314316

315-
desc, err := ic.commitAttestationsManifest(ctx, opts, desc.Digest.String(), stmts)
317+
desc, err := ic.commitAttestationsManifest(ctx, opts, *desc, stmts, opts.OCIArtifact)
316318
if err != nil {
317319
return nil, err
318320
}
@@ -553,7 +555,7 @@ func (ic *ImageWriter) commitDistributionManifest(ctx context.Context, opts *Ima
553555
}, &configDesc, nil
554556
}
555557

556-
func (ic *ImageWriter) commitAttestationsManifest(ctx context.Context, opts *ImageCommitOpts, target string, statements []intoto.Statement) (*ocispecs.Descriptor, error) {
558+
func (ic *ImageWriter) commitAttestationsManifest(ctx context.Context, opts *ImageCommitOpts, target ocispecs.Descriptor, statements []intoto.Statement, ociArtifact bool) (*ocispecs.Descriptor, error) {
557559
var (
558560
manifestType = ocispecs.MediaTypeImageManifest
559561
configType = ocispecs.MediaTypeImageConfig
@@ -588,31 +590,38 @@ func (ic *ImageWriter) commitAttestationsManifest(ctx context.Context, opts *Ima
588590
layers[i] = desc
589591
}
590592

591-
config, err := attestationsConfig(layers)
592-
if err != nil {
593-
return nil, err
594-
}
595-
configDigest := digest.FromBytes(config)
596-
configDesc := ocispecs.Descriptor{
597-
Digest: configDigest,
598-
Size: int64(len(config)),
599-
MediaType: configType,
593+
configDesc := ocispecs.DescriptorEmptyJSON
594+
config := configDesc.Data
595+
596+
if !ociArtifact {
597+
var err error
598+
config, err = attestationsConfig(layers)
599+
if err != nil {
600+
return nil, err
601+
}
602+
configDigest := digest.FromBytes(config)
603+
configDesc = ocispecs.Descriptor{
604+
Digest: configDigest,
605+
Size: int64(len(config)),
606+
MediaType: configType,
607+
}
600608
}
601609

602610
mfst := ocispecs.Manifest{
603611
MediaType: manifestType,
604612
Versioned: specs.Versioned{
605613
SchemaVersion: 2,
606614
},
607-
Config: ocispecs.Descriptor{
608-
Digest: configDigest,
609-
Size: int64(len(config)),
610-
MediaType: configType,
611-
},
615+
Config: configDesc,
616+
}
617+
618+
if ociArtifact {
619+
mfst.ArtifactType = attestationManifestArtifactType
620+
mfst.Subject = &target
612621
}
613622

614623
labels := map[string]string{
615-
"containerd.io/gc.ref.content.0": configDigest.String(),
624+
"containerd.io/gc.ref.content.0": configDesc.Digest.String(),
616625
}
617626
for i, desc := range layers {
618627
desc.Annotations = RemoveInternalLayerAnnotations(desc.Annotations, opts.OCITypes)
@@ -635,7 +644,7 @@ func (ic *ImageWriter) commitAttestationsManifest(ctx context.Context, opts *Ima
635644
if err := content.WriteBlob(ctx, ic.opt.ContentStore, mfstDigest.String(), bytes.NewReader(mfstJSON), mfstDesc, content.WithLabels((labels))); err != nil {
636645
return nil, done(errors.Wrapf(err, "error writing manifest blob %s", mfstDigest))
637646
}
638-
if err := content.WriteBlob(ctx, ic.opt.ContentStore, configDigest.String(), bytes.NewReader(config), configDesc); err != nil {
647+
if err := content.WriteBlob(ctx, ic.opt.ContentStore, configDesc.Digest.String(), bytes.NewReader(config), configDesc); err != nil {
639648
return nil, done(errors.Wrap(err, "error writing config blob"))
640649
}
641650
done(nil)
@@ -646,7 +655,7 @@ func (ic *ImageWriter) commitAttestationsManifest(ctx context.Context, opts *Ima
646655
MediaType: manifestType,
647656
Annotations: map[string]string{
648657
attestationTypes.DockerAnnotationReferenceType: attestationTypes.DockerAnnotationReferenceTypeDefault,
649-
attestationTypes.DockerAnnotationReferenceDigest: target,
658+
attestationTypes.DockerAnnotationReferenceDigest: string(target.Digest),
650659
},
651660
}, nil
652661
}

0 commit comments

Comments
 (0)