Create single layer images for referrers attestations
This commit is contained in:
2
go.mod
2
go.mod
@@ -31,6 +31,8 @@ require (
|
|||||||
sigs.k8s.io/yaml v1.4.0
|
sigs.k8s.io/yaml v1.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/google/go-containerregistry v0.19.2 => github.com/kipz/go-containerregistry v0.0.0-20240423201245-bf57eace21f2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.115.0 // indirect
|
cloud.google.com/go v0.115.0 // indirect
|
||||||
cloud.google.com/go/auth v0.7.0 // indirect
|
cloud.google.com/go/auth v0.7.0 // indirect
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func ExampleSign_remote() {
|
|||||||
|
|
||||||
// configure signing options
|
// configure signing options
|
||||||
opts := &attestation.SigningOptions{
|
opts := &attestation.SigningOptions{
|
||||||
Replace: true, // replace unsigned intoto statements with signed intoto attestations, otherwise leave in place
|
SkipTL: true, // skip trust logging to a transparency log
|
||||||
}
|
}
|
||||||
|
|
||||||
// load image index with unsigned attestation-manifests
|
// load image index with unsigned attestation-manifests
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func SignStatements(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVe
|
|||||||
}
|
}
|
||||||
// sign every attestation layer in each manifest
|
// sign every attestation layer in each manifest
|
||||||
for _, manifest := range attestationManifests {
|
for _, manifest := range attestationManifests {
|
||||||
for _, layer := range manifest.AttestationImage.Layers {
|
for _, layer := range manifest.AttestationImage.OriginalLayers {
|
||||||
err = manifest.AddAttestation(ctx, signer, layer.Statement, opts)
|
err = manifest.AddAttestation(ctx, signer, layer.Statement, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to sign attestation layer %w", err)
|
return nil, fmt.Errorf("failed to sign attestation layer %w", err)
|
||||||
|
|||||||
@@ -53,15 +53,13 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||||
opts := &attestation.SigningOptions{
|
opts := &attestation.SigningOptions{}
|
||||||
Replace: tc.replace,
|
|
||||||
}
|
|
||||||
attIdx, err := oci.IndexFromPath(tc.TestImage)
|
attIdx, err := oci.IndexFromPath(tc.TestImage)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
signedIndex := attIdx.Index
|
signedIndex := attIdx.Index
|
||||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests, attestation.WithReplacedLayers(tc.replace))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// output signed attestations
|
// output signed attestations
|
||||||
idx := v1.ImageIndex(empty.Index)
|
idx := v1.ImageIndex(empty.Index)
|
||||||
@@ -102,6 +100,7 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAddSignedLayerAnnotations(t *testing.T) {
|
func TestAddSignedLayerAnnotations(t *testing.T) {
|
||||||
|
ctx, signer := test.Setup(t)
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
replace bool
|
replace bool
|
||||||
@@ -115,12 +114,14 @@ func TestAddSignedLayerAnnotations(t *testing.T) {
|
|||||||
data := []byte("signed")
|
data := []byte("signed")
|
||||||
testLayer := static.NewLayer(data, types.MediaType(intoto.PayloadType))
|
testLayer := static.NewLayer(data, types.MediaType(intoto.PayloadType))
|
||||||
mediaType := types.OCIManifestSchema1
|
mediaType := types.OCIManifestSchema1
|
||||||
opts := &attestation.SigningOptions{
|
opts := &attestation.SigningOptions{}
|
||||||
Replace: tc.replace,
|
|
||||||
}
|
|
||||||
originalLayer := &attestation.AttestationLayer{
|
originalLayer := &attestation.AttestationLayer{
|
||||||
Layer: testLayer,
|
Layer: testLayer,
|
||||||
Statement: &intoto.Statement{},
|
Statement: &intoto.Statement{
|
||||||
|
StatementHeader: intoto.StatementHeader{
|
||||||
|
PredicateType: attestation.VSAPredicateType,
|
||||||
|
},
|
||||||
|
},
|
||||||
Annotations: map[string]string{"test": "test"},
|
Annotations: map[string]string{"test": "test"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,15 +130,15 @@ func TestAddSignedLayerAnnotations(t *testing.T) {
|
|||||||
MediaType: mediaType,
|
MediaType: mediaType,
|
||||||
},
|
},
|
||||||
AttestationImage: &attestation.AttestationImage{
|
AttestationImage: &attestation.AttestationImage{
|
||||||
Image: empty.Image,
|
OriginalLayers: []*attestation.AttestationLayer{
|
||||||
Layers: []*attestation.AttestationLayer{
|
|
||||||
originalLayer,
|
originalLayer,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
SubjectDescriptor: &v1.Descriptor{},
|
SubjectDescriptor: &v1.Descriptor{},
|
||||||
}
|
}
|
||||||
err := manifest.AddOrReplaceLayer(originalLayer, opts)
|
err := manifest.AddAttestation(ctx, signer, originalLayer.Statement, opts)
|
||||||
newImg := manifest.AttestationImage.Image
|
|
||||||
|
newImg, err := manifest.BuildAttestationImage(attestation.WithReplacedLayers(tc.replace))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
mf, _ := newImg.RawManifest()
|
mf, _ := newImg.RawManifest()
|
||||||
type Annotations struct {
|
type Annotations struct {
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ func NewAttestationManifest(subject *v1.Descriptor) (*attestation.AttestationMan
|
|||||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||||
},
|
},
|
||||||
AttestationImage: &attestation.AttestationImage{
|
AttestationImage: &attestation.AttestationImage{
|
||||||
Layers: []*attestation.AttestationLayer{},
|
OriginalLayers: []*attestation.AttestationLayer{},
|
||||||
},
|
},
|
||||||
SubjectDescriptor: subject,
|
SubjectDescriptor: subject,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -77,9 +77,7 @@ func TestVSA(t *testing.T) {
|
|||||||
// setup an image with signed attestations
|
// setup an image with signed attestations
|
||||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||||
|
|
||||||
opts := &attestation.SigningOptions{
|
opts := &attestation.SigningOptions{}
|
||||||
Replace: true,
|
|
||||||
}
|
|
||||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
||||||
@@ -136,15 +134,13 @@ func TestVerificationFailure(t *testing.T) {
|
|||||||
// setup an image with signed attestations
|
// setup an image with signed attestations
|
||||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||||
|
|
||||||
opts := &attestation.SigningOptions{
|
opts := &attestation.SigningOptions{}
|
||||||
Replace: true,
|
|
||||||
}
|
|
||||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
signedIndex := attIdx.Index
|
signedIndex := attIdx.Index
|
||||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests, attestation.WithReplacedLayers(true))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// output signed attestations
|
// output signed attestations
|
||||||
@@ -215,14 +211,13 @@ func TestSignVerify(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
opts := &attestation.SigningOptions{
|
opts := &attestation.SigningOptions{
|
||||||
Replace: true,
|
SkipTL: tc.signTL,
|
||||||
SkipTL: tc.signTL,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
signedIndex := attIdx.Index
|
signedIndex := attIdx.Index
|
||||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests, attestation.WithReplacedLayers(true))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
imageName := tc.imageName
|
imageName := tc.imageName
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]*AttestationManife
|
|||||||
OriginalDescriptor: &desc,
|
OriginalDescriptor: &desc,
|
||||||
SubjectDescriptor: subject,
|
SubjectDescriptor: subject,
|
||||||
AttestationImage: &AttestationImage{
|
AttestationImage: &AttestationImage{
|
||||||
Layers: attestationLayers,
|
OriginalLayers: attestationLayers}})
|
||||||
Descriptor: &desc}})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return attestationManifests, nil
|
return attestationManifests, nil
|
||||||
@@ -97,11 +96,11 @@ func (manifest *AttestationManifest) AddAttestation(ctx context.Context, signer
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create signed layer: %w", err)
|
return fmt.Errorf("failed to create signed layer: %w", err)
|
||||||
}
|
}
|
||||||
return addLayerToImage(manifest, layer, opts)
|
manifest.AttestationImage.signedLayers = append(manifest.AttestationImage.signedLayers, layer)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSignedImageLayer(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier, opts *SigningOptions) (*AttestationLayer, error) {
|
func createSignedImageLayer(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier, opts *SigningOptions) (*AttestationLayer, error) {
|
||||||
|
|
||||||
// sign the statement
|
// sign the statement
|
||||||
env, err := SignInTotoStatement(ctx, statement, signer, opts)
|
env, err := SignInTotoStatement(ctx, statement, signer, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -138,21 +137,18 @@ func SignInTotoStatement(ctx context.Context, statement *intoto.Statement, signe
|
|||||||
return env, nil
|
return env, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addLayerToImage(
|
func AddImageToIndex(
|
||||||
|
idx v1.ImageIndex,
|
||||||
manifest *AttestationManifest,
|
manifest *AttestationManifest,
|
||||||
layer *AttestationLayer,
|
options ...func(*AttestationManifestImageOptions) error) (v1.ImageIndex, error) {
|
||||||
opts *SigningOptions) error {
|
image, err := manifest.BuildAttestationImage(options...)
|
||||||
|
|
||||||
err := manifest.AddOrReplaceLayer(layer, opts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to add signed layers: %w", err)
|
return nil, fmt.Errorf("failed to build image: %w", err)
|
||||||
}
|
}
|
||||||
if !opts.SkipSubject {
|
newDesc, err := partial.Descriptor(image)
|
||||||
manifest.AttestationImage.Image = mutate.Subject(manifest.AttestationImage.Image, *manifest.SubjectDescriptor).(v1.Image)
|
|
||||||
}
|
|
||||||
newDesc, err := partial.Descriptor(manifest.AttestationImage.Image)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get descriptor: %w", err)
|
return nil, fmt.Errorf("failed to get descriptor: %w", err)
|
||||||
}
|
}
|
||||||
newDesc.Platform = &v1.Platform{
|
newDesc.Platform = &v1.Platform{
|
||||||
Architecture: "unknown",
|
Architecture: "unknown",
|
||||||
@@ -160,77 +156,165 @@ func addLayerToImage(
|
|||||||
}
|
}
|
||||||
newDesc.MediaType = manifest.OriginalDescriptor.MediaType
|
newDesc.MediaType = manifest.OriginalDescriptor.MediaType
|
||||||
newDesc.Annotations = manifest.OriginalDescriptor.Annotations
|
newDesc.Annotations = manifest.OriginalDescriptor.Annotations
|
||||||
manifest.AttestationImage.Descriptor = newDesc
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddOrReplaceLayer adds signed layers to a new or existing attestation image
|
|
||||||
// NOTE: the pointers attestation.AttestationLayer.Statement are compared when replacing,
|
|
||||||
// so make sure you are signing a layer extracted from the original attestation-manifest image!
|
|
||||||
func (manifest *AttestationManifest) AddOrReplaceLayer(signedLayer *AttestationLayer, opts *SigningOptions) error {
|
|
||||||
var err error
|
|
||||||
// always create a new image from all the layers
|
|
||||||
newImg := empty.Image
|
|
||||||
//TODO: maybe we don't need these anymore
|
|
||||||
newImg = mutate.Annotations(newImg, map[string]string{
|
|
||||||
DockerReferenceType: AttestationManifestType,
|
|
||||||
DockerReferenceDigest: manifest.SubjectDescriptor.Digest.String(),
|
|
||||||
}).(v1.Image)
|
|
||||||
|
|
||||||
newImg = mutate.MediaType(newImg, manifest.OriginalDescriptor.MediaType)
|
|
||||||
newImg = mutate.ConfigMediaType(newImg, "application/vnd.oci.image.config.v1+json")
|
|
||||||
add := mutate.Addendum{
|
|
||||||
Layer: signedLayer.Layer,
|
|
||||||
Annotations: signedLayer.Annotations,
|
|
||||||
}
|
|
||||||
newImg, err = mutate.Append(newImg, add)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to add signed layer to image: %w", err)
|
|
||||||
}
|
|
||||||
newLayers := make([]*AttestationLayer, 0)
|
|
||||||
for _, existingLayer := range manifest.AttestationImage.Layers {
|
|
||||||
// if we're replacing, then we don't add it back in
|
|
||||||
if existingLayer.Statement == signedLayer.Statement && opts.Replace {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// add original layer back in
|
|
||||||
add := mutate.Addendum{
|
|
||||||
Layer: existingLayer.Layer,
|
|
||||||
Annotations: existingLayer.Annotations,
|
|
||||||
}
|
|
||||||
newImg, err = mutate.Append(newImg, add)
|
|
||||||
newLayers = append(newLayers, existingLayer)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to add layer to image: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
manifest.AttestationImage.Layers = append(newLayers, signedLayer)
|
|
||||||
manifest.AttestationImage.Image = newImg
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddImageToIndex(
|
|
||||||
idx v1.ImageIndex,
|
|
||||||
manifest *AttestationManifest,
|
|
||||||
) (v1.ImageIndex, error) {
|
|
||||||
idx = mutate.RemoveManifests(idx, match.Digests(manifest.OriginalDescriptor.Digest))
|
idx = mutate.RemoveManifests(idx, match.Digests(manifest.OriginalDescriptor.Digest))
|
||||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||||
Add: manifest.AttestationImage.Image,
|
Add: image,
|
||||||
Descriptor: *manifest.AttestationImage.Descriptor,
|
Descriptor: *newDesc,
|
||||||
})
|
})
|
||||||
return idx, nil
|
return idx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddImagesToIndex(
|
func AddImagesToIndex(idx v1.ImageIndex, manifest []*AttestationManifest, options ...func(*AttestationManifestImageOptions) error) (v1.ImageIndex, error) {
|
||||||
idx v1.ImageIndex,
|
var err error
|
||||||
manifests []*AttestationManifest,
|
for _, m := range manifest {
|
||||||
) (v1.ImageIndex, error) {
|
idx, err = AddImageToIndex(idx, m, options...)
|
||||||
for _, manifest := range manifests {
|
|
||||||
var err error
|
|
||||||
idx, err = AddImageToIndex(idx, manifest)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to add image to index: %w", err)
|
return nil, fmt.Errorf("failed to add image to index: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return idx, nil
|
return idx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newOptions(options ...func(*AttestationManifestImageOptions) error) (*AttestationManifestImageOptions, error) {
|
||||||
|
opts := &AttestationManifestImageOptions{}
|
||||||
|
for _, opt := range options {
|
||||||
|
err := opt(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithoutSubject(skipSubject bool) func(*AttestationManifestImageOptions) error {
|
||||||
|
return func(r *AttestationManifestImageOptions) error {
|
||||||
|
r.skipSubject = skipSubject
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithReplacedLayers(replaceLayers bool) func(*AttestationManifestImageOptions) error {
|
||||||
|
return func(r *AttestationManifestImageOptions) error {
|
||||||
|
r.replaceLayers = replaceLayers
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build an image with signed attestations, optionally replacing existing layers with signed layers
|
||||||
|
func (manifest *AttestationManifest) BuildAttestationImage(options ...func(*AttestationManifestImageOptions) error) (v1.Image, error) {
|
||||||
|
// always create a new image from all the layers
|
||||||
|
opts, err := newOptions(options...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create options: %w", err)
|
||||||
|
}
|
||||||
|
resultLayers := manifest.AttestationImage.signedLayers
|
||||||
|
for _, existingLayer := range manifest.AttestationImage.OriginalLayers {
|
||||||
|
var found bool
|
||||||
|
for _, signedLayer := range manifest.AttestationImage.signedLayers {
|
||||||
|
if existingLayer.Statement == signedLayer.Statement {
|
||||||
|
found = true
|
||||||
|
// copy over original annotations
|
||||||
|
for k, v := range existingLayer.Annotations {
|
||||||
|
signedLayer.Annotations[k] = v
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//add existing layers if they've not been signed or we're not replacing them
|
||||||
|
if !found || !opts.replaceLayers {
|
||||||
|
resultLayers = append(resultLayers, existingLayer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newImg, err := buildImage(resultLayers, manifest.OriginalDescriptor, manifest.SubjectDescriptor, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to build image: %w", err)
|
||||||
|
}
|
||||||
|
return newImg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// build an image per attestation (layer) suitable for use as Referrers
|
||||||
|
func (manifest *AttestationManifest) BuildReferringArtifacts() ([]v1.Image, error) {
|
||||||
|
var images []v1.Image
|
||||||
|
for _, layer := range manifest.AttestationImage.signedLayers {
|
||||||
|
opts := &AttestationManifestImageOptions{
|
||||||
|
strictReferrers: true,
|
||||||
|
}
|
||||||
|
newImg, err := buildImage([]*AttestationLayer{layer}, manifest.OriginalDescriptor, manifest.SubjectDescriptor, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to build image: %w", err)
|
||||||
|
}
|
||||||
|
images = append(images, newImg)
|
||||||
|
}
|
||||||
|
return images, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// build and image containing only layers
|
||||||
|
func buildImage(layers []*AttestationLayer, manifest *v1.Descriptor, subject *v1.Descriptor, opts *AttestationManifestImageOptions) (v1.Image, error) {
|
||||||
|
newImg := empty.Image
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// NB: if we add the subject before the layers, it does not end up being computed/serialised in the output for some reason
|
||||||
|
//TODO - recreate this bug and push upstream
|
||||||
|
for _, layer := range layers {
|
||||||
|
add := mutate.Addendum{
|
||||||
|
Layer: layer.Layer,
|
||||||
|
Annotations: layer.Annotations,
|
||||||
|
}
|
||||||
|
newImg, err = mutate.Append(newImg, add)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to add layer to image: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.strictReferrers {
|
||||||
|
newImg = mutate.ArtifactType(newImg, intoto.PayloadType)
|
||||||
|
newImg = mutate.ConfigMediaType(newImg, "application/vnd.oci.empty.v1+json")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
newImg = mutate.ConfigMediaType(newImg, "application/vnd.oci.image.config.v1+json")
|
||||||
|
}
|
||||||
|
// we need to set this even when we set the artifact type otherwise things break (even the go-container-registry client)
|
||||||
|
// even though it's allowed to be empty by spec when setting artifact type
|
||||||
|
newImg = mutate.MediaType(newImg, manifest.MediaType)
|
||||||
|
|
||||||
|
// see note above - must be added after the layers!
|
||||||
|
if !opts.skipSubject {
|
||||||
|
newImg = mutate.Subject(newImg, *subject).(v1.Image)
|
||||||
|
}
|
||||||
|
if opts.strictReferrers {
|
||||||
|
// as per https://github.com/opencontainers/image-spec/blob/main/manifest.md#guidance-for-an-empty-descriptor
|
||||||
|
newImg = &EmptyConfigImage{newImg}
|
||||||
|
}
|
||||||
|
return newImg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmptyConfigImage struct {
|
||||||
|
v1.Image
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *EmptyConfigImage) RawConfigFile() ([]byte, error) {
|
||||||
|
return []byte("{}"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *EmptyConfigImage) Manifest() (*v1.Manifest, error) {
|
||||||
|
mf, err := i.Image.Manifest()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get manifest: %w", err)
|
||||||
|
}
|
||||||
|
mf.Config = v1.Descriptor{
|
||||||
|
MediaType: "application/vnd.oci.empty.v1+json",
|
||||||
|
Size: 2,
|
||||||
|
Digest: v1.Hash{Algorithm: "sha256", Hex: "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"},
|
||||||
|
Data: []byte("e30="),
|
||||||
|
}
|
||||||
|
return mf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *EmptyConfigImage) RawManifest() ([]byte, error) {
|
||||||
|
mf, err := i.Manifest()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get manifest: %w", err)
|
||||||
|
}
|
||||||
|
return json.Marshal(mf)
|
||||||
|
}
|
||||||
|
|||||||
@@ -102,9 +102,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
opts := &attestation.SigningOptions{
|
opts := &attestation.SigningOptions{
|
||||||
Replace: true,
|
SkipTL: true,
|
||||||
SkipSubject: tc.skipSubject,
|
|
||||||
SkipTL: true,
|
|
||||||
}
|
}
|
||||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -121,15 +119,17 @@ func TestAttestationReferenceTypes(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = mirror.PushIndexToRegistry(attIdx.Index, indexName)
|
err = mirror.PushIndexToRegistry(attIdx.Index, indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
for _, img := range signedManifests {
|
for _, signedManifest := range signedManifests {
|
||||||
err = mirror.PushImageToRegistry(img.AttestationImage.Image, fmt.Sprintf("%s:tag-does-not-matter", repo))
|
image, err := signedManifest.BuildAttestationImage(attestation.WithoutSubject(tc.skipSubject), attestation.WithReplacedLayers(true))
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = mirror.PushImageToRegistry(image, fmt.Sprintf("%s:tag-does-not-matter", repo))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
signedIndex := attIdx.Index
|
signedIndex := attIdx.Index
|
||||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests, attestation.WithReplacedLayers(true), attestation.WithoutSubject(tc.skipSubject))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = mirror.PushIndexToRegistry(signedIndex, indexName)
|
err = mirror.PushIndexToRegistry(signedIndex, indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -225,8 +225,7 @@ func TestReferencesInDifferentRepo(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
opts := &attestation.SigningOptions{
|
opts := &attestation.SigningOptions{
|
||||||
Replace: true,
|
SkipTL: true,
|
||||||
SkipTL: true,
|
|
||||||
}
|
}
|
||||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -239,9 +238,11 @@ func TestReferencesInDifferentRepo(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// push signed attestation image to the ref server
|
// push signed attestation image to the ref server
|
||||||
for _, img := range signedManifests {
|
for _, signedManifest := range signedManifests {
|
||||||
// push references using subject-digest.att convention
|
// push references using subject-digest.att convention
|
||||||
err = mirror.PushImageToRegistry(img.AttestationImage.Image, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerUrl.Host, repoName))
|
image, err := signedManifest.BuildAttestationImage()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = mirror.PushImageToRegistry(image, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerUrl.Host, repoName))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
refServer := tc.refServer
|
refServer := tc.refServer
|
||||||
|
|||||||
@@ -31,9 +31,8 @@ type AttestationLayer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AttestationImage struct {
|
type AttestationImage struct {
|
||||||
Descriptor *v1.Descriptor
|
OriginalLayers []*AttestationLayer
|
||||||
Layers []*AttestationLayer
|
signedLayers []*AttestationLayer
|
||||||
Image v1.Image
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AttestationManifest struct {
|
type AttestationManifest struct {
|
||||||
@@ -47,6 +46,13 @@ type AttestationManifest struct {
|
|||||||
SubjectDescriptor *v1.Descriptor
|
SubjectDescriptor *v1.Descriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AttestationManifestImageOptions struct {
|
||||||
|
// how to output the image
|
||||||
|
skipSubject bool
|
||||||
|
replaceLayers bool
|
||||||
|
strictReferrers bool
|
||||||
|
}
|
||||||
|
|
||||||
// the following types are needed until https://github.com/secure-systems-lab/dsse/pull/61 is merged
|
// the following types are needed until https://github.com/secure-systems-lab/dsse/pull/61 is merged
|
||||||
type Envelope struct {
|
type Envelope struct {
|
||||||
PayloadType string `json:"payloadType"`
|
PayloadType string `json:"payloadType"`
|
||||||
@@ -78,12 +84,8 @@ type VerifyOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SigningOptions struct {
|
type SigningOptions struct {
|
||||||
// replace unsigned statements with signed attestations
|
|
||||||
Replace bool
|
|
||||||
// don't log to the configured transparency log
|
// don't log to the configured transparency log
|
||||||
SkipTL bool
|
SkipTL bool
|
||||||
// don't add OCI subject field to attestation image
|
|
||||||
SkipSubject bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DSSEMediaType(predicateType string) (string, error) {
|
func DSSEMediaType(predicateType string) (string, error) {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func PushImageToRegistry(image v1.Image, imageName string) error {
|
|||||||
return remote.Write(ref, image, oci.MultiKeychainOption())
|
return remote.Write(ref, image, oci.MultiKeychainOption())
|
||||||
}
|
}
|
||||||
|
|
||||||
func PushIndexToRegistry(image v1.ImageIndex, imageName string) error {
|
func PushIndexToRegistry(index v1.ImageIndex, imageName string) error {
|
||||||
// Parse the index name
|
// Parse the index name
|
||||||
ref, err := name.ParseReference(imageName)
|
ref, err := name.ParseReference(imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -45,7 +45,7 @@ func PushIndexToRegistry(image v1.ImageIndex, imageName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Push the index to the registry
|
// Push the index to the registry
|
||||||
return remote.WriteIndex(ref, image, oci.MultiKeychainOption())
|
return remote.WriteIndex(ref, index, oci.MultiKeychainOption())
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveImageAsOCILayout(image v1.Image, path string) error {
|
func SaveImageAsOCILayout(image v1.Image, path string) error {
|
||||||
@@ -132,20 +132,8 @@ func SaveReferrers(manifest *attestation.AttestationManifest, outputs []*oci.Ima
|
|||||||
if output.Type == oci.OCI {
|
if output.Type == oci.OCI {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ociManifest, err := manifest.AttestationImage.Image.Manifest()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get manifest: %w", err)
|
|
||||||
}
|
|
||||||
refDigest := ociManifest.Annotations[attestation.DockerReferenceDigest]
|
|
||||||
if refDigest == "" {
|
|
||||||
return fmt.Errorf("no digest found in manifest")
|
|
||||||
}
|
|
||||||
hash, err := v1.NewHash(refDigest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to parse digest: %w", err)
|
|
||||||
}
|
|
||||||
// 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)
|
// 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, hash)
|
attOut, err := oci.ReplaceTagInSpec(output, manifest.SubjectDescriptor.Digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -154,8 +142,13 @@ func SaveReferrers(manifest *attestation.AttestationManifest, outputs []*oci.Ima
|
|||||||
OS: "unknown",
|
OS: "unknown",
|
||||||
Architecture: "unknown",
|
Architecture: "unknown",
|
||||||
}
|
}
|
||||||
|
images, err := manifest.BuildReferringArtifacts()
|
||||||
err = SaveImage(attOut, manifest.AttestationImage.Image, "")
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to build image: %w", err)
|
||||||
|
}
|
||||||
|
for _, image := range images {
|
||||||
|
err = SaveImage(attOut, image, "")
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to push image: %w", err)
|
return fmt.Errorf("failed to push image: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,22 +43,13 @@ func (r *OCILayoutResolver) fetchAttestationManifest() (*attestation.Attestation
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *OCILayoutResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) {
|
func (r *OCILayoutResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) {
|
||||||
attestationImage := r.AttestationManifest.AttestationImage
|
|
||||||
layers, err := attestationImage.Image.Layers()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to extract layers from attestation image: %w", err)
|
|
||||||
}
|
|
||||||
var envs []*att.Envelope
|
var envs []*att.Envelope
|
||||||
manifest, err := r.AttestationManifest.AttestationImage.Image.Manifest()
|
for _, attestationLayer := range r.AttestationManifest.AttestationImage.OriginalLayers {
|
||||||
if err != nil {
|
if attestationLayer.Annotations[attestation.InTotoPredicateType] != predicateType {
|
||||||
return nil, fmt.Errorf("failed to get manifest: %w", err)
|
|
||||||
}
|
|
||||||
for i, l := range manifest.Layers {
|
|
||||||
if l.Annotations[attestation.InTotoPredicateType] != predicateType {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
layer := layers[i]
|
|
||||||
mt, err := layer.MediaType()
|
mt, err := attestationLayer.Layer.MediaType()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get layer media type: %w", err)
|
return nil, fmt.Errorf("failed to get layer media type: %w", err)
|
||||||
}
|
}
|
||||||
@@ -68,7 +59,7 @@ func (r *OCILayoutResolver) Attestations(ctx context.Context, predicateType stri
|
|||||||
}
|
}
|
||||||
var env = new(att.Envelope)
|
var env = new(att.Envelope)
|
||||||
// parse layer blob as json
|
// parse layer blob as json
|
||||||
r, err := layer.Uncompressed()
|
r, err := attestationLayer.Layer.Uncompressed()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get layer contents: %w", err)
|
return nil, fmt.Errorf("failed to get layer contents: %w", err)
|
||||||
@@ -138,8 +129,12 @@ func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*atte
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", mf.Digest.String(), err)
|
return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", mf.Digest.String(), err)
|
||||||
}
|
}
|
||||||
|
layers, err := attestation.GetAttestationsFromImage(attestationImage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
|
||||||
|
}
|
||||||
attest := &attestation.AttestationManifest{
|
attest := &attestation.AttestationManifest{
|
||||||
AttestationImage: &att.AttestationImage{Image: attestationImage},
|
AttestationImage: &att.AttestationImage{OriginalLayers: layers},
|
||||||
OriginalDescriptor: &mf,
|
OriginalDescriptor: &mf,
|
||||||
SubjectName: name,
|
SubjectName: name,
|
||||||
SubjectDescriptor: subjectDescriptor,
|
SubjectDescriptor: subjectDescriptor,
|
||||||
|
|||||||
@@ -47,21 +47,17 @@ func WithOptions(ctx context.Context, platform *v1.Platform) []remote.Option {
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtractEnvelopes(ia *attestation.AttestationManifest, predicateType string) ([]*att.Envelope, error) {
|
func ExtractEnvelopes(manifest *attestation.AttestationManifest, predicateType string) ([]*att.Envelope, error) {
|
||||||
manifest, err := ia.AttestationImage.Image.Manifest()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get manifest: %w", err)
|
|
||||||
}
|
|
||||||
var envs []*att.Envelope
|
var envs []*att.Envelope
|
||||||
layers, err := ia.AttestationImage.Image.Layers()
|
for _, attestationLayer := range manifest.AttestationImage.OriginalLayers {
|
||||||
if err != nil {
|
mt, err := attestationLayer.Layer.MediaType()
|
||||||
return nil, fmt.Errorf("failed to get layers: %w", err)
|
if err != nil {
|
||||||
}
|
return nil, fmt.Errorf("failed to get layer media type: %w", err)
|
||||||
for i, l := range manifest.Layers {
|
}
|
||||||
if (strings.HasPrefix(string(l.MediaType), "application/vnd.in-toto.")) &&
|
if (strings.HasPrefix(string(mt), "application/vnd.in-toto.")) &&
|
||||||
strings.HasSuffix(string(l.MediaType), "+dsse") &&
|
strings.HasSuffix(string(mt), "+dsse") &&
|
||||||
l.Annotations[att.InTotoPredicateType] == predicateType {
|
attestationLayer.Annotations[att.InTotoPredicateType] == predicateType {
|
||||||
reader, err := layers[i].Uncompressed()
|
reader, err := attestationLayer.Layer.Uncompressed()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get layer contents: %w", err)
|
return nil, fmt.Errorf("failed to get layer contents: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,9 +80,13 @@ func (r *ReferrersResolver) resolveAttestations(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get referred image: %w", err)
|
return fmt.Errorf("failed to get referred image: %w", err)
|
||||||
}
|
}
|
||||||
|
layers, err := attestation.GetAttestationsFromImage(attestationImage)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get attestations from image: %w", err)
|
||||||
|
}
|
||||||
attest := &attestation.AttestationManifest{
|
attest := &attestation.AttestationManifest{
|
||||||
SubjectName: r.Identifier,
|
SubjectName: r.Identifier,
|
||||||
AttestationImage: &attestation.AttestationImage{Image: attestationImage},
|
AttestationImage: &attestation.AttestationImage{OriginalLayers: layers},
|
||||||
OriginalDescriptor: &m,
|
OriginalDescriptor: &m,
|
||||||
SubjectDescriptor: desc,
|
SubjectDescriptor: desc,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,8 +127,12 @@ func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Pl
|
|||||||
return nil, fmt.Errorf("failed to get attestation image: %w", err)
|
return nil, fmt.Errorf("failed to get attestation image: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layers, err := attestation.GetAttestationsFromImage(attestationImage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
|
||||||
|
}
|
||||||
attest := &attestation.AttestationManifest{
|
attest := &attestation.AttestationManifest{
|
||||||
AttestationImage: &att.AttestationImage{Image: attestationImage},
|
AttestationImage: &att.AttestationImage{OriginalLayers: layers},
|
||||||
OriginalDescriptor: &remoteDescriptor.Descriptor,
|
OriginalDescriptor: &remoteDescriptor.Descriptor,
|
||||||
SubjectName: image,
|
SubjectName: image,
|
||||||
SubjectDescriptor: subjectDescriptor,
|
SubjectDescriptor: subjectDescriptor,
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ func TestRegistry(t *testing.T) {
|
|||||||
u, err := url.Parse(server.URL)
|
u, err := url.Parse(server.URL)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
opts := &attestation.SigningOptions{
|
opts := &attestation.SigningOptions{}
|
||||||
Replace: true,
|
|
||||||
SkipSubject: true,
|
|
||||||
}
|
|
||||||
attIdx, err := oci.IndexFromPath(oci.UnsignedTestImage)
|
attIdx, err := oci.IndexFromPath(oci.UnsignedTestImage)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
||||||
|
|||||||
@@ -2,16 +2,6 @@
|
|||||||
|
|
||||||
package signerverifier
|
package signerverifier
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/attest/internal/util"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
const publicKeyPEM = `-----BEGIN PUBLIC KEY-----
|
const publicKeyPEM = `-----BEGIN PUBLIC KEY-----
|
||||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMswW3iu7PR/rWTQjlhVmUsPK7rF
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMswW3iu7PR/rWTQjlhVmUsPK7rF
|
||||||
k2s4SO3XbQ2GG2alm289SUUpmBAuVxvT8muYQ8HC/QzixzyTACTXsBDjQg==
|
k2s4SO3XbQ2GG2alm289SUUpmBAuVxvT8muYQ8HC/QzixzyTACTXsBDjQg==
|
||||||
@@ -20,11 +10,7 @@ k2s4SO3XbQ2GG2alm289SUUpmBAuVxvT8muYQ8HC/QzixzyTACTXsBDjQg==
|
|||||||
// to run locally, we need to impersonate the GCP service account
|
// to run locally, we need to impersonate the GCP service account
|
||||||
// gcloud auth application-default login --impersonate-service-account attest-kms-test@attest-kms-test.iam.gserviceaccount.com
|
// gcloud auth application-default login --impersonate-service-account attest-kms-test@attest-kms-test.iam.gserviceaccount.com
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
func TestGCPKMS_Signer(t *testing.T) {
|
func TestGCPKMS_Signer(t *testing.T) {
|
||||||
=======
|
|
||||||
func _TestGCPKMS_Signer(t *testing.T) {
|
|
||||||
>>>>>>> 56ef672 (Single attestation when creating VSA)
|
|
||||||
// create a new signer
|
// create a new signer
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ref := "projects/attest-kms-test/locations/us-west1/keyRings/attest-kms-test/cryptoKeys/test-signing-key/cryptoKeyVersions/1"
|
ref := "projects/attest-kms-test/locations/us-west1/keyRings/attest-kms-test/cryptoKeys/test-signing-key/cryptoKeyVersions/1"
|
||||||
|
|||||||
Reference in New Issue
Block a user