Skip to content

Commit

Permalink
feat(referrer): kind parameter support (#434)
Browse files Browse the repository at this point in the history
Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>
  • Loading branch information
migmartri authored Nov 14, 2023
1 parent bf91cd0 commit 05f3dff
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 87 deletions.
5 changes: 3 additions & 2 deletions app/cli/cmd/referrer_discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ import (
)

func newReferrerDiscoverCmd() *cobra.Command {
var digest string
var digest, kind string

cmd := &cobra.Command{
Use: "discover",
Short: "(Preview) inspect pieces of evidence or artifacts stored through Chainloop",
RunE: func(cmd *cobra.Command, args []string) error {
res, err := action.NewReferrerDiscover(actionOpts).Run(context.Background(), digest)
res, err := action.NewReferrerDiscover(actionOpts).Run(context.Background(), digest, kind)
if err != nil {
return err
}
Expand All @@ -42,6 +42,7 @@ func newReferrerDiscoverCmd() *cobra.Command {
cmd.Flags().StringVarP(&digest, "digest", "d", "", "hash of the attestation, piece of evidence or artifact, i.e sha256:deadbeef")
err := cmd.MarkFlagRequired("digest")
cobra.CheckErr(err)
cmd.Flags().StringVarP(&kind, "kind", "k", "", "optional kind of the referrer, used to disambiguate between multiple referrers with the same digest")

return cmd
}
6 changes: 4 additions & 2 deletions app/cli/internal/action/referrer_discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ func NewReferrerDiscover(cfg *ActionsOpts) *ReferrerDiscover {
return &ReferrerDiscover{cfg}
}

func (action *ReferrerDiscover) Run(ctx context.Context, digest string) (*ReferrerItem, error) {
func (action *ReferrerDiscover) Run(ctx context.Context, digest, kind string) (*ReferrerItem, error) {
client := pb.NewReferrerServiceClient(action.cfg.CPConnection)
resp, err := client.Discover(ctx, &pb.ReferrerServiceDiscoverRequest{Digest: digest})
resp, err := client.Discover(ctx, &pb.ReferrerServiceDiscoverRequest{
Digest: digest, Kind: kind,
})
if err != nil {
return nil, err
}
Expand Down
83 changes: 47 additions & 36 deletions app/controlplane/api/controlplane/v1/referrer.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions app/controlplane/api/controlplane/v1/referrer.pb.validate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions app/controlplane/api/controlplane/v1/referrer.proto
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ service ReferrerService {

message ReferrerServiceDiscoverRequest {
string digest = 1 [(validate.rules).string = {min_len: 1}];
// Optional kind of referrer, i.e CONTAINER_IMAGE, GIT_HEAD, ...
// Used to filter and resolve ambiguities
string kind = 2;
}

message ReferrerServiceDiscoverResponse {
Expand Down
24 changes: 22 additions & 2 deletions app/controlplane/api/gen/frontend/controlplane/v1/referrer.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions app/controlplane/internal/biz/referrer.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ type ReferrerRepo interface {
// GetFromRoot returns the referrer identified by the provided content digest, including its first-level references
// For example if sha:deadbeef represents an attestation, the result will contain the attestation + materials associated to it
// OrgIDs represent an allowList of organizations where the referrers should be looked for
GetFromRoot(ctx context.Context, digest string, orgIDS []uuid.UUID) (*StoredReferrer, error)
GetFromRoot(ctx context.Context, digest, kind string, orgIDS []uuid.UUID) (*StoredReferrer, error)
}

type ReferrerUseCase struct {
Expand Down Expand Up @@ -104,7 +104,7 @@ func (s *ReferrerUseCase) ExtractAndPersist(ctx context.Context, att *dsse.Envel
// GetFromRoot returns the referrer identified by the provided content digest, including its first-level references
// For example if sha:deadbeef represents an attestation, the result will contain the attestation + materials associated to it
// It only returns referrers that belong to organizations the user is member of
func (s *ReferrerUseCase) GetFromRoot(ctx context.Context, digest string, userID string) (*StoredReferrer, error) {
func (s *ReferrerUseCase) GetFromRoot(ctx context.Context, digest string, rootKind, userID string) (*StoredReferrer, error) {
userUUID, err := uuid.Parse(userID)
if err != nil {
return nil, NewErrInvalidUUID(err)
Expand All @@ -123,7 +123,7 @@ func (s *ReferrerUseCase) GetFromRoot(ctx context.Context, digest string, userID
orgIDs = append(orgIDs, m.OrganizationID)
}

ref, err := s.repo.GetFromRoot(ctx, digest, orgIDs)
ref, err := s.repo.GetFromRoot(ctx, digest, rootKind, orgIDs)
if err != nil {
if errors.As(err, &ErrAmbiguousReferrer{}) {
return nil, NewErrValidation(fmt.Errorf("please provide the referrer kind: %w", err))
Expand Down
39 changes: 27 additions & 12 deletions app/controlplane/internal/biz/referrer_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,21 +86,21 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() {
s.T().Run("it can store properly the first time", func(t *testing.T) {
err := s.Referrer.ExtractAndPersist(ctx, envelope, s.org1.ID)
s.NoError(err)
prevStoredRef, err = s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, s.user.ID)
prevStoredRef, err = s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, "", s.user.ID)
s.NoError(err)
})

s.T().Run("and it's idempotent", func(t *testing.T) {
err := s.Referrer.ExtractAndPersist(ctx, envelope, s.org1.ID)
s.NoError(err)
ref, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, s.user.ID)
ref, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, "", s.user.ID)
s.NoError(err)
// Check it's the same referrer than previously retrieved, including timestamps
s.Equal(prevStoredRef, ref)
})

s.T().Run("contains all the info", func(t *testing.T) {
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, s.user.ID)
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, "", s.user.ID)
s.NoError(err)
// parent i.e attestation
s.Equal(wantReferrerAtt.Digest, got.Digest)
Expand All @@ -119,15 +119,15 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() {

s.T().Run("can't be accessed by a second user in another org", func(t *testing.T) {
// the user2 has not access to org1
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, s.user2.ID)
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, "", s.user2.ID)
s.True(biz.IsNotFound(err))
s.Nil(got)
})

s.T().Run("but another org can be attached", func(t *testing.T) {
err = s.Referrer.ExtractAndPersist(ctx, envelope, s.org2.ID)
s.NoError(err)
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, s.user.ID)
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, "", s.user.ID)
s.NoError(err)
require.Len(t, got.OrgIDs, 2)
s.Contains(got.OrgIDs, s.org1UUID)
Expand All @@ -136,21 +136,21 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() {
// and it's idempotent (no new orgs added)
err = s.Referrer.ExtractAndPersist(ctx, envelope, s.org2.ID)
s.NoError(err)
got, err = s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, s.user.ID)
got, err = s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, "", s.user.ID)
s.NoError(err)
require.Len(t, got.OrgIDs, 2)
})

s.T().Run("and now user2 has access to it since it has access to org2", func(t *testing.T) {
err = s.Referrer.ExtractAndPersist(ctx, envelope, s.org2.ID)
s.NoError(err)
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, s.user2.ID)
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerAtt.Digest, "", s.user2.ID)
s.NoError(err)
require.Len(t, got.OrgIDs, 2)
})

s.T().Run("you can ask for info about materials that are subjects", func(t *testing.T) {
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerContainerImage.Digest, s.user.ID)
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerContainerImage.Digest, "", s.user.ID)
s.NoError(err)
// parent i.e attestation
s.Equal(wantReferrerContainerImage.Digest, got.Digest)
Expand All @@ -164,7 +164,7 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() {
})

s.T().Run("it might not have references", func(t *testing.T) {
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerSarif.Digest, s.user.ID)
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerSarif.Digest, "", s.user.ID)
s.NoError(err)
// parent i.e attestation
s.Equal(wantReferrerSarif.Digest, got.Digest)
Expand All @@ -174,7 +174,7 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() {
})

s.T().Run("or not to exist", func(t *testing.T) {
got, err := s.Referrer.GetFromRoot(ctx, "sha256:deadbeef", s.user.ID)
got, err := s.Referrer.GetFromRoot(ctx, "sha256:deadbeef", "", s.user.ID)
s.True(biz.IsNotFound(err))
s.Nil(got)
})
Expand All @@ -199,14 +199,29 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() {
s.NoError(err)

// but retrieval should fail. In the future we will ask the user to provide the artifact type in these cases of ambiguity
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerSarif.Digest, s.user.ID)
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerSarif.Digest, "", s.user.ID)
s.Nil(got)
s.ErrorContains(err, "present in 2 kinds")
})

s.T().Run("it should not fail on retrieval if we filter out by one kind", func(t *testing.T) {
// but retrieval should fail. In the future we will ask the user to provide the artifact type in these cases of ambiguity
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerSarif.Digest, "SARIF", s.user.ID)
s.NoError(err)
s.Equal(wantReferrerSarif.Digest, got.Digest)
s.Equal(true, got.Downloadable)
s.Equal("SARIF", got.Kind)

got, err = s.Referrer.GetFromRoot(ctx, wantReferrerSarif.Digest, "ARTIFACT", s.user.ID)
s.NoError(err)
s.Equal(wantReferrerSarif.Digest, got.Digest)
s.Equal(true, got.Downloadable)
s.Equal("ARTIFACT", got.Kind)
})

s.T().Run("now there should a container image pointing to two attestations", func(t *testing.T) {
// but retrieval should fail. In the future we will ask the user to provide the artifact type in these cases of ambiguity
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerContainerImage.Digest, s.user.ID)
got, err := s.Referrer.GetFromRoot(ctx, wantReferrerContainerImage.Digest, "", s.user.ID)
s.NoError(err)
// it should be referenced by two attestations since it's subject of both
require.Len(t, got.References, 2)
Expand Down
Loading

0 comments on commit 05f3dff

Please sign in to comment.