Skip to content

Commit 530c691

Browse files
committed
Fix the incorrect approvable check of Pipeline input
Signed-off-by: rick <rick@jenkins-zh.cn>
1 parent 1f4d5cb commit 530c691

File tree

8 files changed

+114
-58
lines changed

8 files changed

+114
-58
lines changed

hack/docker_build.sh

+9-1
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,16 @@ tag_for_branch() {
1717
}
1818

1919
get_repo() {
20-
local repo=$1
20+
local repo=${REPO} # read from env
2121
repo=${repo:-kubespheredev}
22+
if [[ "$1" != "" ]]; then
23+
repo="$1"
24+
fi
25+
26+
# set the default value if there's no user defined
27+
if [[ "${repo}" == "" ]]; then
28+
repo="kubespheredev"
29+
fi
2230
echo "$repo"
2331
}
2432

pkg/apiserver/apiserver.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ func (s *APIServer) installKubeSphereAPIs() {
250250
s.KubernetesClient.KubeSphere(),
251251
s.S3Client,
252252
s.Config.DevopsOptions.Host,
253-
amOperator))
253+
rbacAuthorizer))
254254
urlruntime.Must(devopsv1alpha3.AddToContainer(s.container,
255255
s.DevopsClient,
256256
s.KubernetesClient.Kubernetes(),

pkg/apiserver/authorization/authorizer/interfaces.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func (a AttributesRecord) GetVerb() string {
133133
}
134134

135135
func (a AttributesRecord) IsReadOnly() bool {
136-
return a.Verb == "get" || a.Verb == "list" || a.Verb == "watch"
136+
return a.Verb == VerbGet || a.Verb == VerbList || a.Verb == VerbWatch
137137
}
138138

139139
func (a AttributesRecord) GetCluster() string {
@@ -199,3 +199,14 @@ const (
199199
// to allow or deny an action.
200200
DecisionNoOpinion
201201
)
202+
203+
const (
204+
// VerbList represents the verb of listing resources
205+
VerbList = "list"
206+
// VerbCreate represents the verb of creating a resource
207+
VerbCreate = "create"
208+
// VerbGet represents the verb of getting a resource or resources
209+
VerbGet = "get"
210+
// VerbWatch represents the verb of watching a resource
211+
VerbWatch = "watch"
212+
)

pkg/kapis/devops/v1alpha2/devops.go

+64-35
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import (
2323
"github.com/emicklei/go-restful"
2424
"k8s.io/apiserver/pkg/authentication/user"
2525
log "k8s.io/klog"
26+
"k8s.io/klog/v2"
2627
"kubesphere.io/kubesphere/pkg/api"
2728
"kubesphere.io/kubesphere/pkg/apis/devops/v1alpha3"
28-
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
29+
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
2930
"kubesphere.io/kubesphere/pkg/apiserver/request"
3031
"kubesphere.io/kubesphere/pkg/constants"
3132
"kubesphere.io/kubesphere/pkg/models/devops"
@@ -277,11 +278,53 @@ func (h *ProjectPipelineHandler) GetPipelineRunNodes(req *restful.Request, resp
277278
resp.WriteAsJson(res)
278279
}
279280

280-
func (h *ProjectPipelineHandler) approvableCheck(nodes []clientDevOps.NodesDetail, req *restful.Request) {
281-
currentUserName, roleName := h.getCurrentUser(req)
281+
// there're two situation here:
282+
// 1. the particular submitters exist
283+
// the users who are the owner of this Pipeline or the submitter of this Pipeline, or has the auth to create a DevOps project
284+
// 2. no particular submitters
285+
// only the owner of this Pipeline can approve or reject it
286+
func (h *ProjectPipelineHandler) approvableCheck(nodes []clientDevOps.NodesDetail, pipe pipelineParam) {
287+
var userInfo user.Info
288+
var ok bool
289+
var isAdmin bool
282290
// check if current user belong to the admin group, grant it if it's true
283-
isAdmin := roleName == iamv1alpha2.PlatformAdmin
291+
if userInfo, ok = request.UserFrom(pipe.Context); ok {
292+
createAuth := authorizer.AttributesRecord{
293+
User: userInfo,
294+
Verb: authorizer.VerbCreate,
295+
Workspace: pipe.Workspace,
296+
Resource: "devopsprojects",
297+
ResourceRequest: true,
298+
ResourceScope: request.DevOpsScope,
299+
}
300+
301+
if decision, _, err := h.authorizer.Authorize(createAuth); err == nil {
302+
isAdmin = decision == authorizer.DecisionAllow
303+
} else {
304+
// this is an expected case, printing the debug info for troubleshooting
305+
klog.V(8).Infof("authorize failed with '%v', error is '%v'",
306+
createAuth, err)
307+
}
308+
} else {
309+
klog.V(6).Infof("cannot get the current user when checking the approvable with pipeline '%s/%s'",
310+
pipe.ProjectName, pipe.Name)
311+
return
312+
}
284313

314+
var createdByCurrentUser bool // indicate if the current user is the owner
315+
if pipeline, err := h.devopsOperator.GetPipelineObj(pipe.ProjectName, pipe.Name); err == nil {
316+
if creator, ok := pipeline.GetAnnotations()[constants.CreatorAnnotationKey]; ok {
317+
createdByCurrentUser = userInfo.GetName() == creator
318+
} else {
319+
klog.V(6).Infof("annotation '%s' is necessary but it is missing from '%s/%s'",
320+
constants.CreatorAnnotationKey, pipe.ProjectName, pipe.Name)
321+
}
322+
} else {
323+
klog.V(6).Infof("cannot find pipeline '%s/%s', error is '%v'", pipe.ProjectName, pipe.Name, err)
324+
return
325+
}
326+
327+
// check every input steps if it's approvable
285328
for i, node := range nodes {
286329
if node.State != clientDevOps.StatePaused {
287330
continue
@@ -292,7 +335,7 @@ func (h *ProjectPipelineHandler) approvableCheck(nodes []clientDevOps.NodesDetai
292335
continue
293336
}
294337

295-
nodes[i].Steps[j].Approvable = isAdmin || step.Input.Approvable(currentUserName)
338+
nodes[i].Steps[j].Approvable = isAdmin || createdByCurrentUser || step.Input.Approvable(userInfo.GetName())
296339
}
297340
}
298341
}
@@ -308,33 +351,8 @@ func (h *ProjectPipelineHandler) createdBy(projectName string, pipelineName stri
308351
return false
309352
}
310353

311-
func (h *ProjectPipelineHandler) getCurrentUser(req *restful.Request) (username, roleName string) {
312-
var userInfo user.Info
313-
var ok bool
314-
var err error
315-
316-
ctx := req.Request.Context()
317-
if userInfo, ok = request.UserFrom(ctx); ok {
318-
var role *iamv1alpha2.GlobalRole
319-
username = userInfo.GetName()
320-
if role, err = h.amInterface.GetGlobalRoleOfUser(username); err == nil {
321-
roleName = role.Name
322-
}
323-
}
324-
return
325-
}
326-
327354
func (h *ProjectPipelineHandler) hasSubmitPermission(req *restful.Request) (hasPermit bool, err error) {
328-
currentUserName, roleName := h.getCurrentUser(req)
329-
projectName := req.PathParameter("devops")
330-
pipelineName := req.PathParameter("pipeline")
331-
// check if current user belong to the admin group or he's the owner, grant it if it's true
332-
if roleName == iamv1alpha2.PlatformAdmin || h.createdBy(projectName, pipelineName, currentUserName) {
333-
hasPermit = true
334-
return
335-
}
336-
337-
// step 2, check if current user if was addressed
355+
pipeParam := parsePipelineParam(req)
338356
httpReq := &http.Request{
339357
URL: req.Request.URL,
340358
Header: req.Request.Header,
@@ -348,7 +366,9 @@ func (h *ProjectPipelineHandler) hasSubmitPermission(req *restful.Request) (hasP
348366

349367
// check if current user can approve this input
350368
var res []clientDevOps.NodesDetail
351-
if res, err = h.devopsOperator.GetNodesDetail(projectName, pipelineName, runId, httpReq); err == nil {
369+
if res, err = h.devopsOperator.GetNodesDetail(pipeParam.ProjectName, pipeParam.Name, runId, httpReq); err == nil {
370+
h.approvableCheck(res, parsePipelineParam(req))
371+
352372
for _, node := range res {
353373
if node.ID != nodeId {
354374
continue
@@ -359,7 +379,7 @@ func (h *ProjectPipelineHandler) hasSubmitPermission(req *restful.Request) (hasP
359379
continue
360380
}
361381

362-
hasPermit = step.Input.Approvable(currentUserName)
382+
hasPermit = step.Approvable
363383
break
364384
}
365385
break
@@ -410,7 +430,7 @@ func (h *ProjectPipelineHandler) GetNodesDetail(req *restful.Request, resp *rest
410430
parseErr(err, resp)
411431
return
412432
}
413-
h.approvableCheck(res, req)
433+
h.approvableCheck(res, parsePipelineParam(req))
414434

415435
resp.WriteAsJson(res)
416436
}
@@ -618,10 +638,19 @@ func (h *ProjectPipelineHandler) GetBranchNodesDetail(req *restful.Request, resp
618638
parseErr(err, resp)
619639
return
620640
}
621-
h.approvableCheck(res, req)
641+
h.approvableCheck(res, parsePipelineParam(req))
622642
resp.WriteAsJson(res)
623643
}
624644

645+
func parsePipelineParam(req *restful.Request) pipelineParam {
646+
return pipelineParam{
647+
Workspace: req.PathParameter("workspace"),
648+
ProjectName: req.PathParameter("devops"),
649+
Name: req.PathParameter("pipeline"),
650+
Context: req.Request.Context(),
651+
}
652+
}
653+
625654
func (h *ProjectPipelineHandler) GetPipelineBranch(req *restful.Request, resp *restful.Response) {
626655
projectName := req.PathParameter("devops")
627656
pipelineName := req.PathParameter("pipeline")

pkg/kapis/devops/v1alpha2/handler.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ limitations under the License.
1717
package v1alpha2
1818

1919
import (
20+
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
2021
"kubesphere.io/kubesphere/pkg/client/clientset/versioned"
2122
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
2223
"kubesphere.io/kubesphere/pkg/models/devops"
23-
"kubesphere.io/kubesphere/pkg/models/iam/am"
2424
devopsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
2525
"kubesphere.io/kubesphere/pkg/simple/client/s3"
2626
"kubesphere.io/kubesphere/pkg/simple/client/sonarqube"
@@ -29,18 +29,19 @@ import (
2929
type ProjectPipelineHandler struct {
3030
devopsOperator devops.DevopsOperator
3131
projectCredentialGetter devops.ProjectCredentialGetter
32-
amInterface am.AccessManagementInterface
32+
authorizer authorizer.Authorizer
3333
}
3434

3535
type PipelineSonarHandler struct {
3636
pipelineSonarGetter devops.PipelineSonarGetter
3737
}
3838

39-
func NewProjectPipelineHandler(devopsClient devopsClient.Interface, ksInformers externalversions.SharedInformerFactory, amInterface am.AccessManagementInterface) ProjectPipelineHandler {
39+
func NewProjectPipelineHandler(devopsClient devopsClient.Interface, ksInformers externalversions.SharedInformerFactory,
40+
authorizer authorizer.Authorizer) ProjectPipelineHandler {
4041
return ProjectPipelineHandler{
4142
devopsOperator: devops.NewDevopsOperator(devopsClient, nil, nil, ksInformers, nil),
4243
projectCredentialGetter: devops.NewProjectCredentialOperator(devopsClient),
43-
amInterface: amInterface,
44+
authorizer: authorizer,
4445
}
4546
}
4647

pkg/kapis/devops/v1alpha2/register.go

+17-6
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,25 @@ limitations under the License.
1717
package v1alpha2
1818

1919
import (
20+
"context"
2021
"fmt"
2122
"github.com/emicklei/go-restful"
2223
"github.com/emicklei/go-restful-openapi"
2324
"k8s.io/apimachinery/pkg/runtime/schema"
2425
"k8s.io/apimachinery/pkg/util/proxy"
2526
"k8s.io/klog"
2627
devopsv1alpha1 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha1"
28+
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
2729
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
2830
"kubesphere.io/kubesphere/pkg/client/clientset/versioned"
2931
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
3032
"kubesphere.io/kubesphere/pkg/constants"
31-
"kubesphere.io/kubesphere/pkg/models/iam/am"
3233
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
3334
"kubesphere.io/kubesphere/pkg/simple/client/s3"
3435
"kubesphere.io/kubesphere/pkg/simple/client/sonarqube"
3536
"net/url"
3637
"strings"
3738

38-
//"kubesphere.io/kubesphere/pkg/models/devops"
3939
"kubesphere.io/kubesphere/pkg/simple/client/devops"
4040
"net/http"
4141
)
@@ -47,10 +47,12 @@ const (
4747

4848
var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"}
4949

50-
func AddToContainer(container *restful.Container, ksInformers externalversions.SharedInformerFactory, devopsClient devops.Interface, sonarqubeClient sonarqube.SonarInterface, ksClient versioned.Interface, s3Client s3.Interface, endpoint string, amInterface am.AccessManagementInterface) error {
50+
func AddToContainer(container *restful.Container, ksInformers externalversions.SharedInformerFactory, devopsClient devops.Interface,
51+
sonarqubeClient sonarqube.SonarInterface, ksClient versioned.Interface, s3Client s3.Interface, endpoint string,
52+
authorizer authorizer.Authorizer) error {
5153
ws := runtime.NewWebService(GroupVersion)
5254

53-
err := AddPipelineToWebService(ws, devopsClient, ksInformers, amInterface)
55+
err := AddPipelineToWebService(ws, devopsClient, ksInformers, authorizer)
5456
if err != nil {
5557
return err
5658
}
@@ -75,12 +77,13 @@ func AddToContainer(container *restful.Container, ksInformers externalversions.S
7577
return nil
7678
}
7779

78-
func AddPipelineToWebService(webservice *restful.WebService, devopsClient devops.Interface, ksInformers externalversions.SharedInformerFactory, amInterface am.AccessManagementInterface) error {
80+
func AddPipelineToWebService(webservice *restful.WebService, devopsClient devops.Interface, ksInformers externalversions.SharedInformerFactory,
81+
authorizer authorizer.Authorizer) error {
7982

8083
projectPipelineEnable := devopsClient != nil
8184

8285
if projectPipelineEnable {
83-
projectPipelineHandler := NewProjectPipelineHandler(devopsClient, ksInformers, amInterface)
86+
projectPipelineHandler := NewProjectPipelineHandler(devopsClient, ksInformers, authorizer)
8487

8588
webservice.Route(webservice.GET("/devops/{devops}/credentials/{credential}/usage").
8689
To(projectPipelineHandler.GetProjectCredentialUsage).
@@ -723,6 +726,14 @@ func AddJenkinsToContainer(webservice *restful.WebService, devopsClient devops.I
723726
return nil
724727
}
725728

729+
type pipelineParam struct {
730+
Workspace string
731+
ProjectName string
732+
Name string
733+
734+
Context context.Context
735+
}
736+
726737
type errorResponder struct{}
727738

728739
func (e *errorResponder) Error(w http.ResponseWriter, req *http.Request, err error) {

pkg/simple/client/devops/pipeline.go

+4-8
Original file line numberDiff line numberDiff line change
@@ -1122,14 +1122,10 @@ func (i *Input) GetSubmitters() (submitters []string) {
11221122
func (i *Input) Approvable(identify string) (ok bool) {
11231123
submitters := i.GetSubmitters()
11241124

1125-
// it means anyone can approve this if there's no specific one
1126-
if len(submitters) == 0 {
1127-
ok = true
1128-
} else {
1129-
for _, submitter := range submitters {
1130-
if submitter == identify {
1131-
ok = true
1132-
}
1125+
for _, submitter := range submitters {
1126+
if submitter == identify {
1127+
ok = true
1128+
break
11331129
}
11341130
}
11351131
return

pkg/simple/client/devops/pipeline_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ func TestGetSubmitters(t *testing.T) {
1919
func TestApprovable(t *testing.T) {
2020
input := &Input{}
2121

22-
assert.Equal(t, input.Approvable(""), true, "should allow anyone to approve it if there's no submitter given")
23-
assert.Equal(t, input.Approvable("fake"), true, "should allow anyone to approve it if there's no submitter given")
22+
assert.Equal(t, input.Approvable(""), false, "should allow anyone to approve it if there's no submitter given")
23+
assert.Equal(t, input.Approvable("fake"), false, "should allow anyone to approve it if there's no submitter given")
2424

2525
input.Submitter = "fake"
2626
assert.Equal(t, input.Approvable(""), false, "should not approve by nobody if there's a particular submitter")

0 commit comments

Comments
 (0)