Skip to content

Commit 57d7247

Browse files
authored
Merge pull request kubesphere#3286 from LinuxSuRen/fix-pip-input-approve-check
Fix the incorrect approvable check of Pipeline input
2 parents a0780e2 + 530c691 commit 57d7247

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
@@ -254,7 +254,7 @@ func (s *APIServer) installKubeSphereAPIs() {
254254
s.KubernetesClient.KubeSphere(),
255255
s.S3Client,
256256
s.Config.DevopsOptions.Host,
257-
amOperator))
257+
rbacAuthorizer))
258258
urlruntime.Must(devopsv1alpha3.AddToContainer(s.container,
259259
s.DevopsClient,
260260
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"
@@ -279,11 +280,53 @@ func (h *ProjectPipelineHandler) GetPipelineRunNodes(req *restful.Request, resp
279280
resp.WriteAsJson(res)
280281
}
281282

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

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

297-
nodes[i].Steps[j].Approvable = isAdmin || step.Input.Approvable(currentUserName)
340+
nodes[i].Steps[j].Approvable = isAdmin || createdByCurrentUser || step.Input.Approvable(userInfo.GetName())
298341
}
299342
}
300343
}
@@ -310,33 +353,8 @@ func (h *ProjectPipelineHandler) createdBy(projectName string, pipelineName stri
310353
return false
311354
}
312355

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

351369
// check if current user can approve this input
352370
var res []clientDevOps.NodesDetail
353-
if res, err = h.devopsOperator.GetNodesDetail(projectName, pipelineName, runId, httpReq); err == nil {
371+
if res, err = h.devopsOperator.GetNodesDetail(pipeParam.ProjectName, pipeParam.Name, runId, httpReq); err == nil {
372+
h.approvableCheck(res, parsePipelineParam(req))
373+
354374
for _, node := range res {
355375
if node.ID != nodeId {
356376
continue
@@ -361,7 +381,7 @@ func (h *ProjectPipelineHandler) hasSubmitPermission(req *restful.Request) (hasP
361381
continue
362382
}
363383

364-
hasPermit = step.Input.Approvable(currentUserName)
384+
hasPermit = step.Approvable
365385
break
366386
}
367387
break
@@ -412,7 +432,7 @@ func (h *ProjectPipelineHandler) GetNodesDetail(req *restful.Request, resp *rest
412432
parseErr(err, resp)
413433
return
414434
}
415-
h.approvableCheck(res, req)
435+
h.approvableCheck(res, parsePipelineParam(req))
416436

417437
resp.WriteAsJson(res)
418438
}
@@ -620,10 +640,19 @@ func (h *ProjectPipelineHandler) GetBranchNodesDetail(req *restful.Request, resp
620640
parseErr(err, resp)
621641
return
622642
}
623-
h.approvableCheck(res, req)
643+
h.approvableCheck(res, parsePipelineParam(req))
624644
resp.WriteAsJson(res)
625645
}
626646

647+
func parsePipelineParam(req *restful.Request) pipelineParam {
648+
return pipelineParam{
649+
Workspace: req.PathParameter("workspace"),
650+
ProjectName: req.PathParameter("devops"),
651+
Name: req.PathParameter("pipeline"),
652+
Context: req.Request.Context(),
653+
}
654+
}
655+
627656
func (h *ProjectPipelineHandler) GetPipelineBranch(req *restful.Request, resp *restful.Response) {
628657
projectName := req.PathParameter("devops")
629658
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).
@@ -732,6 +735,14 @@ func AddJenkinsToContainer(webservice *restful.WebService, devopsClient devops.I
732735
return nil
733736
}
734737

738+
type pipelineParam struct {
739+
Workspace string
740+
ProjectName string
741+
Name string
742+
743+
Context context.Context
744+
}
745+
735746
type errorResponder struct{}
736747

737748
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)