From 71d7cbb2b69f78492560b06ca04a349c23f23b73 Mon Sep 17 00:00:00 2001 From: Mikhail Swift Date: Thu, 5 Sep 2024 17:58:17 -0400 Subject: [PATCH] test: add backref tests Signed-off-by: Mikhail Swift --- verify_test.go | 152 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/verify_test.go b/verify_test.go index 80b1200e..991905e4 100644 --- a/verify_test.go +++ b/verify_test.go @@ -34,6 +34,7 @@ import ( "github.com/in-toto/go-witness/dsse" "github.com/in-toto/go-witness/policy" "github.com/in-toto/go-witness/source" + "github.com/invopop/jsonschema" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -191,6 +192,71 @@ func TestVerify(t *testing.T) { }) } +func TestBackRefs(t *testing.T) { + registerDummyAttestors() + testPolicy, functionarySigner := makePolicyWithPublicKeyFunctionary(t) + policyEnvelope, _, policyVerifier := signPolicyRSA(t, testPolicy) + workingDir := t.TempDir() + + step1Result, err := Run( + "step01", + RunWithSigners(functionarySigner), + RunWithAttestors([]attestation.Attestor{ + material.New(), + &dummySubjectAttestor{Data: "test"}, + commandrun.New( + commandrun.WithCommand([]string{"bash", "-c", "echo 'test01' > test.txt"}), + ), + product.New(), + }), + RunWithAttestationOpts( + attestation.WithWorkingDir(workingDir), + ), + ) + require.NoError(t, err) + + step2Result, err := Run( + "step02", + RunWithSigners(functionarySigner), + RunWithAttestors([]attestation.Attestor{ + material.New(), + &dummyBackrefAttestor{}, + commandrun.New( + commandrun.WithCommand([]string{"bash", "-c", "echo 'test02' >> test.txt"}), + ), + product.New(), + }), + RunWithAttestationOpts( + attestation.WithWorkingDir(workingDir), + ), + ) + require.NoError(t, err) + + artifactSubject, err := cryptoutil.CalculateDigestSetFromFile( + filepath.Join(workingDir, "test.txt"), + []cryptoutil.DigestValue{ + { + GitOID: false, + Hash: crypto.SHA256, + }, + }, + ) + require.NoError(t, err) + memorySource := source.NewMemorySource() + require.NoError(t, memorySource.LoadEnvelope("step01", step1Result.SignedEnvelope)) + require.NoError(t, memorySource.LoadEnvelope("step02", step2Result.SignedEnvelope)) + + results, err := Verify( + context.Background(), + policyEnvelope, + []cryptoutil.Verifier{policyVerifier}, + VerifyWithCollectionSource(memorySource), + VerifyWithSubjectDigests([]cryptoutil.DigestSet{artifactSubject}), + ) + + require.NoError(t, err, fmt.Sprintf("failed with results: %+v", results)) +} + func makePolicy(functionary policy.Functionary, publicKey policy.PublicKey, roots map[string]policy.Root) policy.Policy { step01 := policy.Step{ Name: "step01", @@ -276,3 +342,89 @@ func createTestRSAKey(t *testing.T) cryptoutil.Signer { signer := cryptoutil.NewRSASigner(privKey, crypto.SHA256) return signer } + +const ( + dummySubjectAttestorName = "subject attestor" + dummySubjectAttestorType = "test/subjectattestor" + dummyBackrefAttestorName = "backref attestor" + dummyBackrefAttestorType = "test/backrefattestor" + matchSubjectName = "matchSubject" +) + +// policy verification currently needs attestors to be registers to properly validate them +func registerDummyAttestors() { + attestation.RegisterAttestation(dummyBackrefAttestorName, dummyBackrefAttestorType, attestation.PreMaterialRunType, func() attestation.Attestor { return &dummyBackrefAttestor{} }) + attestation.RegisterAttestation(dummySubjectAttestorName, dummySubjectAttestorType, attestation.PreMaterialRunType, func() attestation.Attestor { return &dummySubjectAttestor{} }) +} + +// dummySubjectAttestor is a test attestor used to create a subject on an attestation. +// this subject will be used to discover this attestor when searching by back ref subjects +// from a subsequent step in the policy. +type dummySubjectAttestor struct { + Data string +} + +func (a *dummySubjectAttestor) Name() string { + return dummySubjectAttestorName +} + +func (a *dummySubjectAttestor) Type() string { + return dummySubjectAttestorType +} + +func (a *dummySubjectAttestor) RunType() attestation.RunType { + return attestation.PreMaterialRunType +} + +func (a *dummySubjectAttestor) Attest(ctx *attestation.AttestationContext) error { + return nil +} + +func (a *dummySubjectAttestor) Schema() *jsonschema.Schema { + return nil +} + +func (a *dummySubjectAttestor) Subjects() map[string]cryptoutil.DigestSet { + return map[string]cryptoutil.DigestSet{ + matchSubjectName: { + {Hash: crypto.SHA256}: "abcde", + }, + } +} + +// dummyBackrefAttestor is a test attestor used to expose a back ref subject, used to find +// attestations from preceding steps. +// for a practical example of this, consider policy that enforces two steps: a test step and a build step that produces a binary. +// when we begin policy evaluation, we only know two things: the hash of the binary, and the steps the policy expects. +// when we look up attestations that contain a product matching the binary's hash and satisfies the build step of the policy. +// that build attestation may contain a back ref subject that is the hash of the git commit, which also appears on the test attestation. +// we can then use this back ref subject to link the test attestation to the build attestation during policy evaluation. +type dummyBackrefAttestor struct{} + +func (a *dummyBackrefAttestor) Name() string { + return dummyBackrefAttestorName +} + +func (a *dummyBackrefAttestor) Type() string { + return dummyBackrefAttestorType +} + +func (a *dummyBackrefAttestor) RunType() attestation.RunType { + return attestation.PreMaterialRunType +} + +func (a *dummyBackrefAttestor) Attest(ctx *attestation.AttestationContext) error { + return nil +} + +func (a *dummyBackrefAttestor) Schema() *jsonschema.Schema { + return nil +} + +func (a *dummyBackrefAttestor) BackRefs() map[string]cryptoutil.DigestSet { + return map[string]cryptoutil.DigestSet{ + matchSubjectName: { + {Hash: crypto.SHA256}: "abcde", + }, + } +}