From cd239ce7a4ac88d403ee7cac6ff13f73c91469ac Mon Sep 17 00:00:00 2001 From: Michal Vala Date: Wed, 2 Dec 2020 18:03:11 +0100 Subject: [PATCH] Gh18399 che SA cluster roles (#543) Signed-off-by: Michal Vala Co-authored-by: Anatolii Bazko --- deploy/cluster_role.yaml | 1 + deploy/crds/org_v1_che_cr.yaml | 5 + deploy/crds/org_v1_che_crd.yaml | 6 + .../che-operator.clusterserviceversion.yaml | 2 + .../manifests/org_v1_che_crd.yaml | 6 + .../che-operator.clusterserviceversion.yaml | 2 + .../manifests/org_v1_che_crd.yaml | 6 + pkg/apis/org/v1/che_types.go | 4 + pkg/controller/che/che_controller.go | 19 +++ pkg/deploy/clusterrolebinding.go | 110 ++++++++++++++++++ pkg/deploy/rolebinding.go | 8 +- 11 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 pkg/deploy/clusterrolebinding.go diff --git a/deploy/cluster_role.yaml b/deploy/cluster_role.yaml index b6fd11525..587f0ca44 100644 --- a/deploy/cluster_role.yaml +++ b/deploy/cluster_role.yaml @@ -60,3 +60,4 @@ rules: - list - create - watch + - update diff --git a/deploy/crds/org_v1_che_cr.yaml b/deploy/crds/org_v1_che_cr.yaml index 6786db8b8..5f313d470 100644 --- a/deploy/crds/org_v1_che_cr.yaml +++ b/deploy/crds/org_v1_che_cr.yaml @@ -29,6 +29,11 @@ spec: # defaults to `che`. When set to `codeready`, CodeReady Workspaces is deployed # the difference is in images, labels, exec commands cheFlavor: '' + # Comma-separated list of ClusterRoles that will be assigned + # to che ServiceAccount. By default it is set to `che-namespace-editor`, which is used by Che + # to label the namespaces. Set it empty to not grant any cluster-wide permissions to Che ServiceAccount. + # Be aware that che-operator has to already have all permissions in these ClusterRoles to be able to grant them. + cheClusterRoles: 'che-namespace-editor' # specifies a custom cluster role to user for the Che workspaces # Uses the default roles if left blank. cheWorkspaceClusterRole: '' diff --git a/deploy/crds/org_v1_che_crd.yaml b/deploy/crds/org_v1_che_crd.yaml index 0fc752d8d..64ddb304e 100644 --- a/deploy/crds/org_v1_che_crd.yaml +++ b/deploy/crds/org_v1_che_crd.yaml @@ -310,6 +310,12 @@ spec: to configured true without OAuth configured. This property is also used by the OpenShift infra. type: boolean + cheClusterRoles: + description: Comma-separated list of ClusterRoles that will be assigned + to che ServiceAccount. Be aware that che-operator has to already + have all permissions in these ClusterRoles to be able to grant + them. + type: string cheDebug: description: Enables the debug mode for Che server. Defaults to `false`. diff --git a/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/che-operator.clusterserviceversion.yaml b/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/che-operator.clusterserviceversion.yaml index f6d0e281e..35f08b514 100644 --- a/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/che-operator.clusterserviceversion.yaml +++ b/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/che-operator.clusterserviceversion.yaml @@ -45,6 +45,7 @@ metadata: }, "server": { "allowUserDefinedWorkspaceNamespaces": false, + "cheClusterRoles": "che-namespace-editor", "cheFlavor": "", "cheImage": "", "cheImageTag": "", @@ -272,6 +273,7 @@ spec: - list - create - watch + - update serviceAccountName: che-operator - rules: - apiGroups: diff --git a/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml b/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml index 0fc752d8d..64ddb304e 100644 --- a/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml +++ b/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml @@ -310,6 +310,12 @@ spec: to configured true without OAuth configured. This property is also used by the OpenShift infra. type: boolean + cheClusterRoles: + description: Comma-separated list of ClusterRoles that will be assigned + to che ServiceAccount. Be aware that che-operator has to already + have all permissions in these ClusterRoles to be able to grant + them. + type: string cheDebug: description: Enables the debug mode for Che server. Defaults to `false`. diff --git a/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/che-operator.clusterserviceversion.yaml b/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/che-operator.clusterserviceversion.yaml index 8b16db26b..caf1dd0f2 100644 --- a/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/che-operator.clusterserviceversion.yaml +++ b/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/che-operator.clusterserviceversion.yaml @@ -37,6 +37,7 @@ metadata: }, "server": { "allowUserDefinedWorkspaceNamespaces": false, + "cheClusterRoles": "che-namespace-editor", "cheFlavor": "", "cheImage": "", "cheImageTag": "", @@ -288,6 +289,7 @@ spec: - list - create - watch + - update serviceAccountName: che-operator - rules: - apiGroups: diff --git a/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml b/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml index b202b5db0..b22f5397e 100644 --- a/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml +++ b/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml @@ -311,6 +311,12 @@ spec: to configured true without OAuth configured. This property is also used by the OpenShift infra. type: boolean + cheClusterRoles: + description: Comma-separated list of ClusterRoles that will be assigned + to che ServiceAccount. Be aware that che-operator has to already + have all permissions in these ClusterRoles to be able to grant + them. + type: string cheDebug: description: Enables the debug mode for Che server. Defaults to `false`. diff --git a/pkg/apis/org/v1/che_types.go b/pkg/apis/org/v1/che_types.go index 68b6b4a4d..0c84b2327 100644 --- a/pkg/apis/org/v1/che_types.go +++ b/pkg/apis/org/v1/che_types.go @@ -99,6 +99,10 @@ type CheClusterSpecServer struct { // Enables the debug mode for Che server. Defaults to `false`. // +optional CheDebug string `json:"cheDebug,omitempty"` + // Comma-separated list of ClusterRoles that will be assigned to che ServiceAccount. + // Be aware that che-operator has to already have all permissions in these ClusterRoles to be able to grant them. + // +optional + CheClusterRoles string `json:"cheClusterRoles,omitempty"` // Custom cluster role bound to the user for the Che workspaces. // The default roles are used if this is omitted or left blank. // +optional diff --git a/pkg/controller/che/che_controller.go b/pkg/controller/che/che_controller.go index b3a9c3f92..801aef247 100644 --- a/pkg/controller/che/che_controller.go +++ b/pkg/controller/che/che_controller.go @@ -15,6 +15,7 @@ import ( "context" "fmt" "strconv" + "strings" "time" orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" @@ -593,6 +594,24 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e } } + if len(instance.Spec.Server.CheClusterRoles) > 0 { + cheClusterRoles := strings.Split(instance.Spec.Server.CheClusterRoles, ",") + for _, cheClusterRole := range cheClusterRoles { + cheClusterRole := strings.TrimSpace(cheClusterRole) + cheClusterRoleBindingName := instance.Namespace + "-che-" + cheClusterRole + cheClusterRoleBinding, err := deploy.SyncClusterRoleBindingToCluster(deployContext, cheClusterRoleBindingName, "che", cheClusterRole) + if cheClusterRoleBinding == nil { + logrus.Infof("Waiting on cluster role binding '%s' to be created", cheClusterRoleBindingName) + if err != nil { + logrus.Error(err) + } + if !tests { + return reconcile.Result{RequeueAfter: time.Second}, err + } + } + } + } + cheWSExecRoleBinding, err := deploy.SyncRoleBindingToCluster(deployContext, "che-workspace-exec", "che-workspace", "exec", "Role") if cheWSExecRoleBinding == nil { logrus.Info("Waiting on role binding 'che-workspace-exec' to be created") diff --git a/pkg/deploy/clusterrolebinding.go b/pkg/deploy/clusterrolebinding.go new file mode 100644 index 000000000..aa0b8366b --- /dev/null +++ b/pkg/deploy/clusterrolebinding.go @@ -0,0 +1,110 @@ +// +// Copyright (c) 2012-2019 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// +package deploy + +import ( + "context" + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sirupsen/logrus" + rbac "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + runtimeClient "sigs.k8s.io/controller-runtime/pkg/client" +) + +var crbDiffOpts = cmp.Options{ + cmpopts.IgnoreFields(rbac.ClusterRoleBinding{}, "TypeMeta", "ObjectMeta"), +} + +func SyncClusterRoleBindingToCluster( + deployContext *DeployContext, + name string, + serviceAccountName string, + clusterRoleName string) (*rbac.ClusterRoleBinding, error) { + + specCRB, err := getSpecClusterRoleBinding(deployContext, name, serviceAccountName, clusterRoleName) + if err != nil { + return nil, err + } + + clusterRB, err := getClusterRoleBiding(specCRB.Name, deployContext.ClusterAPI.Client) + if err != nil { + return nil, err + } + + if clusterRB == nil { + logrus.Infof("Creating a new object: %s, name %s", specCRB.Kind, specCRB.Name) + err := deployContext.ClusterAPI.Client.Create(context.TODO(), specCRB) + return nil, err + } + + diff := cmp.Diff(clusterRB, specCRB, crbDiffOpts) + if len(diff) > 0 { + logrus.Infof("Updating existed object: %s, name: %s", clusterRB.Kind, clusterRB.Name) + fmt.Printf("Difference:\n%s", diff) + clusterRB.Subjects = specCRB.Subjects + clusterRB.RoleRef = specCRB.RoleRef + err := deployContext.ClusterAPI.Client.Update(context.TODO(), clusterRB) + return clusterRB, err + } + + return clusterRB, nil +} + +func getClusterRoleBiding(name string, client runtimeClient.Client) (*rbac.ClusterRoleBinding, error) { + clusterRoleBinding := &rbac.ClusterRoleBinding{} + crbName := types.NamespacedName{Name: name} + err := client.Get(context.TODO(), crbName, clusterRoleBinding) + if err != nil { + if errors.IsNotFound(err) { + return nil, nil + } + return nil, err + } + return clusterRoleBinding, nil +} + +func getSpecClusterRoleBinding( + deployContext *DeployContext, + name string, + serviceAccountName string, + roleName string) (*rbac.ClusterRoleBinding, error) { + + labels := GetLabels(deployContext.CheCluster, DefaultCheFlavor(deployContext.CheCluster)) + clusterRoleBinding := &rbac.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRoleBinding", + APIVersion: rbac.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: labels, + }, + Subjects: []rbac.Subject{ + { + Kind: rbac.ServiceAccountKind, + Name: serviceAccountName, + Namespace: deployContext.CheCluster.Namespace, + }, + }, + RoleRef: rbac.RoleRef{ + Name: roleName, + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + }, + } + + return clusterRoleBinding, nil +} diff --git a/pkg/deploy/rolebinding.go b/pkg/deploy/rolebinding.go index 4cc377f0f..5c285c470 100644 --- a/pkg/deploy/rolebinding.go +++ b/pkg/deploy/rolebinding.go @@ -35,21 +35,21 @@ func SyncRoleBindingToCluster( return nil, err } - clusterRB, err := getClusterRoleBiding(specRB.Name, specRB.Namespace, deployContext.ClusterAPI.Client) + roleBinding, err := getRoleBiding(specRB.Name, specRB.Namespace, deployContext.ClusterAPI.Client) if err != nil { return nil, err } - if clusterRB == nil { + if roleBinding == nil { logrus.Infof("Creating a new object: %s, name %s", specRB.Kind, specRB.Name) err := deployContext.ClusterAPI.Client.Create(context.TODO(), specRB) return nil, err } - return clusterRB, nil + return roleBinding, nil } -func getClusterRoleBiding(name string, namespace string, client runtimeClient.Client) (*rbac.RoleBinding, error) { +func getRoleBiding(name string, namespace string, client runtimeClient.Client) (*rbac.RoleBinding, error) { roleBinding := &rbac.RoleBinding{} namespacedName := types.NamespacedName{ Namespace: namespace,