Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/cli/cmd/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ func newArtifactCmd() *cobra.Command {
return cmd
}

func wrappedArtifactConn(cpConn *grpc.ClientConn, role pb.CASCredentialsServiceGetRequest_Role) (*grpc.ClientConn, error) {
func wrappedArtifactConn(cpConn *grpc.ClientConn, role pb.CASCredentialsServiceGetRequest_Role, digest string) (*grpc.ClientConn, error) {
// Retrieve temporary credentials for uploading
client := pb.NewCASCredentialsServiceClient(cpConn)
resp, err := client.Get(context.Background(), &pb.CASCredentialsServiceGetRequest{
Role: role,
Role: role,
Digest: digest,
})
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion app/cli/cmd/artifact_download.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func newArtifactDownloadCmd() *cobra.Command {

// Retrieve temporary credentials for uploading
artifactCASConn, err = wrappedArtifactConn(actionOpts.CPConnection,
pb.CASCredentialsServiceGetRequest_ROLE_DOWNLOADER)
pb.CASCredentialsServiceGetRequest_ROLE_DOWNLOADER, digest)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion app/cli/cmd/artifact_upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func newArtifactUploadCmd() *cobra.Command {
var err error

// Retrieve temporary credentials for uploading
artifactCASConn, err = wrappedArtifactConn(actionOpts.CPConnection, pb.CASCredentialsServiceGetRequest_ROLE_UPLOADER)
artifactCASConn, err = wrappedArtifactConn(actionOpts.CPConnection, pb.CASCredentialsServiceGetRequest_ROLE_UPLOADER, "")
if err != nil {
return err
}
Expand Down
68 changes: 39 additions & 29 deletions app/controlplane/api/controlplane/v1/cas_credentials.pb.go

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

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/cas_credentials.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ service CASCredentialsService {

message CASCredentialsServiceGetRequest {
Role role = 1 [(validate.rules).enum = {in: [1,2]}];
// during the download we need the digest to find the proper cas backend
string digest = 2;

enum Role {
ROLE_UNSPECIFIED = 0;
Expand Down
2 changes: 1 addition & 1 deletion app/controlplane/api/controlplane/v1/status_http.pb.go

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

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/cmd/wire_gen.go

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

89 changes: 82 additions & 7 deletions app/controlplane/internal/biz/casmapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,39 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"time"

"github.com/chainloop-dev/chainloop/internal/attestation/renderer/chainloop"
"github.com/chainloop-dev/chainloop/internal/servicelogger"
"github.com/go-kratos/kratos/v2/log"
cr_v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/uuid"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
)

type CASMapping struct {
ID, CASBackendID, OrgID, WorkflowRunID uuid.UUID
Digest string
CreatedAt *time.Time
ID, OrgID, WorkflowRunID uuid.UUID
CASBackend *CASBackend
Digest string
CreatedAt *time.Time
}

type CASMappingRepo interface {
Create(ctx context.Context, digest string, casBackendID, workflowRunID uuid.UUID) (*CASMapping, error)
// List all the CAS mappings for the given digest
FindByDigest(ctx context.Context, digest string) ([]*CASMapping, error)
}

type CASMappingUseCase struct {
repo CASMappingRepo
logger *log.Helper
repo CASMappingRepo
membershipRepo MembershipRepo
logger *log.Helper
}

func NewCASMappingUseCase(repo CASMappingRepo, logger log.Logger) *CASMappingUseCase {
return &CASMappingUseCase{repo, log.NewHelper(logger)}
func NewCASMappingUseCase(repo CASMappingRepo, mRepo MembershipRepo, logger log.Logger) *CASMappingUseCase {
return &CASMappingUseCase{repo, mRepo, servicelogger.ScopedHelper(logger, "cas-mapping-usecase")}
}

func (uc *CASMappingUseCase) Create(ctx context.Context, digest string, casBackendID, workflowRunID string) (*CASMapping, error) {
Expand All @@ -67,6 +73,75 @@ func (uc *CASMappingUseCase) Create(ctx context.Context, digest string, casBacke
return uc.repo.Create(ctx, digest, casBackendUUID, workflowRunUUID)
}

// FindCASMappingForDownload returns the CASMapping appropriate for the given digest and user
// This means any mapping that points to an organization which the user is member of
// If there are multiple mappings, it will try to pick the one that points to a default backend
// Otherwise the first one
func (uc *CASMappingUseCase) FindCASMappingForDownload(ctx context.Context, digest string, userID string) (*CASMapping, error) {
uc.logger.Infow("msg", "finding cas mapping for download", "digest", digest, "user", userID)

userUUID, err := uuid.Parse(userID)
if err != nil {
return nil, NewErrInvalidUUID(err)
}

if _, err = cr_v1.NewHash(digest); err != nil {
return nil, NewErrValidation(fmt.Errorf("invalid digest format: %w", err))
}

// list all the CAS allMappings for the given digest
allMappings, err := uc.repo.FindByDigest(ctx, digest)
if err != nil {
return nil, fmt.Errorf("failed to list cas mappings: %w", err)
}

uc.logger.Debugw("msg", fmt.Sprintf("found %d entries globally", len(allMappings)), "digest", digest, "user", userID)
// The given digest has not been uploaded to any CAS backend
if len(allMappings) == 0 {
return nil, NewErrNotFound("digest not found in any mapping")
}

// filter the ones that the user has access to.
// This means any mapping that points to an organization which the user is member of
userMappings := make([]*CASMapping, 0)
memberships, err := uc.membershipRepo.FindByUser(ctx, userUUID)
if err != nil {
return nil, fmt.Errorf("failed to list memberships: %w", err)
}

for _, mapping := range allMappings {
for _, m := range memberships {
if mapping.OrgID == m.OrganizationID {
userMappings = append(userMappings, mapping)
}
}
}

uc.logger.Debugw("msg", fmt.Sprintf("found %d entries for the user", len(userMappings)), "digest", digest, "user", userID)

// The user has not access to
if len(userMappings) == 0 {
uc.logger.Warnw("msg", "digest exist but user does not have access to it", "digest", digest, "user", userID)
return nil, NewErrUnauthorized(errors.New("unauthorized access to the artifact"))
}

// Pick the appropriate mapping from multiple ones
// for now it will work as follows
// 1 - If there is only one mapping, return it
// 2 - if there are more than 1, we try to pick the one that points to a default backend
// 3 - Otherwise the first one
result := userMappings[0]
for _, mapping := range userMappings {
if mapping.CASBackend.Default {
result = mapping
break
}
}

uc.logger.Infow("msg", "mapping found!", "digest", digest, "user", userID, "casBackend", result.CASBackend.ID, "default", result.CASBackend.Default)
return result, nil
}

type CASMappingLookupRef struct {
Name, Digest string
}
Expand Down
Loading