未验证 提交 57d72472 编写于 作者: K KubeSphere CI Bot 提交者: GitHub

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

Fix the incorrect approvable check of Pipeline input
...@@ -17,8 +17,16 @@ tag_for_branch() { ...@@ -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"
} }
......
...@@ -254,7 +254,7 @@ func (s *APIServer) installKubeSphereAPIs() { ...@@ -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(),
......
...@@ -133,7 +133,7 @@ func (a AttributesRecord) GetVerb() string { ...@@ -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 ( ...@@ -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"
)
...@@ -23,9 +23,10 @@ import ( ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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")
......
...@@ -17,10 +17,10 @@ limitations under the License. ...@@ -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 ( ...@@ -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,
} }
} }
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -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 ( ...@@ -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 ( ...@@ -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 ...@@ -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 ...@@ -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) {
......
...@@ -1122,14 +1122,10 @@ func (i *Input) GetSubmitters() (submitters []string) { ...@@ -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
......
...@@ -19,8 +19,8 @@ func TestGetSubmitters(t *testing.T) { ...@@ -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")
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册