Merge pull request #3286 from LinuxSuRen/fix-pip-input-approve-check

Fix the incorrect approvable check of Pipeline input
This commit is contained in:
KubeSphere CI Bot
2021-02-18 10:41:26 +08:00
committed by GitHub
8 changed files with 114 additions and 58 deletions

View File

@@ -17,8 +17,16 @@ tag_for_branch() {
} }
get_repo() { get_repo() {
local repo=$1 local repo=${REPO} # read from env
repo=${repo:-kubespheredev} repo=${repo:-kubespheredev}
if [[ "$1" != "" ]]; then
repo="$1"
fi
# set the default value if there's no user defined
if [[ "${repo}" == "" ]]; then
repo="kubespheredev"
fi
echo "$repo" echo "$repo"
} }

View File

@@ -254,7 +254,7 @@ func (s *APIServer) installKubeSphereAPIs() {
s.KubernetesClient.KubeSphere(), s.KubernetesClient.KubeSphere(),
s.S3Client, s.S3Client,
s.Config.DevopsOptions.Host, s.Config.DevopsOptions.Host,
amOperator)) rbacAuthorizer))
urlruntime.Must(devopsv1alpha3.AddToContainer(s.container, urlruntime.Must(devopsv1alpha3.AddToContainer(s.container,
s.DevopsClient, s.DevopsClient,
s.KubernetesClient.Kubernetes(), s.KubernetesClient.Kubernetes(),

View File

@@ -133,7 +133,7 @@ func (a AttributesRecord) GetVerb() string {
} }
func (a AttributesRecord) IsReadOnly() bool { func (a AttributesRecord) IsReadOnly() bool {
return a.Verb == "get" || a.Verb == "list" || a.Verb == "watch" return a.Verb == VerbGet || a.Verb == VerbList || a.Verb == VerbWatch
} }
func (a AttributesRecord) GetCluster() string { func (a AttributesRecord) GetCluster() string {
@@ -199,3 +199,14 @@ const (
// to allow or deny an action. // to allow or deny an action.
DecisionNoOpinion DecisionNoOpinion
) )
const (
// VerbList represents the verb of listing resources
VerbList = "list"
// VerbCreate represents the verb of creating a resource
VerbCreate = "create"
// VerbGet represents the verb of getting a resource or resources
VerbGet = "get"
// VerbWatch represents the verb of watching a resource
VerbWatch = "watch"
)

View File

@@ -23,9 +23,10 @@ import (
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
log "k8s.io/klog" log "k8s.io/klog"
"k8s.io/klog/v2"
"kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apis/devops/v1alpha3" "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha3"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/apiserver/request" "kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models/devops" "kubesphere.io/kubesphere/pkg/models/devops"
@@ -279,11 +280,53 @@ func (h *ProjectPipelineHandler) GetPipelineRunNodes(req *restful.Request, resp
resp.WriteAsJson(res) resp.WriteAsJson(res)
} }
func (h *ProjectPipelineHandler) approvableCheck(nodes []clientDevOps.NodesDetail, req *restful.Request) { // there're two situation here:
currentUserName, roleName := h.getCurrentUser(req) // 1. the particular submitters exist
// the users who are the owner of this Pipeline or the submitter of this Pipeline, or has the auth to create a DevOps project
// 2. no particular submitters
// only the owner of this Pipeline can approve or reject it
func (h *ProjectPipelineHandler) approvableCheck(nodes []clientDevOps.NodesDetail, pipe pipelineParam) {
var userInfo user.Info
var ok bool
var isAdmin bool
// check if current user belong to the admin group, grant it if it's true // check if current user belong to the admin group, grant it if it's true
isAdmin := roleName == iamv1alpha2.PlatformAdmin if userInfo, ok = request.UserFrom(pipe.Context); ok {
createAuth := authorizer.AttributesRecord{
User: userInfo,
Verb: authorizer.VerbCreate,
Workspace: pipe.Workspace,
Resource: "devopsprojects",
ResourceRequest: true,
ResourceScope: request.DevOpsScope,
}
if decision, _, err := h.authorizer.Authorize(createAuth); err == nil {
isAdmin = decision == authorizer.DecisionAllow
} else {
// this is an expected case, printing the debug info for troubleshooting
klog.V(8).Infof("authorize failed with '%v', error is '%v'",
createAuth, err)
}
} else {
klog.V(6).Infof("cannot get the current user when checking the approvable with pipeline '%s/%s'",
pipe.ProjectName, pipe.Name)
return
}
var createdByCurrentUser bool // indicate if the current user is the owner
if pipeline, err := h.devopsOperator.GetPipelineObj(pipe.ProjectName, pipe.Name); err == nil {
if creator, ok := pipeline.GetAnnotations()[constants.CreatorAnnotationKey]; ok {
createdByCurrentUser = userInfo.GetName() == creator
} else {
klog.V(6).Infof("annotation '%s' is necessary but it is missing from '%s/%s'",
constants.CreatorAnnotationKey, pipe.ProjectName, pipe.Name)
}
} else {
klog.V(6).Infof("cannot find pipeline '%s/%s', error is '%v'", pipe.ProjectName, pipe.Name, err)
return
}
// check every input steps if it's approvable
for i, node := range nodes { for i, node := range nodes {
if node.State != clientDevOps.StatePaused { if node.State != clientDevOps.StatePaused {
continue continue
@@ -294,7 +337,7 @@ func (h *ProjectPipelineHandler) approvableCheck(nodes []clientDevOps.NodesDetai
continue continue
} }
nodes[i].Steps[j].Approvable = isAdmin || step.Input.Approvable(currentUserName) nodes[i].Steps[j].Approvable = isAdmin || createdByCurrentUser || step.Input.Approvable(userInfo.GetName())
} }
} }
} }
@@ -310,33 +353,8 @@ func (h *ProjectPipelineHandler) createdBy(projectName string, pipelineName stri
return false return false
} }
func (h *ProjectPipelineHandler) getCurrentUser(req *restful.Request) (username, roleName string) {
var userInfo user.Info
var ok bool
var err error
ctx := req.Request.Context()
if userInfo, ok = request.UserFrom(ctx); ok {
var role *iamv1alpha2.GlobalRole
username = userInfo.GetName()
if role, err = h.amInterface.GetGlobalRoleOfUser(username); err == nil {
roleName = role.Name
}
}
return
}
func (h *ProjectPipelineHandler) hasSubmitPermission(req *restful.Request) (hasPermit bool, err error) { func (h *ProjectPipelineHandler) hasSubmitPermission(req *restful.Request) (hasPermit bool, err error) {
currentUserName, roleName := h.getCurrentUser(req) pipeParam := parsePipelineParam(req)
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
// check if current user belong to the admin group or he's the owner, grant it if it's true
if roleName == iamv1alpha2.PlatformAdmin || h.createdBy(projectName, pipelineName, currentUserName) {
hasPermit = true
return
}
// step 2, check if current user if was addressed
httpReq := &http.Request{ httpReq := &http.Request{
URL: req.Request.URL, URL: req.Request.URL,
Header: req.Request.Header, Header: req.Request.Header,
@@ -350,7 +368,9 @@ func (h *ProjectPipelineHandler) hasSubmitPermission(req *restful.Request) (hasP
// check if current user can approve this input // check if current user can approve this input
var res []clientDevOps.NodesDetail var res []clientDevOps.NodesDetail
if res, err = h.devopsOperator.GetNodesDetail(projectName, pipelineName, runId, httpReq); err == nil { if res, err = h.devopsOperator.GetNodesDetail(pipeParam.ProjectName, pipeParam.Name, runId, httpReq); err == nil {
h.approvableCheck(res, parsePipelineParam(req))
for _, node := range res { for _, node := range res {
if node.ID != nodeId { if node.ID != nodeId {
continue continue
@@ -361,7 +381,7 @@ func (h *ProjectPipelineHandler) hasSubmitPermission(req *restful.Request) (hasP
continue continue
} }
hasPermit = step.Input.Approvable(currentUserName) hasPermit = step.Approvable
break break
} }
break break
@@ -412,7 +432,7 @@ func (h *ProjectPipelineHandler) GetNodesDetail(req *restful.Request, resp *rest
parseErr(err, resp) parseErr(err, resp)
return return
} }
h.approvableCheck(res, req) h.approvableCheck(res, parsePipelineParam(req))
resp.WriteAsJson(res) resp.WriteAsJson(res)
} }
@@ -620,10 +640,19 @@ func (h *ProjectPipelineHandler) GetBranchNodesDetail(req *restful.Request, resp
parseErr(err, resp) parseErr(err, resp)
return return
} }
h.approvableCheck(res, req) h.approvableCheck(res, parsePipelineParam(req))
resp.WriteAsJson(res) resp.WriteAsJson(res)
} }
func parsePipelineParam(req *restful.Request) pipelineParam {
return pipelineParam{
Workspace: req.PathParameter("workspace"),
ProjectName: req.PathParameter("devops"),
Name: req.PathParameter("pipeline"),
Context: req.Request.Context(),
}
}
func (h *ProjectPipelineHandler) GetPipelineBranch(req *restful.Request, resp *restful.Response) { func (h *ProjectPipelineHandler) GetPipelineBranch(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops") projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline") pipelineName := req.PathParameter("pipeline")

View File

@@ -17,10 +17,10 @@ limitations under the License.
package v1alpha2 package v1alpha2
import ( import (
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned" "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/client/informers/externalversions" "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/models/devops" "kubesphere.io/kubesphere/pkg/models/devops"
"kubesphere.io/kubesphere/pkg/models/iam/am"
devopsClient "kubesphere.io/kubesphere/pkg/simple/client/devops" devopsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/simple/client/s3" "kubesphere.io/kubesphere/pkg/simple/client/s3"
"kubesphere.io/kubesphere/pkg/simple/client/sonarqube" "kubesphere.io/kubesphere/pkg/simple/client/sonarqube"
@@ -29,18 +29,19 @@ import (
type ProjectPipelineHandler struct { type ProjectPipelineHandler struct {
devopsOperator devops.DevopsOperator devopsOperator devops.DevopsOperator
projectCredentialGetter devops.ProjectCredentialGetter projectCredentialGetter devops.ProjectCredentialGetter
amInterface am.AccessManagementInterface authorizer authorizer.Authorizer
} }
type PipelineSonarHandler struct { type PipelineSonarHandler struct {
pipelineSonarGetter devops.PipelineSonarGetter pipelineSonarGetter devops.PipelineSonarGetter
} }
func NewProjectPipelineHandler(devopsClient devopsClient.Interface, ksInformers externalversions.SharedInformerFactory, amInterface am.AccessManagementInterface) ProjectPipelineHandler { func NewProjectPipelineHandler(devopsClient devopsClient.Interface, ksInformers externalversions.SharedInformerFactory,
authorizer authorizer.Authorizer) ProjectPipelineHandler {
return ProjectPipelineHandler{ return ProjectPipelineHandler{
devopsOperator: devops.NewDevopsOperator(devopsClient, nil, nil, ksInformers, nil), devopsOperator: devops.NewDevopsOperator(devopsClient, nil, nil, ksInformers, nil),
projectCredentialGetter: devops.NewProjectCredentialOperator(devopsClient), projectCredentialGetter: devops.NewProjectCredentialOperator(devopsClient),
amInterface: amInterface, authorizer: authorizer,
} }
} }

View File

@@ -17,6 +17,7 @@ limitations under the License.
package v1alpha2 package v1alpha2
import ( import (
"context"
"fmt" "fmt"
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
"github.com/emicklei/go-restful-openapi" "github.com/emicklei/go-restful-openapi"
@@ -24,18 +25,17 @@ import (
"k8s.io/apimachinery/pkg/util/proxy" "k8s.io/apimachinery/pkg/util/proxy"
"k8s.io/klog" "k8s.io/klog"
devopsv1alpha1 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha1" devopsv1alpha1 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha1"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/apiserver/runtime" "kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned" "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/client/informers/externalversions" "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins" "kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
"kubesphere.io/kubesphere/pkg/simple/client/s3" "kubesphere.io/kubesphere/pkg/simple/client/s3"
"kubesphere.io/kubesphere/pkg/simple/client/sonarqube" "kubesphere.io/kubesphere/pkg/simple/client/sonarqube"
"net/url" "net/url"
"strings" "strings"
//"kubesphere.io/kubesphere/pkg/models/devops"
"kubesphere.io/kubesphere/pkg/simple/client/devops" "kubesphere.io/kubesphere/pkg/simple/client/devops"
"net/http" "net/http"
) )
@@ -47,10 +47,12 @@ const (
var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"} var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"}
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 { func AddToContainer(container *restful.Container, ksInformers externalversions.SharedInformerFactory, devopsClient devops.Interface,
sonarqubeClient sonarqube.SonarInterface, ksClient versioned.Interface, s3Client s3.Interface, endpoint string,
authorizer authorizer.Authorizer) error {
ws := runtime.NewWebService(GroupVersion) ws := runtime.NewWebService(GroupVersion)
err := AddPipelineToWebService(ws, devopsClient, ksInformers, amInterface) err := AddPipelineToWebService(ws, devopsClient, ksInformers, authorizer)
if err != nil { if err != nil {
return err return err
} }
@@ -75,12 +77,13 @@ func AddToContainer(container *restful.Container, ksInformers externalversions.S
return nil return nil
} }
func AddPipelineToWebService(webservice *restful.WebService, devopsClient devops.Interface, ksInformers externalversions.SharedInformerFactory, amInterface am.AccessManagementInterface) error { func AddPipelineToWebService(webservice *restful.WebService, devopsClient devops.Interface, ksInformers externalversions.SharedInformerFactory,
authorizer authorizer.Authorizer) error {
projectPipelineEnable := devopsClient != nil projectPipelineEnable := devopsClient != nil
if projectPipelineEnable { if projectPipelineEnable {
projectPipelineHandler := NewProjectPipelineHandler(devopsClient, ksInformers, amInterface) projectPipelineHandler := NewProjectPipelineHandler(devopsClient, ksInformers, authorizer)
webservice.Route(webservice.GET("/devops/{devops}/credentials/{credential}/usage"). webservice.Route(webservice.GET("/devops/{devops}/credentials/{credential}/usage").
To(projectPipelineHandler.GetProjectCredentialUsage). To(projectPipelineHandler.GetProjectCredentialUsage).
@@ -732,6 +735,14 @@ func AddJenkinsToContainer(webservice *restful.WebService, devopsClient devops.I
return nil return nil
} }
type pipelineParam struct {
Workspace string
ProjectName string
Name string
Context context.Context
}
type errorResponder struct{} type errorResponder struct{}
func (e *errorResponder) Error(w http.ResponseWriter, req *http.Request, err error) { func (e *errorResponder) Error(w http.ResponseWriter, req *http.Request, err error) {

View File

@@ -1122,14 +1122,10 @@ func (i *Input) GetSubmitters() (submitters []string) {
func (i *Input) Approvable(identify string) (ok bool) { func (i *Input) Approvable(identify string) (ok bool) {
submitters := i.GetSubmitters() submitters := i.GetSubmitters()
// it means anyone can approve this if there's no specific one for _, submitter := range submitters {
if len(submitters) == 0 { if submitter == identify {
ok = true ok = true
} else { break
for _, submitter := range submitters {
if submitter == identify {
ok = true
}
} }
} }
return return

View File

@@ -19,8 +19,8 @@ func TestGetSubmitters(t *testing.T) {
func TestApprovable(t *testing.T) { func TestApprovable(t *testing.T) {
input := &Input{} input := &Input{}
assert.Equal(t, input.Approvable(""), true, "should allow anyone to approve it if there's no submitter given") assert.Equal(t, input.Approvable(""), false, "should allow anyone to approve it if there's no submitter given")
assert.Equal(t, input.Approvable("fake"), true, "should allow anyone to approve it if there's no submitter given") assert.Equal(t, input.Approvable("fake"), false, "should allow anyone to approve it if there's no submitter given")
input.Submitter = "fake" input.Submitter = "fake"
assert.Equal(t, input.Approvable(""), false, "should not approve by nobody if there's a particular submitter") assert.Equal(t, input.Approvable(""), false, "should not approve by nobody if there's a particular submitter")