Merge pull request #3156 from LinuxSuRen/pipeline-approve
Restrict only specific users or admin can approve a pipeline
This commit is contained in:
@@ -17,10 +17,18 @@ limitations under the License.
|
||||
package v1alpha2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/emicklei/go-restful"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
log "k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/models/devops"
|
||||
clientDevOps "kubesphere.io/kubesphere/pkg/simple/client/devops"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
@@ -202,6 +210,101 @@ func (h *ProjectPipelineHandler) GetPipelineRunNodes(req *restful.Request, resp
|
||||
resp.WriteAsJson(res)
|
||||
}
|
||||
|
||||
func (h *ProjectPipelineHandler) approvableCheck(nodes []clientDevOps.NodesDetail, req *restful.Request) {
|
||||
currentUserName, roleName := h.getCurrentUser(req)
|
||||
// check if current user belong to the admin group, grant it if it's true
|
||||
isAdmin := roleName == iamv1alpha2.PlatformAdmin
|
||||
|
||||
for i, node := range nodes {
|
||||
if node.State != clientDevOps.StatePaused {
|
||||
continue
|
||||
}
|
||||
|
||||
for j, step := range node.Steps {
|
||||
if step.State != clientDevOps.StatePaused || step.Input == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
nodes[i].Steps[j].Approvable = isAdmin || step.Input.Approvable(currentUserName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ProjectPipelineHandler) createdBy(projectName string, pipelineName string, currentUserName string) bool {
|
||||
if pipeline, err := h.devopsOperator.GetPipelineObj(projectName, pipelineName); err == nil {
|
||||
if creator, ok := pipeline.Annotations[constants.CreatorAnnotationKey]; ok {
|
||||
return creator == currentUserName
|
||||
}
|
||||
} else {
|
||||
log.V(4).Infof("cannot get pipeline %s/%s, error %#v", projectName, pipelineName, err)
|
||||
}
|
||||
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) {
|
||||
currentUserName, roleName := h.getCurrentUser(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{
|
||||
URL: req.Request.URL,
|
||||
Header: req.Request.Header,
|
||||
Form: req.Request.Form,
|
||||
PostForm: req.Request.PostForm,
|
||||
}
|
||||
|
||||
runId := req.PathParameter("run")
|
||||
nodeId := req.PathParameter("node")
|
||||
stepId := req.PathParameter("step")
|
||||
|
||||
// check if current user can approve this input
|
||||
var res []clientDevOps.NodesDetail
|
||||
if res, err = h.devopsOperator.GetNodesDetail(projectName, pipelineName, runId, httpReq); err == nil {
|
||||
for _, node := range res {
|
||||
if node.ID != nodeId {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, step := range node.Steps {
|
||||
if step.ID != stepId || step.Input == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
hasPermit = step.Input.Approvable(currentUserName)
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
} else {
|
||||
log.V(4).Infof("cannot get nodes detail, error: %v", err)
|
||||
err = errors.New("cannot get the submitters of current pipeline run")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *ProjectPipelineHandler) SubmitInputStep(req *restful.Request, resp *restful.Response) {
|
||||
projectName := req.PathParameter("devops")
|
||||
pipelineName := req.PathParameter("pipeline")
|
||||
@@ -209,13 +312,25 @@ func (h *ProjectPipelineHandler) SubmitInputStep(req *restful.Request, resp *res
|
||||
nodeId := req.PathParameter("node")
|
||||
stepId := req.PathParameter("step")
|
||||
|
||||
res, err := h.devopsOperator.SubmitInputStep(projectName, pipelineName, runId, nodeId, stepId, req.Request)
|
||||
if err != nil {
|
||||
parseErr(err, resp)
|
||||
return
|
||||
}
|
||||
var response []byte
|
||||
var err error
|
||||
var ok bool
|
||||
|
||||
resp.Write(res)
|
||||
if ok, err = h.hasSubmitPermission(req); !ok || err != nil {
|
||||
msg := map[string]string{
|
||||
"allow": "false",
|
||||
"message": fmt.Sprintf("%v", err),
|
||||
}
|
||||
|
||||
response, _ = json.Marshal(msg)
|
||||
} else {
|
||||
response, err = h.devopsOperator.SubmitInputStep(projectName, pipelineName, runId, nodeId, stepId, req.Request)
|
||||
if err != nil {
|
||||
parseErr(err, resp)
|
||||
return
|
||||
}
|
||||
}
|
||||
resp.Write(response)
|
||||
}
|
||||
|
||||
func (h *ProjectPipelineHandler) GetNodesDetail(req *restful.Request, resp *restful.Response) {
|
||||
@@ -228,6 +343,8 @@ func (h *ProjectPipelineHandler) GetNodesDetail(req *restful.Request, resp *rest
|
||||
parseErr(err, resp)
|
||||
return
|
||||
}
|
||||
h.approvableCheck(res, req)
|
||||
|
||||
resp.WriteAsJson(res)
|
||||
}
|
||||
|
||||
@@ -401,13 +518,26 @@ func (h *ProjectPipelineHandler) SubmitBranchInputStep(req *restful.Request, res
|
||||
nodeId := req.PathParameter("node")
|
||||
stepId := req.PathParameter("step")
|
||||
|
||||
res, err := h.devopsOperator.SubmitBranchInputStep(projectName, pipelineName, branchName, runId, nodeId, stepId, req.Request)
|
||||
if err != nil {
|
||||
parseErr(err, resp)
|
||||
return
|
||||
var response []byte
|
||||
var err error
|
||||
var ok bool
|
||||
|
||||
if ok, err = h.hasSubmitPermission(req); !ok || err != nil {
|
||||
msg := map[string]string{
|
||||
"allow": "false",
|
||||
"message": fmt.Sprintf("%v", err),
|
||||
}
|
||||
|
||||
response, _ = json.Marshal(msg)
|
||||
} else {
|
||||
response, err = h.devopsOperator.SubmitBranchInputStep(projectName, pipelineName, branchName, runId, nodeId, stepId, req.Request)
|
||||
if err != nil {
|
||||
parseErr(err, resp)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resp.Write(res)
|
||||
resp.Write(response)
|
||||
}
|
||||
|
||||
func (h *ProjectPipelineHandler) GetBranchNodesDetail(req *restful.Request, resp *restful.Response) {
|
||||
@@ -421,6 +551,7 @@ func (h *ProjectPipelineHandler) GetBranchNodesDetail(req *restful.Request, resp
|
||||
parseErr(err, resp)
|
||||
return
|
||||
}
|
||||
h.approvableCheck(res, req)
|
||||
resp.WriteAsJson(res)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
|
||||
"kubesphere.io/kubesphere/pkg/models/devops"
|
||||
"kubesphere.io/kubesphere/pkg/models/iam/am"
|
||||
devopsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/s3"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/sonarqube"
|
||||
@@ -28,16 +29,18 @@ import (
|
||||
type ProjectPipelineHandler struct {
|
||||
devopsOperator devops.DevopsOperator
|
||||
projectCredentialGetter devops.ProjectCredentialGetter
|
||||
amInterface am.AccessManagementInterface
|
||||
}
|
||||
|
||||
type PipelineSonarHandler struct {
|
||||
pipelineSonarGetter devops.PipelineSonarGetter
|
||||
}
|
||||
|
||||
func NewProjectPipelineHandler(devopsClient devopsClient.Interface) ProjectPipelineHandler {
|
||||
func NewProjectPipelineHandler(devopsClient devopsClient.Interface, ksInformers externalversions.SharedInformerFactory, amInterface am.AccessManagementInterface) ProjectPipelineHandler {
|
||||
return ProjectPipelineHandler{
|
||||
devopsOperator: devops.NewDevopsOperator(devopsClient, nil, nil, nil, nil),
|
||||
devopsOperator: devops.NewDevopsOperator(devopsClient, nil, nil, ksInformers, nil),
|
||||
projectCredentialGetter: devops.NewProjectCredentialOperator(devopsClient),
|
||||
amInterface: amInterface,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
|
||||
"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/s3"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/sonarqube"
|
||||
@@ -46,10 +47,10 @@ const (
|
||||
|
||||
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) error {
|
||||
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 {
|
||||
ws := runtime.NewWebService(GroupVersion)
|
||||
|
||||
err := AddPipelineToWebService(ws, devopsClient)
|
||||
err := AddPipelineToWebService(ws, devopsClient, ksInformers, amInterface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -74,12 +75,12 @@ func AddToContainer(container *restful.Container, ksInformers externalversions.S
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddPipelineToWebService(webservice *restful.WebService, devopsClient devops.Interface) error {
|
||||
func AddPipelineToWebService(webservice *restful.WebService, devopsClient devops.Interface, ksInformers externalversions.SharedInformerFactory, amInterface am.AccessManagementInterface) error {
|
||||
|
||||
projectPipelineEnable := devopsClient != nil
|
||||
|
||||
if projectPipelineEnable {
|
||||
projectPipelineHandler := NewProjectPipelineHandler(devopsClient)
|
||||
projectPipelineHandler := NewProjectPipelineHandler(devopsClient, ksInformers, amInterface)
|
||||
|
||||
webservice.Route(webservice.GET("/devops/{devops}/credentials/{credential}/usage").
|
||||
To(projectPipelineHandler.GetProjectCredentialUsage).
|
||||
|
||||
Reference in New Issue
Block a user