/
cascredential.go
133 lines (113 loc) · 4.54 KB
/
cascredential.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//
// Copyright 2023 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package service
import (
"context"
pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
errors "github.com/go-kratos/kratos/v2/errors"
"github.com/chainloop-dev/chainloop/app/controlplane/internal/authz"
"github.com/chainloop-dev/chainloop/app/controlplane/internal/biz"
casJWT "github.com/chainloop-dev/chainloop/internal/robotaccount/cas"
)
type CASCredentialsService struct {
*service
pb.UnimplementedCASCredentialsServiceServer
casUC *biz.CASCredentialsUseCase
casBackendUC *biz.CASBackendUseCase
casMappingUC *biz.CASMappingUseCase
authz *authz.Enforcer
}
func NewCASCredentialsService(casUC *biz.CASCredentialsUseCase, casmUC *biz.CASMappingUseCase, casBUC *biz.CASBackendUseCase, authz *authz.Enforcer, opts ...NewOpt) *CASCredentialsService {
return &CASCredentialsService{
service: newService(opts...),
casUC: casUC,
// we use the casMappingUC to find the backend to download from
casMappingUC: casmUC,
// we use the casBackendUC to find the default upload backend
casBackendUC: casBUC,
authz: authz,
}
}
// Get will generate temporary credentials to be used against the CAS service for the current organization
func (s *CASCredentialsService) Get(ctx context.Context, req *pb.CASCredentialsServiceGetRequest) (*pb.CASCredentialsServiceGetResponse, error) {
currentUser, currentAPIToken, err := requireCurrentUserOrAPIToken(ctx)
if err != nil {
return nil, err
}
currentOrg, err := requireCurrentOrg(ctx)
if err != nil {
return nil, err
}
// Load the authz subject from the context
currentAuthzSubject, err := requireCurrentAuthzSubject(ctx)
if err != nil {
return nil, err
}
var role casJWT.Role
var policyToCheck *authz.Policy
switch req.GetRole() {
case pb.CASCredentialsServiceGetRequest_ROLE_DOWNLOADER:
role = casJWT.Downloader
policyToCheck = authz.PolicyArtifactDownload
case pb.CASCredentialsServiceGetRequest_ROLE_UPLOADER:
role = casJWT.Uploader
policyToCheck = authz.PolicyArtifactUpload
}
if ok, err := s.authz.Enforce(currentAuthzSubject, policyToCheck); err != nil {
return nil, handleUseCaseErr(err, s.log)
} else if !ok {
return nil, errors.Forbidden("forbidden", "not allowed to perform this operation")
}
// Load the default CAS backend, we'll use it for uploads and as fallback on downloads
backend, err := s.casBackendUC.FindDefaultBackend(ctx, currentOrg.ID)
if err != nil && !biz.IsNotFound(err) {
return nil, handleUseCaseErr(err, s.log)
} else if backend == nil {
return nil, errors.NotFound("not found", "main CAS backend not found")
}
// Try to find the proper backend where the artifact is stored
if role == casJWT.Downloader {
var mapping *biz.CASMapping
// If we are logged in as a user, we'll try to find a mapping for that user
if currentUser != nil {
mapping, err = s.casMappingUC.FindCASMappingForDownloadByUser(ctx, req.Digest, currentUser.ID)
// otherwise, we'll try to find a mapping for the current API token associated orgs
} else if currentAPIToken != nil {
mapping, err = s.casMappingUC.FindCASMappingForDownloadByOrg(ctx, req.Digest, []string{currentOrg.ID})
}
// If we can't find a mapping, we'll use the default backend
if err != nil && !biz.IsNotFound(err) && !biz.IsErrUnauthorized(err) {
if biz.IsErrValidation(err) {
return nil, errors.BadRequest("invalid", err.Error())
}
return nil, handleUseCaseErr(err, s.log)
}
if mapping != nil {
backend = mapping.CASBackend
}
}
// inline backends don't have a download URL
if backend.Inline {
return nil, errors.BadRequest("invalid argument", "cannot upload or download artifacts from an inline CAS backend")
}
ref := &biz.CASCredsOpts{BackendType: string(backend.Provider), SecretPath: backend.SecretName, Role: role}
t, err := s.casUC.GenerateTemporaryCredentials(ref)
if err != nil {
return nil, handleUseCaseErr(err, s.log)
}
return &pb.CASCredentialsServiceGetResponse{
Result: &pb.CASCredentialsServiceGetResponse_Result{Token: t},
}, nil
}