Handle errors from Go in Rego. Support for skipping TL (#47)
* Make TL logging/verification optional * Return errors from go-lang fns * Update pkg/policy/rego.go Co-authored-by: Jonny Stoten <jonny@jonnystoten.com> * Update pkg/attestation/sign.go Co-authored-by: Joel Kamp <joel.kamp@docker.com> * Move public key marshelling until later * Simplify logSignature and pass down opts --------- Co-authored-by: Jonny Stoten <jonny@jonnystoten.com> Co-authored-by: Joel Kamp <joel.kamp@docker.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/docker/attest/pkg/attest"
|
"github.com/docker/attest/pkg/attest"
|
||||||
|
"github.com/docker/attest/pkg/attestation"
|
||||||
"github.com/docker/attest/pkg/mirror"
|
"github.com/docker/attest/pkg/mirror"
|
||||||
"github.com/docker/attest/pkg/oci"
|
"github.com/docker/attest/pkg/oci"
|
||||||
"github.com/docker/attest/pkg/signerverifier"
|
"github.com/docker/attest/pkg/signerverifier"
|
||||||
@@ -25,7 +26,7 @@ func ExampleSign_remote() {
|
|||||||
// signer, err := signerverifier.GetAWSSigner(cmd.Context(), aws_arn, aws_region)
|
// signer, err := signerverifier.GetAWSSigner(cmd.Context(), aws_arn, aws_region)
|
||||||
|
|
||||||
// configure signing options
|
// configure signing options
|
||||||
opts := &attest.SigningOptions{
|
opts := &attestation.SigningOptions{
|
||||||
Replace: true, // replace unsigned intoto statements with signed intoto attestations, otherwise leave in place
|
Replace: true, // replace unsigned intoto statements with signed intoto attestations, otherwise leave in place
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Sign(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVerifier, opts *SigningOptions) (v1.ImageIndex, error) {
|
func Sign(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVerifier, opts *attestation.SigningOptions) (v1.ImageIndex, error) {
|
||||||
// extract attestation manifests from index
|
// extract attestation manifests from index
|
||||||
attestationManifests, err := attestation.GetAttestationManifestsFromIndex(idx)
|
attestationManifests, err := attestation.GetAttestationManifestsFromIndex(idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -63,7 +63,7 @@ func AddAttestation(ctx context.Context, idx v1.ImageIndex, statement *intoto.St
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
// hard-coding replace to false here, because if it's true we will remove any unsigned statements, even unrelated ones
|
// hard-coding replace to false here, because if it's true we will remove any unsigned statements, even unrelated ones
|
||||||
idx, err = signLayersAndAddToIndex(ctx, idx, attestationLayers, manifest, signer, &SigningOptions{Replace: false})
|
idx, err = signLayersAndAddToIndex(ctx, idx, attestationLayers, manifest, signer, &attestation.SigningOptions{Replace: false})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to add signed layers: %w", err)
|
return nil, fmt.Errorf("failed to add signed layers: %w", err)
|
||||||
}
|
}
|
||||||
@@ -83,9 +83,9 @@ func signLayersAndAddToIndex(
|
|||||||
attestationLayers []attestation.AttestationLayer,
|
attestationLayers []attestation.AttestationLayer,
|
||||||
manifest attestation.AttestationManifest,
|
manifest attestation.AttestationManifest,
|
||||||
signer dsse.SignerVerifier,
|
signer dsse.SignerVerifier,
|
||||||
opts *SigningOptions) (v1.ImageIndex, error) {
|
opts *attestation.SigningOptions) (v1.ImageIndex, error) {
|
||||||
|
|
||||||
signedLayers, err := signLayers(ctx, attestationLayers, signer)
|
signedLayers, err := signLayers(ctx, attestationLayers, signer, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to sign attestations: %w", err)
|
return nil, fmt.Errorf("failed to sign attestations: %w", err)
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ func signLayersAndAddToIndex(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// signLayers signs each intoto attestation layer with the given signer
|
// signLayers signs each intoto attestation layer with the given signer
|
||||||
func signLayers(ctx context.Context, layers []attestation.AttestationLayer, signer dsse.SignerVerifier) ([]mutate.Addendum, error) {
|
func signLayers(ctx context.Context, layers []attestation.AttestationLayer, signer dsse.SignerVerifier, opts *attestation.SigningOptions) ([]mutate.Addendum, error) {
|
||||||
var signedLayers []mutate.Addendum
|
var signedLayers []mutate.Addendum
|
||||||
for _, layer := range layers {
|
for _, layer := range layers {
|
||||||
// only sign intoto layers
|
// only sign intoto layers
|
||||||
@@ -131,10 +131,11 @@ func signLayers(ctx context.Context, layers []attestation.AttestationLayer, sign
|
|||||||
layer.Annotations[InTotoReferenceLifecycleStage] = LifecycleStageExperimental
|
layer.Annotations[InTotoReferenceLifecycleStage] = LifecycleStageExperimental
|
||||||
|
|
||||||
// sign the statement
|
// sign the statement
|
||||||
env, err := signInTotoStatement(ctx, layer.Statement, signer)
|
env, err := signInTotoStatement(ctx, layer.Statement, signer, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaType, err := attestation.DSSEMediaType(layer.Statement.PredicateType)
|
mediaType, err := attestation.DSSEMediaType(layer.Statement.PredicateType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get DSSE media type: %w", err)
|
return nil, fmt.Errorf("failed to get DSSE media type: %w", err)
|
||||||
@@ -153,12 +154,12 @@ func signLayers(ctx context.Context, layers []attestation.AttestationLayer, sign
|
|||||||
return signedLayers, nil
|
return signedLayers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func signInTotoStatement(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier) (*attestation.Envelope, error) {
|
func signInTotoStatement(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier, opts *attestation.SigningOptions) (*attestation.Envelope, error) {
|
||||||
payload, err := json.Marshal(statement)
|
payload, err := json.Marshal(statement)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal statement: %w", err)
|
return nil, fmt.Errorf("failed to marshal statement: %w", err)
|
||||||
}
|
}
|
||||||
env, err := attestation.SignDSSE(ctx, payload, intoto.PayloadType, signer)
|
env, err := attestation.SignDSSE(ctx, payload, signer, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||||
}
|
}
|
||||||
@@ -166,7 +167,7 @@ func signInTotoStatement(ctx context.Context, statement *intoto.Statement, signe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// addSignedLayers adds signed layers to a new or existing attestation image
|
// addSignedLayers adds signed layers to a new or existing attestation image
|
||||||
func addSignedLayers(signedLayers []mutate.Addendum, manifest attestation.AttestationManifest, opts *SigningOptions) (v1.Image, error) {
|
func addSignedLayers(signedLayers []mutate.Addendum, manifest attestation.AttestationManifest, opts *attestation.SigningOptions) (v1.Image, error) {
|
||||||
var err error
|
var err error
|
||||||
if opts.Replace {
|
if opts.Replace {
|
||||||
// create a new attestation image with only signed layers
|
// create a new attestation image with only signed layers
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ var (
|
|||||||
UnsignedTestImage = filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
|
UnsignedTestImage = filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
|
||||||
NoProvenanceImage = filepath.Join("..", "..", "test", "testdata", "no-provenance-image")
|
NoProvenanceImage = filepath.Join("..", "..", "test", "testdata", "no-provenance-image")
|
||||||
PassPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-pass")
|
PassPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-pass")
|
||||||
|
PassNoTLPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-no-tl")
|
||||||
FailPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-fail")
|
FailPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-fail")
|
||||||
TestTempDir = "attest-sign-test"
|
TestTempDir = "attest-sign-test"
|
||||||
)
|
)
|
||||||
@@ -53,7 +54,7 @@ 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 := &SigningOptions{
|
opts := &attestation.SigningOptions{
|
||||||
Replace: tc.replace,
|
Replace: tc.replace,
|
||||||
}
|
}
|
||||||
attIdx, err := oci.AttestationIndexFromPath(tc.TestImage)
|
attIdx, err := oci.AttestationIndexFromPath(tc.TestImage)
|
||||||
@@ -185,7 +186,7 @@ func TestAddSignedLayerAnnotations(t *testing.T) {
|
|||||||
data = []byte("test")
|
data = []byte("test")
|
||||||
testLayer := static.NewLayer(data, types.MediaType(intoto.PayloadType))
|
testLayer := static.NewLayer(data, types.MediaType(intoto.PayloadType))
|
||||||
mediaType := types.OCIManifestSchema1
|
mediaType := types.OCIManifestSchema1
|
||||||
opts := &SigningOptions{
|
opts := &attestation.SigningOptions{
|
||||||
Replace: tc.replace,
|
Replace: tc.replace,
|
||||||
}
|
}
|
||||||
manifest := attestation.AttestationManifest{
|
manifest := attestation.AttestationManifest{
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ const (
|
|||||||
LifecycleStageExperimental = "experimental"
|
LifecycleStageExperimental = "experimental"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SigningOptions struct {
|
|
||||||
Replace bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Outcome string
|
type Outcome string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -73,7 +73,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 := &SigningOptions{
|
opts := &attestation.SigningOptions{
|
||||||
Replace: true,
|
Replace: true,
|
||||||
}
|
}
|
||||||
attIdx, err := oci.AttestationIndexFromPath(UnsignedTestImage)
|
attIdx, err := oci.AttestationIndexFromPath(UnsignedTestImage)
|
||||||
@@ -94,7 +94,6 @@ func TestVSA(t *testing.T) {
|
|||||||
_, err = layout.Write(outputLayout, idx)
|
_, err = layout.Write(outputLayout, idx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
//verify (without vsa should fail)
|
|
||||||
resolver, err := oci.NewOCILayoutAttestationResolver(outputLayout, "linux/amd64")
|
resolver, err := oci.NewOCILayoutAttestationResolver(outputLayout, "linux/amd64")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -126,7 +125,7 @@ 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 := &SigningOptions{
|
opts := &attestation.SigningOptions{
|
||||||
Replace: true,
|
Replace: true,
|
||||||
}
|
}
|
||||||
attIdx, err := oci.AttestationIndexFromPath(UnsignedTestImage)
|
attIdx, err := oci.AttestationIndexFromPath(UnsignedTestImage)
|
||||||
@@ -147,11 +146,10 @@ func TestVerificationFailure(t *testing.T) {
|
|||||||
_, err = layout.Write(outputLayout, idx)
|
_, err = layout.Write(outputLayout, idx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
//verify (without vsa should fail)
|
|
||||||
resolver, err := oci.NewOCILayoutAttestationResolver(outputLayout, "linux/amd64")
|
resolver, err := oci.NewOCILayoutAttestationResolver(outputLayout, "linux/amd64")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// mocked vsa query should pass
|
// mocked vsa query should fail
|
||||||
policyOpts := &policy.PolicyOptions{
|
policyOpts := &policy.PolicyOptions{
|
||||||
LocalPolicyDir: FailPolicyDir,
|
LocalPolicyDir: FailPolicyDir,
|
||||||
}
|
}
|
||||||
@@ -177,3 +175,60 @@ func TestVerificationFailure(t *testing.T) {
|
|||||||
assert.Equal(t, []string{"SLSA_BUILD_LEVEL_3"}, attestationPredicate.VerifiedLevels)
|
assert.Equal(t, []string{"SLSA_BUILD_LEVEL_3"}, attestationPredicate.VerifiedLevels)
|
||||||
assert.Equal(t, "https://docker.com/official/policy/v0.1", attestationPredicate.Policy.URI)
|
assert.Equal(t, "https://docker.com/official/policy/v0.1", attestationPredicate.Policy.URI)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test signing without a TL entry
|
||||||
|
func TestSignVerifyNoTL(t *testing.T) {
|
||||||
|
ctx, signer := test.Setup(t)
|
||||||
|
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
|
||||||
|
// setup an image with signed attestations
|
||||||
|
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
signTL bool
|
||||||
|
policyDir string
|
||||||
|
success bool
|
||||||
|
}{
|
||||||
|
{name: "happy path", signTL: true, policyDir: PassNoTLPolicyDir, success: true},
|
||||||
|
{name: "sign tl, verify no tl", signTL: true, policyDir: PassPolicyDir, success: false},
|
||||||
|
{name: "no tl", signTL: false, policyDir: PassPolicyDir, success: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
attIdx, err := oci.AttestationIndexFromPath(UnsignedTestImage)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
opts := &attestation.SigningOptions{
|
||||||
|
Replace: true,
|
||||||
|
SkipTL: tc.signTL,
|
||||||
|
}
|
||||||
|
|
||||||
|
signedIndex, err := Sign(ctx, attIdx.Index, signer, opts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// output signed attestations
|
||||||
|
idx := v1.ImageIndex(empty.Index)
|
||||||
|
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||||
|
Add: signedIndex,
|
||||||
|
Descriptor: v1.Descriptor{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
oci.OciReferenceTarget: attIdx.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
_, err = layout.Write(outputLayout, idx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
resolver, err := oci.NewOCILayoutAttestationResolver(outputLayout, "linux/amd64")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
policyOpts := &policy.PolicyOptions{
|
||||||
|
LocalPolicyDir: tc.policyDir,
|
||||||
|
}
|
||||||
|
results, err := Verify(ctx, policyOpts, resolver)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, OutcomeSuccess, results.Outcome)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,13 +6,16 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/attest/internal/util"
|
"github.com/docker/attest/internal/util"
|
||||||
"github.com/docker/attest/pkg/tlog"
|
"github.com/docker/attest/pkg/tlog"
|
||||||
|
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SignDSSE signs a payload with a given signer and uploads the signature to the transparency log
|
// SignDSSE signs a payload with a given signer and uploads the signature to the transparency log
|
||||||
func SignDSSE(ctx context.Context, payload []byte, payloadType string, signer dsse.SignerVerifier) (*Envelope, error) {
|
func SignDSSE(ctx context.Context, payload []byte, signer dsse.SignerVerifier, opts *SigningOptions) (*Envelope, error) {
|
||||||
t := tlog.GetTL(ctx)
|
payloadType := opts.PayloadType
|
||||||
|
if payloadType == "" {
|
||||||
|
payloadType = intoto.PayloadType
|
||||||
|
}
|
||||||
env := new(Envelope)
|
env := new(Envelope)
|
||||||
env.Payload = base64Encoding.EncodeToString(payload)
|
env.Payload = base64Encoding.EncodeToString(payload)
|
||||||
env.PayloadType = payloadType
|
env.PayloadType = payloadType
|
||||||
@@ -33,8 +36,31 @@ func SignDSSE(ctx context.Context, payload []byte, payloadType string, signer ds
|
|||||||
return nil, fmt.Errorf("error getting public key ID: %w", err)
|
return nil, fmt.Errorf("error getting public key ID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// upload to TL
|
dsseSig := Signature{
|
||||||
entry, err := t.UploadLogEntry(ctx, keyId, encPayload, sig, signer)
|
KeyID: keyId,
|
||||||
|
Sig: base64Encoding.EncodeToString(sig),
|
||||||
|
}
|
||||||
|
if !opts.SkipTL {
|
||||||
|
ext, err := logSignature(ctx, tlog.GetTL(ctx), &sig, &encPayload, signer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to log to rekor: %w", err)
|
||||||
|
}
|
||||||
|
dsseSig.Extension = *ext
|
||||||
|
}
|
||||||
|
// add signature to dsse envelope
|
||||||
|
env.Signatures = []Signature{dsseSig}
|
||||||
|
|
||||||
|
return env, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a new envelope with the transparency log entry added to the signature extension
|
||||||
|
func logSignature(ctx context.Context, t tlog.TL, sig *[]byte, encPayload *[]byte, signer dsse.SignerVerifier) (*Extension, error) {
|
||||||
|
// get Key ID from signer
|
||||||
|
keyId, err := signer.KeyID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting public key ID: %w", err)
|
||||||
|
}
|
||||||
|
entry, err := t.UploadLogEntry(ctx, keyId, *encPayload, *sig, signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error uploading TL entry: %w", err)
|
return nil, fmt.Errorf("error uploading TL entry: %w", err)
|
||||||
}
|
}
|
||||||
@@ -42,21 +68,13 @@ func SignDSSE(ctx context.Context, payload []byte, payloadType string, signer ds
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error unmarshaling tl entry: %w", err)
|
return nil, fmt.Errorf("error unmarshaling tl entry: %w", err)
|
||||||
}
|
}
|
||||||
|
return &Extension{
|
||||||
// add signature w/ tl extension to dsse envelope
|
Kind: DockerDsseExtKind,
|
||||||
env.Signatures = append(env.Signatures, Signature{
|
Ext: DockerDsseExtension{
|
||||||
KeyID: keyId,
|
Tl: DockerTlExtension{
|
||||||
Sig: base64Encoding.EncodeToString(sig),
|
Kind: RekorTlExtKind,
|
||||||
Extension: Extension{
|
Data: entryObj, // transparency log entry metadata
|
||||||
Kind: DockerDsseExtKind,
|
|
||||||
Ext: DockerDsseExtension{
|
|
||||||
Tl: DockerTlExtension{
|
|
||||||
Kind: RekorTlExtKind,
|
|
||||||
Data: entryObj, // transparency log entry metadata
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}, nil
|
||||||
|
|
||||||
return env, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/docker/attest/pkg/signerverifier"
|
"github.com/docker/attest/pkg/signerverifier"
|
||||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSignVerifyAttestation(t *testing.T) {
|
func TestSignVerifyAttestation(t *testing.T) {
|
||||||
@@ -27,17 +28,17 @@ func TestSignVerifyAttestation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
payload, err := json.Marshal(stmt)
|
payload, err := json.Marshal(stmt)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
opts := &attestation.SigningOptions{}
|
||||||
env, err := attestation.SignDSSE(ctx, payload, intoto.PayloadType, signer)
|
env, err := attestation.SignDSSE(ctx, payload, signer, opts)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// marshal envelope to json to test for bugs when marshaling envelope data
|
// marshal envelope to json to test for bugs when marshaling envelope data
|
||||||
serializedEnv, err := json.Marshal(env)
|
serializedEnv, err := json.Marshal(env)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
deserializedEnv := new(attestation.Envelope)
|
deserializedEnv := new(attestation.Envelope)
|
||||||
err = json.Unmarshal(serializedEnv, deserializedEnv)
|
err = json.Unmarshal(serializedEnv, deserializedEnv)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// signer.Public() calls AWS API when using AWS signer, use attestation.GetPublicVerificationKey() to get key from TUF repo
|
// signer.Public() calls AWS API when using AWS signer, use attestation.GetPublicVerificationKey() to get key from TUF repo
|
||||||
// signer.Public() used here for test purposes
|
// signer.Public() used here for test purposes
|
||||||
@@ -49,10 +50,10 @@ func TestSignVerifyAttestation(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
badKeyPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
badKeyPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
badKey := &badKeyPriv.PublicKey
|
badKey := &badKeyPriv.PublicKey
|
||||||
badPEM, err := signerverifier.ToPEM(badKey)
|
badPEM, err := signerverifier.ToPEM(badKey)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -136,7 +137,10 @@ func TestSignVerifyAttestation(t *testing.T) {
|
|||||||
To: tc.to,
|
To: tc.to,
|
||||||
Status: tc.status,
|
Status: tc.status,
|
||||||
}
|
}
|
||||||
_, err = attestation.VerifyDSSE(ctx, deserializedEnv, attestation.KeysMap{tc.keyId: keyMeta})
|
opts := &attestation.VerifyOptions{
|
||||||
|
Keys: attestation.Keys{keyMeta},
|
||||||
|
}
|
||||||
|
_, err = attestation.VerifyDSSE(ctx, deserializedEnv, opts)
|
||||||
if tc.expectedError != "" {
|
if tc.expectedError != "" {
|
||||||
assert.Contains(t, err.Error(), tc.expectedError)
|
assert.Contains(t, err.Error(), tc.expectedError)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -66,6 +66,17 @@ type DockerTlExtension struct {
|
|||||||
Data any `json:"data"`
|
Data any `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VerifyOptions struct {
|
||||||
|
Keys []KeyMetadata `json:"keys"`
|
||||||
|
SkipTL bool `json:"skip_tl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SigningOptions struct {
|
||||||
|
Replace bool
|
||||||
|
SkipTL bool
|
||||||
|
PayloadType string
|
||||||
|
}
|
||||||
|
|
||||||
func DSSEMediaType(predicateType string) (string, error) {
|
func DSSEMediaType(predicateType string) (string, error) {
|
||||||
var predicateName string
|
var predicateName string
|
||||||
switch predicateType {
|
switch predicateType {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type KeyMetadata struct {
|
|||||||
type Keys []KeyMetadata
|
type Keys []KeyMetadata
|
||||||
type KeysMap map[string]KeyMetadata
|
type KeysMap map[string]KeyMetadata
|
||||||
|
|
||||||
func VerifyDSSE(ctx context.Context, env *Envelope, keys KeysMap) ([]byte, error) {
|
func VerifyDSSE(ctx context.Context, env *Envelope, opts *VerifyOptions) ([]byte, error) {
|
||||||
// enforce payload type
|
// enforce payload type
|
||||||
if !ValidPayloadType(env.PayloadType) {
|
if !ValidPayloadType(env.PayloadType) {
|
||||||
return nil, fmt.Errorf("unsupported payload type %s", env.PayloadType)
|
return nil, fmt.Errorf("unsupported payload type %s", env.PayloadType)
|
||||||
@@ -49,7 +49,7 @@ func VerifyDSSE(ctx context.Context, env *Envelope, keys KeysMap) ([]byte, error
|
|||||||
|
|
||||||
// verify signatures and transparency log entry
|
// verify signatures and transparency log entry
|
||||||
for _, sig := range env.Signatures {
|
for _, sig := range env.Signatures {
|
||||||
err := verifySignature(ctx, sig, encPayload, keys)
|
err := verifySignature(ctx, sig, encPayload, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -58,31 +58,11 @@ func VerifyDSSE(ctx context.Context, env *Envelope, keys KeysMap) ([]byte, error
|
|||||||
return payload, nil
|
return payload, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifySignature(ctx context.Context, sig Signature, payload []byte, keys KeysMap) error {
|
func verifySignature(ctx context.Context, sig Signature, payload []byte, opts *VerifyOptions) error {
|
||||||
t := tlog.GetTL(ctx)
|
keys := make(map[string]KeyMetadata, len(opts.Keys))
|
||||||
|
for _, key := range opts.Keys {
|
||||||
if sig.Extension.Kind == "" {
|
keys[key.ID] = key
|
||||||
return fmt.Errorf("error missing signature extension kind")
|
|
||||||
}
|
}
|
||||||
if sig.Extension.Kind != DockerDsseExtKind {
|
|
||||||
return fmt.Errorf("error unsupported signature extension kind: %s", sig.Extension.Kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify TL entry
|
|
||||||
if sig.Extension.Ext.Tl.Kind != RekorTlExtKind {
|
|
||||||
return fmt.Errorf("error unsupported TL extension kind: %s", sig.Extension.Ext.Tl.Kind)
|
|
||||||
}
|
|
||||||
entry := sig.Extension.Ext.Tl.Data
|
|
||||||
entryBytes, err := json.Marshal(entry)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to marshal TL entry: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
integratedTime, err := t.VerifyLogEntry(ctx, entryBytes)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("TL entry failed verification: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyMeta, ok := keys[sig.KeyID]
|
keyMeta, ok := keys[sig.KeyID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("error key not found: %s", sig.KeyID)
|
return fmt.Errorf("error key not found: %s", sig.KeyID)
|
||||||
@@ -91,30 +71,53 @@ func verifySignature(ctx context.Context, sig Signature, payload []byte, keys Ke
|
|||||||
if keyMeta.Distrust {
|
if keyMeta.Distrust {
|
||||||
return fmt.Errorf("key %s is distrusted", keyMeta.ID)
|
return fmt.Errorf("key %s is distrusted", keyMeta.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if integratedTime.Before(keyMeta.From) {
|
|
||||||
return fmt.Errorf("key %s was not yet valid at TL log time %s (key valid from %s)", keyMeta.ID, integratedTime, keyMeta.From)
|
|
||||||
}
|
|
||||||
|
|
||||||
if keyMeta.To != nil && !integratedTime.Before(*keyMeta.To) {
|
|
||||||
return fmt.Errorf("key %s was already %s at TL log time %s (key %s at %s)", keyMeta.ID, keyMeta.Status, integratedTime, keyMeta.Status, *keyMeta.To)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this is unmarshalling with MarshalPKIXPublicKey only for us to marshal it again
|
// TODO: this is unmarshalling with MarshalPKIXPublicKey only for us to marshal it again
|
||||||
publicKey, err := signerverifier.Parse([]byte(keyMeta.PEM))
|
publicKey, err := signerverifier.Parse([]byte(keyMeta.PEM))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse public key: %w", err)
|
return fmt.Errorf("failed to parse public key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify TL entry payload
|
if !opts.SkipTL {
|
||||||
encodedPub, err := x509.MarshalPKIXPublicKey(publicKey)
|
t := tlog.GetTL(ctx)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error failed to marshal public key: %w", err)
|
if sig.Extension.Kind == "" {
|
||||||
}
|
return fmt.Errorf("error missing signature extension kind")
|
||||||
err = t.VerifyEntryPayload(entryBytes, payload, encodedPub)
|
}
|
||||||
if err != nil {
|
if sig.Extension.Kind != DockerDsseExtKind {
|
||||||
return fmt.Errorf("TL entry failed payload verification: %w", err)
|
return fmt.Errorf("error unsupported signature extension kind: %s", sig.Extension.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify TL entry
|
||||||
|
if sig.Extension.Ext.Tl.Kind != RekorTlExtKind {
|
||||||
|
return fmt.Errorf("error unsupported TL extension kind: %s", sig.Extension.Ext.Tl.Kind)
|
||||||
|
}
|
||||||
|
entry := sig.Extension.Ext.Tl.Data
|
||||||
|
entryBytes, err := json.Marshal(entry)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal TL entry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
integratedTime, err := t.VerifyLogEntry(ctx, entryBytes)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("TL entry failed verification: %w", err)
|
||||||
|
}
|
||||||
|
if integratedTime.Before(keyMeta.From) {
|
||||||
|
return fmt.Errorf("key %s was not yet valid at TL log time %s (key valid from %s)", keyMeta.ID, integratedTime, keyMeta.From)
|
||||||
|
}
|
||||||
|
if keyMeta.To != nil && !integratedTime.Before(*keyMeta.To) {
|
||||||
|
return fmt.Errorf("key %s was already %s at TL log time %s (key %s at %s)", keyMeta.ID, keyMeta.Status, integratedTime, keyMeta.Status, *keyMeta.To)
|
||||||
|
}
|
||||||
|
// verify TL entry payload
|
||||||
|
encodedPub, err := x509.MarshalPKIXPublicKey(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error failed to marshal public key: %w", err)
|
||||||
|
}
|
||||||
|
err = t.VerifyEntryPayload(entryBytes, payload, encodedPub)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("TL entry failed payload verification: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode signature
|
// decode signature
|
||||||
signature, err := base64.StdEncoding.Strict().DecodeString(sig.Sig)
|
signature, err := base64.StdEncoding.Strict().DecodeString(sig.Sig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -39,8 +39,11 @@ func TestVerifyUnsignedAttestation(t *testing.T) {
|
|||||||
Payload: base64.StdEncoding.EncodeToString(payload),
|
Payload: base64.StdEncoding.EncodeToString(payload),
|
||||||
PayloadType: intoto.PayloadType,
|
PayloadType: intoto.PayloadType,
|
||||||
}
|
}
|
||||||
|
opts := &attestation.VerifyOptions{
|
||||||
|
Keys: attestation.Keys{},
|
||||||
|
}
|
||||||
|
|
||||||
_, err := attestation.VerifyDSSE(ctx, env, attestation.KeysMap{})
|
_, err := attestation.VerifyDSSE(ctx, env, opts)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "no signatures")
|
assert.Contains(t, err.Error(), "no signatures")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,21 +133,42 @@ func jsonGenerator[T any]() func(t *ast.Term, ec *rego.EvalContext) (any, error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var dynamicObj = types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))
|
var dynamicObj = types.NewObject(nil, types.NewDynamicProperty(types.A, types.A))
|
||||||
var arrayObj = types.NewArray(nil, dynamicObj)
|
|
||||||
var setObj = types.NewSet(dynamicObj)
|
|
||||||
|
|
||||||
var verifyDecl = &ast.Builtin{
|
var verifyDecl = &ast.Builtin{
|
||||||
Name: "attestations.verify_envelope",
|
Name: "attest.verify",
|
||||||
Decl: types.NewFunction(types.Args(dynamicObj, arrayObj), dynamicObj),
|
Decl: types.NewFunction(types.Args(dynamicObj, dynamicObj), dynamicObj),
|
||||||
Nondeterministic: true,
|
Nondeterministic: true,
|
||||||
}
|
}
|
||||||
var attestDecl = &ast.Builtin{
|
var attestDecl = &ast.Builtin{
|
||||||
Name: "attestations.attestation",
|
Name: "attest.fetch",
|
||||||
Decl: types.NewFunction(types.Args(types.S), setObj),
|
Decl: types.NewFunction(types.Args(types.S), dynamicObj),
|
||||||
Nondeterministic: true,
|
Nondeterministic: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func wrapFunctionResult(value *ast.Term, err error) (*ast.Term, error) {
|
||||||
|
var terms [][2]*ast.Term
|
||||||
|
if err != nil {
|
||||||
|
terms = append(terms, [2]*ast.Term{ast.StringTerm("error"), ast.StringTerm(err.Error())})
|
||||||
|
}
|
||||||
|
if value != nil {
|
||||||
|
terms = append(terms, [2]*ast.Term{ast.StringTerm("value"), value})
|
||||||
|
}
|
||||||
|
return ast.ObjectTerm(terms...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleErrors1(f func(rCtx rego.BuiltinContext, a *ast.Term) (*ast.Term, error)) rego.Builtin1 {
|
||||||
|
return func(rCtx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) {
|
||||||
|
return wrapFunctionResult(f(rCtx, a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleErrors2(f func(rCtx rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error)) rego.Builtin2 {
|
||||||
|
return func(rCtx rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error) {
|
||||||
|
return wrapFunctionResult(f(rCtx, a, b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func RegoFunctions(resolver oci.AttestationResolver) []*tester.Builtin {
|
func RegoFunctions(resolver oci.AttestationResolver) []*tester.Builtin {
|
||||||
return []*tester.Builtin{
|
return []*tester.Builtin{
|
||||||
{
|
{
|
||||||
@@ -159,7 +180,7 @@ func RegoFunctions(resolver oci.AttestationResolver) []*tester.Builtin {
|
|||||||
Memoize: true,
|
Memoize: true,
|
||||||
Nondeterministic: verifyDecl.Nondeterministic,
|
Nondeterministic: verifyDecl.Nondeterministic,
|
||||||
},
|
},
|
||||||
verifyIntotoEnvelope),
|
handleErrors2(verifyIntotoEnvelope)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Decl: attestDecl,
|
Decl: attestDecl,
|
||||||
@@ -170,12 +191,12 @@ func RegoFunctions(resolver oci.AttestationResolver) []*tester.Builtin {
|
|||||||
Memoize: true,
|
Memoize: true,
|
||||||
Nondeterministic: attestDecl.Nondeterministic,
|
Nondeterministic: attestDecl.Nondeterministic,
|
||||||
},
|
},
|
||||||
fetchIntotoAttestations(resolver)),
|
handleErrors1(fetchIntotoAttestations(resolver))),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchIntotoAttestations(resolver oci.AttestationResolver) func(rego.BuiltinContext, *ast.Term) (*ast.Term, error) {
|
func fetchIntotoAttestations(resolver oci.AttestationResolver) rego.Builtin1 {
|
||||||
return func(rCtx rego.BuiltinContext, predicateTypeTerm *ast.Term) (*ast.Term, error) {
|
return func(rCtx rego.BuiltinContext, predicateTypeTerm *ast.Term) (*ast.Term, error) {
|
||||||
predicateTypeStr, ok := predicateTypeTerm.Value.(ast.String)
|
predicateTypeStr, ok := predicateTypeTerm.Value.(ast.String)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -205,22 +226,19 @@ func fetchIntotoAttestations(resolver oci.AttestationResolver) func(rego.Builtin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyIntotoEnvelope(rCtx rego.BuiltinContext, envTerm, keysTerm *ast.Term) (*ast.Term, error) {
|
func verifyIntotoEnvelope(rCtx rego.BuiltinContext, envTerm, optsTerm *ast.Term) (*ast.Term, error) {
|
||||||
env := new(att.Envelope)
|
env := new(att.Envelope)
|
||||||
var keys att.Keys
|
opts := new(att.VerifyOptions)
|
||||||
err := ast.As(envTerm.Value, env)
|
err := ast.As(envTerm.Value, env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to cast envelope: %w", err)
|
return nil, fmt.Errorf("failed to cast envelope: %w", err)
|
||||||
}
|
}
|
||||||
err = ast.As(keysTerm.Value, &keys)
|
err = ast.As(optsTerm.Value, &opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to cast keys: %w", err)
|
return nil, fmt.Errorf("failed to cast verifier options: %w", err)
|
||||||
}
|
}
|
||||||
keysmap := make(map[string]att.KeyMetadata, len(keys))
|
|
||||||
for _, key := range keys {
|
payload, err := att.VerifyDSSE(rCtx.Context, env, opts)
|
||||||
keysmap[key.ID] = key
|
|
||||||
}
|
|
||||||
payload, err := att.VerifyDSSE(rCtx.Context, env, keysmap)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -242,7 +260,6 @@ func verifyIntotoEnvelope(rCtx rego.BuiltinContext, envTerm, keysTerm *ast.Term)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ast.NewTerm(value), nil
|
return ast.NewTerm(value), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,17 @@ package attest
|
|||||||
import rego.v1
|
import rego.v1
|
||||||
|
|
||||||
keys := [{
|
keys := [{
|
||||||
"id": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4",
|
"id": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4",
|
||||||
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgH23D1i2+ZIOtVjmfB7iFvX8AhVN\n9CPJ4ie9axw+WRHozGnRy99U2dRge3zueBBg2MweF0zrToXGig2v3YOrdw==\n-----END PUBLIC KEY-----",
|
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgH23D1i2+ZIOtVjmfB7iFvX8AhVN\n9CPJ4ie9axw+WRHozGnRy99U2dRge3zueBBg2MweF0zrToXGig2v3YOrdw==\n-----END PUBLIC KEY-----",
|
||||||
"from": "2023-12-15T14:00:00Z",
|
"from": "2023-12-15T14:00:00Z",
|
||||||
"to": null
|
"to": null,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
opts := {"keys": keys}
|
||||||
|
|
||||||
success if {
|
success if {
|
||||||
some env in attestations.attestation("foo")
|
some env in attest.fetch("foo")
|
||||||
statement := attestations.verify_envelope(env, keys)
|
statement := attest.verify(env, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := {
|
result := {"success": success}
|
||||||
"success": success
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,19 +3,28 @@ package attest
|
|||||||
import rego.v1
|
import rego.v1
|
||||||
|
|
||||||
keys := [{
|
keys := [{
|
||||||
"id": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4",
|
"id": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4",
|
||||||
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHyZpSgzvqFqNv7f3x7865OS38rAb\nQMcff55zM2UH/KR3Pr84a8QsGDNgaNGzJQJWjtMSgfV8WnNoffNK+svFNg==\n-----END PUBLIC KEY-----",
|
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHyZpSgzvqFqNv7f3x7865OS38rAb\nQMcff55zM2UH/KR3Pr84a8QsGDNgaNGzJQJWjtMSgfV8WnNoffNK+svFNg==\n-----END PUBLIC KEY-----",
|
||||||
"from": "2023-12-15T14:00:00Z",
|
"from": "2023-12-15T14:00:00Z",
|
||||||
"to": null,
|
"to": null,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
default success := false
|
default success := false
|
||||||
|
|
||||||
success if {
|
provs(pred) := p if {
|
||||||
some env in attestations.attestation("foo")
|
res := attest.fetch(pred)
|
||||||
statement := attestations.verify_envelope(env, keys)
|
not res.error
|
||||||
|
p := res.value
|
||||||
}
|
}
|
||||||
|
|
||||||
result := {
|
atts := union({provs("foo")})
|
||||||
"success": success
|
|
||||||
|
opts := {"keys": keys}
|
||||||
|
|
||||||
|
success if {
|
||||||
|
some env in atts
|
||||||
|
res := attest.verify(env, opts)
|
||||||
|
not res.error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result := {"success": success}
|
||||||
|
|||||||
16
test/testdata/local-policy-fail/doi/policy.rego
vendored
16
test/testdata/local-policy-fail/doi/policy.rego
vendored
@@ -12,14 +12,24 @@ keys := [{
|
|||||||
"signing-format": "dssev1",
|
"signing-format": "dssev1",
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
provs(pred) := p if {
|
||||||
|
res := attest.fetch(pred)
|
||||||
|
not res.error
|
||||||
|
p := res.value
|
||||||
|
}
|
||||||
|
|
||||||
atts := union({
|
atts := union({
|
||||||
attestations.attestation("https://slsa.dev/provenance/v0.2"),
|
provs("https://slsa.dev/provenance/v0.2"),
|
||||||
attestations.attestation("https://spdx.dev/Document"),
|
provs("https://spdx.dev/Document"),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
opts := {"keys": keys}
|
||||||
|
|
||||||
statements contains s if {
|
statements contains s if {
|
||||||
some att in atts
|
some att in atts
|
||||||
s := attestations.verify_envelope(att, keys)
|
res := attest.verify(att, opts)
|
||||||
|
not res.error
|
||||||
|
s := res.value
|
||||||
}
|
}
|
||||||
|
|
||||||
subjects contains subject if {
|
subjects contains subject if {
|
||||||
|
|||||||
49
test/testdata/local-policy-no-tl/doi/policy.rego
vendored
Normal file
49
test/testdata/local-policy-no-tl/doi/policy.rego
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package attest
|
||||||
|
|
||||||
|
import rego.v1
|
||||||
|
|
||||||
|
keys := [{
|
||||||
|
"id": "6b241993defaba26558c64f94a94303ce860e7ad9163d801495c91cf57197c75",
|
||||||
|
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZmicqYSY38DprGr42jU0V3ND0ROj\nzSRH1+yjsxhh0bi52Hh/DuOhrSq2KJ5a09lW3ybnDjljowbkof0Y1i9Oow==\n-----END PUBLIC KEY-----",
|
||||||
|
"from": "2023-12-15T14:00:00Z",
|
||||||
|
"to": null,
|
||||||
|
# this key is still active
|
||||||
|
"status": "active",
|
||||||
|
"signing-format": "dssev1",
|
||||||
|
}]
|
||||||
|
|
||||||
|
provs(pred) := p if {
|
||||||
|
res := attest.fetch(pred)
|
||||||
|
not res.error
|
||||||
|
p := res.value
|
||||||
|
}
|
||||||
|
|
||||||
|
atts := union({
|
||||||
|
provs("https://slsa.dev/provenance/v0.2"),
|
||||||
|
provs("https://spdx.dev/Document"),
|
||||||
|
})
|
||||||
|
|
||||||
|
opts := {"keys": keys, "skip_tl": true}
|
||||||
|
|
||||||
|
statements contains s if {
|
||||||
|
some att in atts
|
||||||
|
res := attest.verify(att, opts)
|
||||||
|
not res.error
|
||||||
|
s := res.value
|
||||||
|
}
|
||||||
|
|
||||||
|
subjects contains subject if {
|
||||||
|
some statement in statements
|
||||||
|
some subject in statement.subject
|
||||||
|
}
|
||||||
|
|
||||||
|
result := {
|
||||||
|
"success": true,
|
||||||
|
"violations": set(),
|
||||||
|
"summary": {
|
||||||
|
"subjects": subjects,
|
||||||
|
"slsa_levels": ["SLSA_BUILD_LEVEL_3"],
|
||||||
|
"verifier": "docker-official-images",
|
||||||
|
"policy_uri": "https://docker.com/official/policy/v0.1",
|
||||||
|
},
|
||||||
|
}
|
||||||
11
test/testdata/local-policy-no-tl/mapping.yaml
vendored
Normal file
11
test/testdata/local-policy-no-tl/mapping.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# map repos to policies
|
||||||
|
version: v1
|
||||||
|
kind: policy-mapping
|
||||||
|
policies:
|
||||||
|
- origin:
|
||||||
|
domain: docker.io
|
||||||
|
prefix: library/
|
||||||
|
id: test-images
|
||||||
|
description: Local test images
|
||||||
|
files:
|
||||||
|
- path: doi/policy.rego
|
||||||
16
test/testdata/local-policy-pass/doi/policy.rego
vendored
16
test/testdata/local-policy-pass/doi/policy.rego
vendored
@@ -12,14 +12,24 @@ keys := [{
|
|||||||
"signing-format": "dssev1",
|
"signing-format": "dssev1",
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
provs(pred) := p if {
|
||||||
|
res := attest.fetch(pred)
|
||||||
|
not res.error
|
||||||
|
p := res.value
|
||||||
|
}
|
||||||
|
|
||||||
atts := union({
|
atts := union({
|
||||||
attestations.attestation("https://slsa.dev/provenance/v0.2"),
|
provs("https://slsa.dev/provenance/v0.2"),
|
||||||
attestations.attestation("https://spdx.dev/Document"),
|
provs("https://spdx.dev/Document"),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
opts := {"keys": keys}
|
||||||
|
|
||||||
statements contains s if {
|
statements contains s if {
|
||||||
some att in atts
|
some att in atts
|
||||||
s := attestations.verify_envelope(att, keys)
|
res := attest.verify(att, opts)
|
||||||
|
not res.error
|
||||||
|
s := res.value
|
||||||
}
|
}
|
||||||
|
|
||||||
subjects contains subject if {
|
subjects contains subject if {
|
||||||
|
|||||||
Reference in New Issue
Block a user