Skip to content

Commit

Permalink
wip: more hacking -- adjust how we handle additional subjects and use…
Browse files Browse the repository at this point in the history
… digestsets in more palces
  • Loading branch information
mikhailswift committed Nov 4, 2023
1 parent 9373b97 commit 120ec25
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 94 deletions.
148 changes: 76 additions & 72 deletions attestation/policyverify/policyverify.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,19 @@ var (

type Attestor struct {
slsa.VerificationSummary
WitnessVerifyInfo WitnessVerifyInfo `json:"witnessverifyinfo,omitempty"`

policyEnvelope dsse.Envelope
policyVerifiers []cryptoutil.Verifier
collectionSource source.Sourcer
subjectDigests []string
addtlSubjects map[string]cryptoutil.DigestSet
}

type WitnessVerifyInfo struct {
// InitialSubjectDigests is the set of subject digests passed to witness Verify to start
// the verification process
InitialSubjectDigests []cryptoutil.DigestSet `json:"initialsubjectdigests,omitempty"`
// AdditionalSubjects is a set of subjects that were used during the verification process.
AdditionalSubjects map[string]cryptoutil.DigestSet `json:"additionalsubjects,omitempty"`
}

type Option func(*Attestor)
Expand All @@ -69,11 +76,7 @@ func VerifyWithPolicyVerifiers(policyVerifiers []cryptoutil.Verifier) Option {

func VerifyWithSubjectDigests(subjectDigests []cryptoutil.DigestSet) Option {
return func(a *Attestor) {
for _, set := range subjectDigests {
for _, digest := range set {
a.subjectDigests = append(a.subjectDigests, digest)
}
}
a.WitnessVerifyInfo.InitialSubjectDigests = append(a.WitnessVerifyInfo.InitialSubjectDigests, subjectDigests...)
}
}

Expand All @@ -84,7 +87,13 @@ func VerifyWithCollectionSource(source source.Sourcer) Option {
}

func New(opts ...Option) *Attestor {
a := &Attestor{addtlSubjects: make(map[string]cryptoutil.DigestSet)}
a := &Attestor{
WitnessVerifyInfo: WitnessVerifyInfo{
AdditionalSubjects: make(map[string]cryptoutil.DigestSet),
InitialSubjectDigests: make([]cryptoutil.DigestSet, 0),
},
}

for _, opt := range opts {
opt(a)
}
Expand All @@ -106,14 +115,12 @@ func (a *Attestor) RunType() attestation.RunType {

func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet {
subjects := map[string]cryptoutil.DigestSet{}
for _, digest := range a.subjectDigests {
subjects[fmt.Sprintf("artifact:%v", digest)] = cryptoutil.DigestSet{
cryptoutil.DigestValue{Hash: crypto.SHA256, GitOID: false}: digest,
}
for n, digestSet := range a.WitnessVerifyInfo.InitialSubjectDigests {
subjects[fmt.Sprintf("artifact:%v", n)] = digestSet
}

subjects[fmt.Sprintf("policy:%v", a.VerificationSummary.Policy.URI)] = a.VerificationSummary.Policy.Digest
for name, ds := range a.addtlSubjects {
for name, ds := range a.WitnessVerifyInfo.AdditionalSubjects {
subjects[name] = ds
}

Expand Down Expand Up @@ -173,7 +180,7 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error {
)

accepted := true
policyResult, policyErr := pol.Verify(ctx.Context(), policy.WithSubjectDigests(a.subjectDigests), policy.WithVerifiedSource(verifiedSource))
policyResult, policyErr := pol.Verify(ctx.Context(), policy.WithSubjectDigests(a.WitnessVerifyInfo.InitialSubjectDigests), policy.WithVerifiedSource(verifiedSource))
if _, ok := policyErr.(policy.ErrPolicyDenied); ok {
accepted = false
} else if policyErr != nil {
Expand All @@ -185,86 +192,83 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error {
return fmt.Errorf("failed to generate verification summary: %w", err)
}

a.findRelevantSubjects(policyResult.EvidenceByStep)
a.findInterestingSubjects(policyResult.EvidenceByStep)
return nil
}

// findRelevantSubjects will find any image tags in attestations that passed our policy check.
// we do this by searching the subject digests the user tested the policy against, and if we find
// a collection with OCI subjects for an imageid that matches one of the provided subject digests,
// we grab the imagetag subjects off of that attestation.
// todo: we need a better solution for this
func (a *Attestor) findRelevantSubjects(evidenceByStep map[string][]source.VerifiedCollection) {
const imageTagSubjectPrefix = "https://witness.dev/attestations/oci/v0.1/imagetag:"
// findInterestingSubjects will search subjects of attestations used during the verification process
// for interesting subjects, and package them onto the VSA as additional subjects. This is used
// primarily to link a VSA back to a specific github or gitlab project, or an artifact hash to
// a specific tagged image.
func (a *Attestor) findInterestingSubjects(evidenceByStep map[string][]source.VerifiedCollection) {
// imageId is especially interesting, and we only treat the other interesting subject candidates
// as valid if we get a match on the imageId
const imageIdSubjectPrefix = "https://witness.dev/attestations/oci/v0.1/imageid:"
const githubProjectPrefix = "https://witness.dev/attestations/github/v0.1/projecturl:"
const gitlabProjectPrefix = "https://witness.dev/attestations/gitlab/v0.1/projecturl:"

// a map of subjects we consider interesting. the value of this map is just a value we'll use
// to repackage the subject as a subject of the VSA itself.
interestingSubjects := map[string]string{
"https://witness.dev/attestations/oci/v0.1/imagetag:": "imagetag",
"https://witness.dev/attestations/github/v0.1/projecturl:": "projecturl",
"https://witness.dev/attestations/gitlab/v0.1/projecturl:": "projecturl",
"https://witness.dev/attestations/git/v0.1/commithash:": "commithash",
}

for _, collections := range evidenceByStep {
for _, collection := range collections {
candidates := make([]intoto.Subject, 0)
matchedSubject := false

// search through every subject on the in-toto statment. if we find imagetags, we set them aside as possible candidates.
// if we find an imageid subject that matches, we consider all the candidates to be matching subjects and return them
// search through every subject on the in-toto statment. if we find any interesting subjects, we set them aside as possible candidates.
// if we find an imageid subject that matches, we consider all the candidates to be matching subjects and add them to our list
for _, subject := range collection.Statement.Subject {
// if we find an image tag subject, add it to the list of candidates
if strings.HasPrefix(subject.Name, imageTagSubjectPrefix) {
candidates = append(candidates, intoto.Subject{
Name: fmt.Sprintf("imagetag:%v", strings.TrimPrefix(subject.Name, imageTagSubjectPrefix)),
Digest: subject.Digest,
})
}

if strings.HasPrefix(subject.Name, githubProjectPrefix) {
candidates = append(candidates, intoto.Subject{
Name: fmt.Sprintf("projecturl:%v", strings.TrimPrefix(subject.Name, githubProjectPrefix)),
Digest: subject.Digest,
})
}

if strings.HasPrefix(subject.Name, gitlabProjectPrefix) {
candidates = append(candidates, intoto.Subject{
Name: fmt.Sprintf("projecturl:%v", strings.TrimPrefix(subject.Name, gitlabProjectPrefix)),
Digest: subject.Digest,
})
}
for interstingSubject, transformedSubject := range interestingSubjects {
if strings.HasPrefix(subject.Name, interstingSubject) {
candidates = append(candidates, intoto.Subject{
Name: fmt.Sprintf("%v:%v", transformedSubject, strings.TrimPrefix(subject.Name, interstingSubject)),
Digest: subject.Digest,
})
}

// if we find an imageid subject, check to see if any the digests we verified match the imageid
if strings.HasPrefix(subject.Name, imageIdSubjectPrefix) {
for _, imageIdDigest := range subject.Digest {
for _, testImageIdDigest := range a.subjectDigests {
if imageIdDigest == testImageIdDigest {
matchedSubject = true
candidates = append(candidates, intoto.Subject{
Name: fmt.Sprintf("imageid:%v", testImageIdDigest),
Digest: subject.Digest,
})
// if we find an imageid subject, check to see if any the digests we verified match the imageid
if strings.HasPrefix(subject.Name, imageIdSubjectPrefix) {
for _, imageIdDigest := range subject.Digest {
for _, testDigestSet := range a.WitnessVerifyInfo.InitialSubjectDigests {
for _, testImageIdDigest := range testDigestSet {
if imageIdDigest == testImageIdDigest {
matchedSubject = true
candidates = append(candidates, intoto.Subject{
Name: fmt.Sprintf("imageid:%v", testImageIdDigest),
Digest: subject.Digest,
})
}
}
}
}

// if we found a matching imageid subject with one of our test subject digests, stop looking
if matchedSubject {
break
// if we found a matching imageid subject with one of our test subject digests, stop looking
if matchedSubject {
break
}
}
}
}
}

// after we've checked all the subjects, if we found a match, add our candidates to our additional subjects
if matchedSubject {
for _, candidate := range candidates {
ds := cryptoutil.DigestSet{}
for hash, value := range candidate.Digest {
digestValue, err := cryptoutil.DigestValueFromString(hash)
if err != nil {
continue
// after we've checked all the subjects, if we found a match, add our candidates to our additional subjects
if matchedSubject {
for _, candidate := range candidates {
ds := cryptoutil.DigestSet{}
for hash, value := range candidate.Digest {
digestValue, err := cryptoutil.DigestValueFromString(hash)
if err != nil {
continue
}

ds[digestValue] = value
}

ds[digestValue] = value
a.WitnessVerifyInfo.AdditionalSubjects[candidate.Name] = ds
}

a.addtlSubjects[candidate.Name] = ds
}
}
}
Expand Down
8 changes: 3 additions & 5 deletions policy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ type VerifyOption func(*verifyOptions)

type verifyOptions struct {
verifiedSource source.VerifiedSourcer
subjectDigests []string
subjectDigests []cryptoutil.DigestSet
searchDepth int
}

Expand All @@ -131,7 +131,7 @@ func WithVerifiedSource(verifiedSource source.VerifiedSourcer) VerifyOption {
}
}

func WithSubjectDigests(subjectDigests []string) VerifyOption {
func WithSubjectDigests(subjectDigests []cryptoutil.DigestSet) VerifyOption {
return func(vo *verifyOptions) {
vo.subjectDigests = subjectDigests
}
Expand Down Expand Up @@ -214,9 +214,7 @@ func (p Policy) Verify(ctx context.Context, opts ...VerifyOption) (PolicyResult,
passedByStep[stepName] = append(passedByStep[stepName], stepResults.Passed...)
for _, coll := range stepResults.Passed {
for _, digestSet := range coll.Collection.BackRefs() {
for _, digest := range digestSet {
vo.subjectDigests = append(vo.subjectDigests, digest)
}
vo.subjectDigests = append(vo.subjectDigests, digestSet)
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions policy/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ deny[msg] {

_, err = policy.Verify(
context.Background(),
WithSubjectDigests([]string{"dummy"}),
WithSubjectDigests([]cryptoutil.DigestSet{{cryptoutil.DigestValue{Hash: crypto.SHA256}: "dummy"}}),
WithVerifiedSource(
newDummyVerifiedSourcer([]source.VerifiedCollection{
{
Expand All @@ -164,7 +164,7 @@ deny[msg] {

_, err = policy.Verify(
context.Background(),
WithSubjectDigests([]string{"dummy"}),
WithSubjectDigests([]cryptoutil.DigestSet{{cryptoutil.DigestValue{Hash: crypto.SHA256}: "dummy"}}),
WithVerifiedSource(
newDummyVerifiedSourcer([]source.VerifiedCollection{
{
Expand Down Expand Up @@ -262,7 +262,7 @@ func TestArtifacts(t *testing.T) {
require.NoError(t, err)
_, err = policy.Verify(
context.Background(),
WithSubjectDigests([]string{dummySha}),
WithSubjectDigests([]cryptoutil.DigestSet{{cryptoutil.DigestValue{Hash: crypto.SHA256}: dummySha}}),
WithVerifiedSource(newDummyVerifiedSourcer([]source.VerifiedCollection{
{
Verifiers: []cryptoutil.Verifier{verifier},
Expand Down Expand Up @@ -301,7 +301,7 @@ func TestArtifacts(t *testing.T) {
require.NoError(t, err)
_, err = policy.Verify(
context.Background(),
WithSubjectDigests([]string{dummySha}),
WithSubjectDigests([]cryptoutil.DigestSet{{cryptoutil.DigestValue{Hash: crypto.SHA256}: dummySha}}),
WithVerifiedSource(newDummyVerifiedSourcer([]source.VerifiedCollection{
{
Verifiers: []cryptoutil.Verifier{verifier},
Expand Down Expand Up @@ -381,6 +381,6 @@ func newDummyVerifiedSourcer(verifiedCollections []source.VerifiedCollection) *d
return &dummyVerifiedSourcer{verifiedCollections}
}

func (s *dummyVerifiedSourcer) Search(ctx context.Context, collectionName string, subjectDigests, attestations []string) ([]source.VerifiedCollection, error) {
func (s *dummyVerifiedSourcer) Search(ctx context.Context, collectionName string, subjectDigests []cryptoutil.DigestSet, attestations []string) ([]source.VerifiedCollection, error) {
return s.verifiedCollections, nil
}
11 changes: 9 additions & 2 deletions source/archivista.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"

"github.com/testifysec/go-witness/archivista"
"github.com/testifysec/go-witness/cryptoutil"
)

type ArchivistaSource struct {
Expand All @@ -32,10 +33,16 @@ func NewArchvistSource(client *archivista.Client) *ArchivistaSource {
}
}

func (s *ArchivistaSource) Search(ctx context.Context, collectionName string, subjectDigests, attestations []string) ([]CollectionEnvelope, error) {
func (s *ArchivistaSource) Search(ctx context.Context, collectionName string, subjectDigests []cryptoutil.DigestSet, attestations []string) ([]CollectionEnvelope, error) {
digestArray := make([]string, 0)
for _, ds := range subjectDigests {
for _, digest := range ds {
digestArray = append(digestArray, digest)
}
}
gitoids, err := s.client.SearchGitoids(ctx, archivista.SearchGitoidVariables{
CollectionName: collectionName,
SubjectDigests: subjectDigests,
SubjectDigests: digestArray,
Attestations: attestations,
ExcludeGitoids: s.seenGitoids,
})
Expand Down
13 changes: 8 additions & 5 deletions source/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"io"
"os"

"github.com/testifysec/go-witness/cryptoutil"
"github.com/testifysec/go-witness/dsse"
)

Expand Down Expand Up @@ -103,7 +104,7 @@ func (s *MemorySource) LoadEnvelope(reference string, env dsse.Envelope) error {
return nil
}

func (s *MemorySource) Search(ctx context.Context, collectionName string, subjectDigests, attestations []string) ([]CollectionEnvelope, error) {
func (s *MemorySource) Search(ctx context.Context, collectionName string, subjectDigests []cryptoutil.DigestSet, attestations []string) ([]CollectionEnvelope, error) {
matches := make([]CollectionEnvelope, 0)
for _, potentialMatchReference := range s.referencesByCollectionName[collectionName] {
env, ok := s.envelopesByReference[potentialMatchReference]
Expand All @@ -114,10 +115,12 @@ func (s *MemorySource) Search(ctx context.Context, collectionName string, subjec
// make sure at least one of the subjects digests exists on the potential matches
subjectMatchFound := false
indexSubjects := s.subjectDigestsByReference[potentialMatchReference]
for _, checkDigest := range subjectDigests {
if _, ok := indexSubjects[checkDigest]; ok {
subjectMatchFound = true
break
for _, checkDigestSet := range subjectDigests {
for _, checkDigest := range checkDigestSet {
if _, ok := indexSubjects[checkDigest]; ok {
subjectMatchFound = true
break
}
}
}

Expand Down
8 changes: 6 additions & 2 deletions source/multi.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@

package source

import "context"
import (
"context"

"github.com/testifysec/go-witness/cryptoutil"
)

type MultiSource struct {
sources []Sourcer
Expand All @@ -24,7 +28,7 @@ func NewMultiSource(sources ...Sourcer) *MultiSource {
return &MultiSource{sources}
}

func (s *MultiSource) Search(ctx context.Context, collectionName string, subjectDigests, attestations []string) ([]CollectionEnvelope, error) {
func (s *MultiSource) Search(ctx context.Context, collectionName string, subjectDigests []cryptoutil.DigestSet, attestations []string) ([]CollectionEnvelope, error) {
results := make([]CollectionEnvelope, 0)
for _, source := range s.sources {
res, err := source.Search(ctx, collectionName, subjectDigests, attestations)
Expand Down
3 changes: 2 additions & 1 deletion source/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"encoding/json"

"github.com/testifysec/go-witness/attestation"
"github.com/testifysec/go-witness/cryptoutil"
"github.com/testifysec/go-witness/dsse"
"github.com/testifysec/go-witness/intoto"
)
Expand All @@ -31,7 +32,7 @@ type CollectionEnvelope struct {
}

type Sourcer interface {
Search(ctx context.Context, collectionName string, subjectDigests, attestations []string) ([]CollectionEnvelope, error)
Search(ctx context.Context, collectionName string, subjectDigests []cryptoutil.DigestSet, attestations []string) ([]CollectionEnvelope, error)
}

func envelopeToCollectionEnvelope(reference string, env dsse.Envelope) (CollectionEnvelope, error) {
Expand Down
Loading

0 comments on commit 120ec25

Please sign in to comment.