Merge pull request #2661 from wansir/devops

fix: devops IAM bugs
This commit is contained in:
KubeSphere CI Bot
2020-07-28 11:05:51 +08:00
committed by GitHub
15 changed files with 307 additions and 85 deletions

View File

@@ -122,7 +122,7 @@ func addControllers(
client.KubeSphere(), devopsClient,
informerFactory.KubernetesSharedInformerFactory().Core().V1().Namespaces(),
informerFactory.KubeSphereSharedInformerFactory().Devops().V1alpha3().DevOpsProjects(),
)
informerFactory.KubeSphereSharedInformerFactory().Tenant().V1alpha1().Workspaces())
devopsPipelineController = pipeline.NewController(client.Kubernetes(),
client.KubeSphere(),

View File

@@ -51,6 +51,9 @@ type Attributes interface {
// The namespace of the object, if a request is for a REST object.
GetNamespace() string
// The devops project of the object, if a request is for a REST object.
GetDevOps() string
// The kind of object, if a request is for a REST object.
GetResource() string
@@ -109,6 +112,7 @@ type AttributesRecord struct {
Cluster string
Workspace string
Namespace string
DevOps string
APIGroup string
APIVersion string
Resource string
@@ -144,6 +148,10 @@ func (a AttributesRecord) GetNamespace() string {
return a.Namespace
}
func (a AttributesRecord) GetDevOps() string {
return a.DevOps
}
func (a AttributesRecord) GetResource() string {
return a.Resource
}

View File

@@ -234,11 +234,21 @@ func (r *RBACAuthorizer) visitRulesFor(requestAttributes authorizer.Attributes,
}
}
if requestAttributes.GetResourceScope() == request.WorkspaceScope || requestAttributes.GetResourceScope() == request.NamespaceScope {
if requestAttributes.GetResourceScope() == request.WorkspaceScope ||
requestAttributes.GetResourceScope() == request.NamespaceScope ||
requestAttributes.GetResourceScope() == request.DevOpsScope {
var workspace string
var err error
// all of resource under namespace and devops belong to workspace
if requestAttributes.GetResourceScope() == request.NamespaceScope {
if workspace, err = r.am.GetControlledWorkspace(requestAttributes.GetNamespace()); err != nil {
if workspace, err = r.am.GetNamespaceControlledWorkspace(requestAttributes.GetNamespace()); err != nil {
if !visitor(nil, "", nil, err) {
return
}
}
} else if requestAttributes.GetResourceScope() == request.DevOpsScope {
if workspace, err = r.am.GetDevOpsControlledWorkspace(requestAttributes.GetDevOps()); err != nil {
if !visitor(nil, "", nil, err) {
return
}
@@ -279,19 +289,33 @@ func (r *RBACAuthorizer) visitRulesFor(requestAttributes authorizer.Attributes,
}
}
if requestAttributes.GetResourceScope() == request.NamespaceScope {
if roleBindings, err := r.am.ListRoleBindings("", requestAttributes.GetNamespace()); err != nil {
if requestAttributes.GetResourceScope() == request.NamespaceScope ||
requestAttributes.GetResourceScope() == request.DevOpsScope {
namespace := requestAttributes.GetNamespace()
// list devops role binding
if requestAttributes.GetResourceScope() == request.DevOpsScope {
if relatedNamespace, err := r.am.GetDevOpsRelatedNamespace(requestAttributes.GetDevOps()); err != nil {
if !visitor(nil, "", nil, err) {
return
}
} else {
namespace = relatedNamespace
}
}
if roleBindings, err := r.am.ListRoleBindings("", namespace); err != nil {
if !visitor(nil, "", nil, err) {
return
}
} else {
sourceDescriber := &roleBindingDescriber{}
for _, roleBinding := range roleBindings {
subjectIndex, applies := appliesTo(requestAttributes.GetUser(), roleBinding.Subjects, requestAttributes.GetNamespace())
subjectIndex, applies := appliesTo(requestAttributes.GetUser(), roleBinding.Subjects, namespace)
if !applies {
continue
}
regoPolicy, rules, err := r.am.GetRoleReferenceRules(roleBinding.RoleRef, requestAttributes.GetNamespace())
regoPolicy, rules, err := r.am.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
if err != nil {
visitor(nil, "", nil, err)
continue

View File

@@ -82,12 +82,12 @@ func getAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error)
attribs.Cluster = requestInfo.Cluster
attribs.Workspace = requestInfo.Workspace
attribs.KubernetesRequest = requestInfo.IsKubernetesRequest
attribs.APIGroup = requestInfo.APIGroup
attribs.APIVersion = requestInfo.APIVersion
attribs.Resource = requestInfo.Resource
attribs.Subresource = requestInfo.Subresource
attribs.Namespace = requestInfo.Namespace
attribs.DevOps = requestInfo.DevOps
attribs.Name = requestInfo.Name
return &attribs, nil

View File

@@ -69,6 +69,9 @@ type RequestInfo struct {
// Cluster of requested resource, this is empty in single-cluster environment
Cluster string
// DevOps project of requested resource
DevOps string
// Scope of requested resource.
ResourceScope string
}
@@ -195,15 +198,6 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
}
}
selector := req.URL.Query().Get("labelSelector")
// URL forms: /api/v1/watch/namespaces?labelSelector=kubesphere.io/workspace=system-workspace
if strings.HasPrefix(selector, workspaceSelectorPrefix) {
workspace := strings.TrimPrefix(selector, workspaceSelectorPrefix)
// URL forms: /api/v1/watch/namespaces?labelSelector=kubesphere.io/workspace==system-workspace
workspace = strings.TrimPrefix(workspace, "=")
requestInfo.Workspace = workspace
}
// URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
if currentParts[0] == "namespaces" {
if len(currentParts) > 1 {
@@ -215,8 +209,19 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
currentParts = currentParts[2:]
}
}
} else if currentParts[0] == "devops" {
if len(currentParts) > 1 {
requestInfo.DevOps = currentParts[1]
// if there is another step after the devops name
// move currentParts to include it as a resource in its own right
if len(currentParts) > 2 {
currentParts = currentParts[2:]
}
}
} else {
requestInfo.Namespace = metav1.NamespaceNone
requestInfo.DevOps = metav1.NamespaceNone
}
// parsing successful, so we now know the proper value for .Parts
@@ -260,6 +265,15 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
requestInfo.Verb = "list"
}
// URL forms: /api/v1/watch/namespaces?labelSelector=kubesphere.io/workspace=system-workspace
if requestInfo.Verb == "watch" {
selector := req.URL.Query().Get("labelSelector")
if strings.HasPrefix(selector, workspaceSelectorPrefix) {
workspace := strings.TrimPrefix(selector, workspaceSelectorPrefix)
requestInfo.Workspace = workspace
}
}
if opts.FieldSelector != nil {
if name, ok := opts.FieldSelector.RequiresExactMatch("metadata.name"); ok {
if len(path.IsValidPathSegmentName(name)) == 0 {
@@ -306,6 +320,7 @@ const (
ClusterScope = "Cluster"
WorkspaceScope = "Workspace"
NamespaceScope = "Namespace"
DevOpsScope = "DevOps"
workspaceSelectorPrefix = constants.WorkspaceLabelKey + "="
)
@@ -318,6 +333,10 @@ func (r *RequestInfoFactory) resolveResourceScope(request RequestInfo) string {
return NamespaceScope
}
if request.DevOps != "" {
return DevOpsScope
}
if request.Workspace != "" {
return WorkspaceScope
}

View File

@@ -18,6 +18,8 @@ import (
"k8s.io/klog"
devopsv1alpha3 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha3"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
tenantv1alpha1informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/tenant/v1alpha1"
tenantv1alpha1listers "kubesphere.io/kubesphere/pkg/client/listers/tenant/v1alpha1"
"kubesphere.io/kubesphere/pkg/constants"
devopsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
@@ -48,6 +50,9 @@ type Controller struct {
namespaceLister corev1lister.NamespaceLister
namespaceSynced cache.InformerSynced
workspaceLister tenantv1alpha1listers.WorkspaceLister
workspaceSynced cache.InformerSynced
workqueue workqueue.RateLimitingInterface
workerLoopPeriod time.Duration
@@ -59,7 +64,8 @@ func NewController(client clientset.Interface,
kubesphereClient kubesphereclient.Interface,
devopsClinet devopsClient.Interface,
namespaceInformer corev1informer.NamespaceInformer,
devopsInformer devopsinformers.DevOpsProjectInformer) *Controller {
devopsInformer devopsinformers.DevOpsProjectInformer,
workspaceInformer tenantv1alpha1informers.WorkspaceInformer) *Controller {
broadcaster := record.NewBroadcaster()
broadcaster.StartLogging(func(format string, args ...interface{}) {
@@ -77,6 +83,8 @@ func NewController(client clientset.Interface,
devOpsProjectSynced: devopsInformer.Informer().HasSynced,
namespaceLister: namespaceInformer.Lister(),
namespaceSynced: namespaceInformer.Informer().HasSynced,
workspaceLister: workspaceInformer.Lister(),
workspaceSynced: workspaceInformer.Informer().HasSynced,
workerLoopPeriod: time.Second,
}
@@ -163,7 +171,7 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}) error {
klog.Info("starting devops project controller")
defer klog.Info("shutting down devops project controller")
if !cache.WaitForCacheSync(stopCh, c.devOpsProjectSynced) {
if !cache.WaitForCacheSync(stopCh, c.devOpsProjectSynced, c.devOpsProjectSynced, c.workspaceSynced) {
return fmt.Errorf("failed to wait for caches to sync")
}
@@ -273,12 +281,18 @@ func (c *Controller) syncHandler(key string) error {
}
if !reflect.DeepEqual(copyProject, project) {
_, err := c.kubesphereClient.DevopsV1alpha3().DevOpsProjects().Update(copyProject)
copyProject, err = c.kubesphereClient.DevopsV1alpha3().DevOpsProjects().Update(copyProject)
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to update ns %s ", key))
return err
}
}
if copyProject, err = c.bindWorkspace(copyProject); err != nil {
klog.Error(err)
return err
}
// Check project exists, otherwise we will create it.
_, err := c.devopsClient.GetDevOpsProject(copyProject.Status.AdminNamespace)
if err != nil {
@@ -312,6 +326,38 @@ func (c *Controller) syncHandler(key string) error {
return nil
}
func (c *Controller) bindWorkspace(project *devopsv1alpha3.DevOpsProject) (*devopsv1alpha3.DevOpsProject, error) {
workspaceName := project.Labels[constants.WorkspaceLabelKey]
if workspaceName == "" {
return project, nil
}
workspace, err := c.workspaceLister.Get(workspaceName)
if err != nil {
// skip if workspace not found
if errors.IsNotFound(err) {
return project, nil
}
klog.Error(err)
return nil, err
}
if !metav1.IsControlledBy(project, workspace) {
project.OwnerReferences = nil
if err := controllerutil.SetControllerReference(workspace, project, scheme.Scheme); err != nil {
klog.Error(err)
return nil, err
}
return c.kubesphereClient.DevopsV1alpha3().DevOpsProjects().Update(project)
}
return project, nil
}
func (c *Controller) deleteDevOpsProjectInDevOps(project *devopsv1alpha3.DevOpsProject) error {
err := c.devopsClient.DeleteDevOpsProject(project.Status.AdminNamespace)
@@ -330,9 +376,16 @@ func (c *Controller) generateNewNamespace(project *devopsv1alpha3.DevOpsProject)
},
ObjectMeta: metav1.ObjectMeta{
GenerateName: project.Name,
Labels: map[string]string{constants.DevOpsProjectLabelKey: project.Name},
Labels: map[string]string{
constants.DevOpsProjectLabelKey: project.Name,
},
},
}
if creator := project.Annotations[constants.CreatorAnnotationKey]; creator != "" {
ns.Annotations = map[string]string{constants.CreatorAnnotationKey: creator}
}
controllerutil.SetControllerReference(project, ns, scheme.Scheme)
return ns
}

View File

@@ -120,8 +120,10 @@ func (f *fixture) newController() (*Controller, informers.SharedInformerFactory,
k8sI := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc())
dI := fakeDevOps.New(f.initDevOpsProject...)
c := NewController(f.kubeclient, f.client, dI, k8sI.Core().V1().Namespaces(),
i.Devops().V1alpha3().DevOpsProjects())
c := NewController(f.kubeclient, f.client, dI,
k8sI.Core().V1().Namespaces(),
i.Devops().V1alpha3().DevOpsProjects(),
i.Tenant().V1alpha1().Workspaces())
c.devOpsProjectSynced = alwaysReady
c.eventRecorder = &record.FakeRecorder{}
@@ -251,7 +253,9 @@ func filterInformerActions(actions []core.Action) []core.Action {
(action.Matches("list", devopsprojects.ResourcePluralDevOpsProject) ||
action.Matches("watch", devopsprojects.ResourcePluralDevOpsProject) ||
action.Matches("list", "namespaces") ||
action.Matches("watch", "namespaces")) {
action.Matches("watch", "namespaces") ||
action.Matches("watch", "workspaces") ||
action.Matches("list", "workspaces")) {
continue
}
ret = append(ret, action)

View File

@@ -229,17 +229,16 @@ func (c *Controller) reconcile(key string) error {
}
if globalRoleBinding.RoleRef.Name == iamv1alpha2.PlatformAdmin {
if err := c.relateToClusterAdmin(globalRoleBinding); err != nil {
if err := c.assignClusterAdminRole(globalRoleBinding); err != nil {
klog.Error(err)
return err
}
if c.devopsClient != nil {
username := findExpectUsername(globalRoleBinding)
err = c.devopsClient.AssignGlobalRole(modeldevops.JenkinsAdminRoleName, username)
if err != nil {
klog.Errorf("%+v", err)
return err
}
}
if c.devopsClient != nil {
if err := c.assignDevOpsAdminRole(globalRoleBinding); err != nil {
klog.Error(err)
return err
}
}
@@ -299,11 +298,9 @@ func (c *Controller) multiClusterSync(globalRoleBinding *iamv1alpha2.GlobalRoleB
return nil
}
func (c *Controller) relateToClusterAdmin(globalRoleBinding *iamv1alpha2.GlobalRoleBinding) error {
func (c *Controller) assignClusterAdminRole(globalRoleBinding *iamv1alpha2.GlobalRoleBinding) error {
username := findExpectUsername(globalRoleBinding)
// unexpected
if username == "" {
return nil
}
@@ -436,6 +433,16 @@ func (c *Controller) ensureNotControlledByKubefed(globalRoleBinding *iamv1alpha2
return nil
}
func (c *Controller) assignDevOpsAdminRole(globalRoleBinding *iamv1alpha2.GlobalRoleBinding) error {
if username := findExpectUsername(globalRoleBinding); username != "" {
if err := c.devopsClient.AssignGlobalRole(modeldevops.JenkinsAdminRoleName, username); err != nil {
klog.Errorf("%+v", err)
return err
}
}
return nil
}
func ensureSubjectAPIVersionIsValid(subjects []rbacv1.Subject) []rbacv1.Subject {
validSubjects := make([]rbacv1.Subject, 0)
for _, subject := range subjects {

View File

@@ -5,8 +5,6 @@ import (
"github.com/emicklei/go-restful"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
@@ -1079,7 +1077,7 @@ func (h *iamHandler) resolveNamespace(namespace string, devops string) (string,
if devops == "" {
return namespace, nil
}
return h.am.GetControlledNamespace(devops)
return h.am.GetDevOpsRelatedNamespace(devops)
}
func (h *iamHandler) PatchWorkspaceRole(request *restful.Request, response *restful.Response) {
@@ -1213,18 +1211,7 @@ func (h *iamHandler) updateGlobalRoleBinding(operator user.Info, user *iamv1alph
func (h *iamHandler) ListUserLoginRecords(request *restful.Request, response *restful.Response) {
username := request.PathParameter("user")
queryParam := query.ParseQueryParameter(request)
selector, _ := labels.Parse(queryParam.LabelSelector)
if selector == nil {
selector = labels.NewSelector()
}
requirement, err := labels.NewRequirement(iamv1alpha2.UserReferenceLabel, selection.Equals, []string{username})
if err != nil {
klog.Error(err)
handleError(request, response, err)
return
}
selector.Add(*requirement)
queryParam.LabelSelector = selector.String()
queryParam.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", iamv1alpha2.UserReferenceLabel, username))
result, err := h.im.ListLoginRecords(queryParam)
if err != nil {
klog.Error(err)

View File

@@ -290,6 +290,7 @@ func (h *handler) passwordGrant(username string, password string, req *restful.R
return
default:
response.WriteError(http.StatusInternalServerError, apierrors.NewInternalError(err))
return
}
}

View File

@@ -101,6 +101,29 @@ func (h *tenantHandler) ListNamespaces(req *restful.Request, resp *restful.Respo
resp.WriteEntity(result)
}
func (h *tenantHandler) ListDevOpsProjects(req *restful.Request, resp *restful.Response) {
user, ok := request.UserFrom(req.Request.Context())
queryParam := query.ParseQueryParameter(req)
if !ok {
err := fmt.Errorf("cannot obtain user info")
klog.Errorln(err)
api.HandleForbidden(resp, nil, err)
return
}
workspace := req.PathParameter("workspace")
result, err := h.tenant.ListDevOpsProjects(user, workspace, queryParam)
if err != nil {
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteEntity(result)
}
func (h *tenantHandler) CreateNamespace(request *restful.Request, response *restful.Response) {
workspace := request.PathParameter("workspace")
var namespace corev1.Namespace

View File

@@ -27,7 +27,6 @@ import (
eventsv1alpha1 "kubesphere.io/kubesphere/pkg/api/events/v1alpha1"
loggingv1alpha2 "kubesphere.io/kubesphere/pkg/api/logging/v1alpha2"
tenantv1alpha2 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha2"
typesv1beta1 "kubesphere.io/kubesphere/pkg/apis/types/v1beta1"
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/constants"
@@ -105,31 +104,37 @@ func AddToContainer(c *restful.Container, factory informers.InformerFactory, k8s
To(handler.ListNamespaces).
Param(ws.PathParameter("workspace", "workspace name")).
Doc("List the namespaces for the current user").
Returns(http.StatusOK, api.StatusOK, []corev1.Namespace{}).
Returns(http.StatusOK, api.StatusOK, api.ListResult{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/federatednamespaces").
To(handler.ListFederatedNamespaces).
Param(ws.PathParameter("workspace", "workspace name")).
Doc("List the federated namespaces for the current user").
Returns(http.StatusOK, api.StatusOK, []typesv1beta1.FederatedNamespace{}).
Returns(http.StatusOK, api.StatusOK, api.ListResult{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/workspaces/{workspace}/federatednamespaces").
To(handler.ListFederatedNamespaces).
Param(ws.PathParameter("workspace", "workspace name")).
Doc("List the federated namespaces of the specified workspace for the current user").
Returns(http.StatusOK, api.StatusOK, []typesv1beta1.FederatedNamespace{}).
Returns(http.StatusOK, api.StatusOK, api.ListResult{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/workspaces/{workspace}/namespaces").
To(handler.ListNamespaces).
Param(ws.PathParameter("workspace", "workspace name")).
Doc("List the namespaces of the specified workspace for the current user").
Returns(http.StatusOK, api.StatusOK, []corev1.Namespace{}).
Returns(http.StatusOK, api.StatusOK, api.ListResult{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/workspaces/{workspace}/devops").
To(handler.ListDevOpsProjects).
Param(ws.PathParameter("workspace", "workspace name")).
Doc("List the devops projects of the specified workspace for the current user").
Returns(http.StatusOK, api.StatusOK, api.ListResult{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/workspaces/{workspace}/namespaces/{namespace}").
To(handler.DescribeNamespace).
Param(ws.PathParameter("workspace", "workspace name")).
Doc("Retrieve namespace details.").
Returns(http.StatusOK, api.StatusOK, []corev1.Namespace{}).
Returns(http.StatusOK, api.StatusOK, corev1.Namespace{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.DELETE("/workspaces/{workspace}/namespaces/{namespace}").
To(handler.DeleteNamespace).
@@ -142,20 +147,20 @@ func AddToContainer(c *restful.Container, factory informers.InformerFactory, k8s
Param(ws.PathParameter("workspace", "workspace name")).
Doc("List the namespaces of the specified workspace for the current user").
Reads(corev1.Namespace{}).
Returns(http.StatusOK, api.StatusOK, []corev1.Namespace{}).
Returns(http.StatusOK, api.StatusOK, corev1.Namespace{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.PUT("/workspaces/{workspace}/namespaces/{namespace}").
To(handler.UpdateNamespace).
Param(ws.PathParameter("workspace", "workspace name")).
Reads(corev1.Namespace{}).
Returns(http.StatusOK, api.StatusOK, []corev1.Namespace{}).
Returns(http.StatusOK, api.StatusOK, corev1.Namespace{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.PATCH("/workspaces/{workspace}/namespaces/{namespace}").
To(handler.PatchNamespace).
Consumes(mimePatch...).
Param(ws.PathParameter("workspace", "workspace name")).
Reads(corev1.Namespace{}).
Returns(http.StatusOK, api.StatusOK, []corev1.Namespace{}).
Returns(http.StatusOK, api.StatusOK, corev1.Namespace{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/events").

View File

@@ -70,8 +70,9 @@ type AccessManagementInterface interface {
RemoveUserFromNamespace(username string, namespace string) error
CreateClusterRoleBinding(username string, role string) error
RemoveUserFromCluster(username string) error
GetControlledNamespace(devops string) (string, error)
GetControlledWorkspace(namespace string) (string, error)
GetDevOpsRelatedNamespace(devops string) (string, error)
GetNamespaceControlledWorkspace(namespace string) (string, error)
GetDevOpsControlledWorkspace(devops string) (string, error)
PatchNamespaceRole(namespace string, role *rbacv1.Role) (*rbacv1.Role, error)
PatchClusterRole(clusterRole *rbacv1.ClusterRole) (*rbacv1.ClusterRole, error)
}
@@ -279,23 +280,19 @@ func (am *amOperator) ListGlobalRoleBindings(username string) ([]*iamv1alpha2.Gl
}
func (am *amOperator) ListRoleBindings(username, namespace string) ([]*rbacv1.RoleBinding, error) {
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralRoleBinding, namespace, query.New())
if err != nil {
klog.Error(err)
return nil, err
}
result := make([]*rbacv1.RoleBinding, 0)
for _, obj := range roleBindings.Items {
roleBinding := obj.(*rbacv1.RoleBinding)
if contains(roleBinding.Subjects, username) {
result = append(result, roleBinding)
}
}
return result, nil
}
@@ -964,7 +961,7 @@ func (am *amOperator) GetClusterRole(name string) (*rbacv1.ClusterRole, error) {
}
return obj.(*rbacv1.ClusterRole), nil
}
func (am *amOperator) GetControlledNamespace(devops string) (string, error) {
func (am *amOperator) GetDevOpsRelatedNamespace(devops string) (string, error) {
obj, err := am.resourceGetter.Get(devopsv1alpha3.ResourcePluralDevOpsProject, "", devops)
if err != nil {
klog.Error(err)
@@ -975,7 +972,17 @@ func (am *amOperator) GetControlledNamespace(devops string) (string, error) {
return devopsProject.Status.AdminNamespace, nil
}
func (am *amOperator) GetControlledWorkspace(namespace string) (string, error) {
func (am *amOperator) GetDevOpsControlledWorkspace(devops string) (string, error) {
obj, err := am.resourceGetter.Get(devopsv1alpha3.ResourcePluralDevOpsProject, "", devops)
if err != nil {
klog.Error(err)
return "", err
}
devopsProject := obj.(*devopsv1alpha3.DevOpsProject)
return devopsProject.Labels[tenantv1alpha1.WorkspaceLabel], nil
}
func (am *amOperator) GetNamespaceControlledWorkspace(namespace string) (string, error) {
obj, err := am.resourceGetter.Get("namespaces", "", namespace)
if err != nil {
if errors.IsNotFound(err) {

View File

@@ -17,26 +17,109 @@ limitations under the License.
package tenant
import (
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/server/params"
dsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
devopsv1alpha3 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha3"
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/apiserver/query"
"kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/constants"
resources "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
)
type DevOpsProjectLister interface {
ListDevOpsProjects(workspace string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error)
}
type devopsProjectLister struct {
dsProject dsClient.ProjectOperator
}
func newProjectLister(client dsClient.ProjectOperator) DevOpsProjectLister {
return &devopsProjectLister{
dsProject: client,
func (t *tenantOperator) ListDevOpsProjects(user user.Info, workspace string, queryParam *query.Query) (*api.ListResult, error) {
scope := request.ClusterScope
if workspace != "" {
scope = request.WorkspaceScope
}
}
func (o *devopsProjectLister) ListDevOpsProjects(workspace string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error) {
//TODO: @runzexia use informer to impl it
return nil, nil
listDevOps := authorizer.AttributesRecord{
User: user,
Verb: "list",
Workspace: workspace,
Resource: "devops",
ResourceRequest: true,
ResourceScope: scope,
}
decision, _, err := t.authorizer.Authorize(listDevOps)
if err != nil {
klog.Error(err)
return nil, err
}
// allowed list devops in the specified scope
if decision == authorizer.DecisionAllow {
// filter by workspace
if workspace != "" {
queryParam.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", tenantv1alpha1.WorkspaceLabel, workspace))
}
result, err := t.resourceGetter.List(devopsv1alpha3.ResourcePluralDevOpsProject, "", queryParam)
if err != nil {
klog.Error(err)
return nil, err
}
return result, nil
}
roleBindings, err := t.am.ListRoleBindings(user.GetName(), "")
if err != nil {
klog.Error(err)
return nil, err
}
devopsProjects := make([]runtime.Object, 0)
for _, roleBinding := range roleBindings {
obj, err := t.resourceGetter.Get("namespaces", "", roleBinding.Namespace)
if err != nil {
klog.Error(err)
return nil, err
}
namespace := obj.(*corev1.Namespace)
controlledDevOpsProject := namespace.Labels[constants.DevOpsProjectLabelKey]
// skip if not controlled by devops project
if controlledDevOpsProject == "" {
continue
}
devopsProject, err := t.resourceGetter.Get(devopsv1alpha3.ResourcePluralDevOpsProject, "", controlledDevOpsProject)
if err != nil {
if errors.IsNotFound(err) {
klog.Warning("orphan devops project found ", devopsProject)
continue
}
klog.Error(err)
return nil, err
}
// skip if not controlled by the specified workspace
if workspace != "" &&
devopsProject.(*devopsv1alpha3.DevOpsProject).Labels[tenantv1alpha1.WorkspaceLabel] != workspace {
continue
}
if !contains(devopsProjects, devopsProject) {
devopsProjects = append(devopsProjects, namespace)
}
}
result := resources.DefaultList(devopsProjects, queryParam, func(left runtime.Object, right runtime.Object, field query.Field) bool {
return resources.DefaultObjectMetaCompare(left.(*devopsv1alpha3.DevOpsProject).ObjectMeta, right.(*devopsv1alpha3.DevOpsProject).ObjectMeta, field)
}, func(object runtime.Object, filter query.Filter) bool {
devopsProject := object.(*devopsv1alpha3.DevOpsProject).ObjectMeta
if workspace != "" {
if workspaceLabel, ok := devopsProject.Labels[tenantv1alpha1.WorkspaceLabel]; !ok || workspaceLabel != workspace {
return false
}
}
return resources.DefaultObjectMetaFilter(devopsProject, filter)
})
return result, nil
}

View File

@@ -60,6 +60,7 @@ import (
type Interface interface {
ListWorkspaces(user user.Info, query *query.Query) (*api.ListResult, error)
ListNamespaces(user user.Info, workspace string, query *query.Query) (*api.ListResult, error)
ListDevOpsProjects(user user.Info, workspace string, query *query.Query) (*api.ListResult, error)
ListFederatedNamespaces(info user.Info, workspace string, param *query.Query) (*api.ListResult, error)
CreateNamespace(workspace string, namespace *corev1.Namespace) (*corev1.Namespace, error)
CreateWorkspace(workspace *tenantv1alpha2.WorkspaceTemplate) (*tenantv1alpha2.WorkspaceTemplate, error)