diff --git a/deploy/crds/org_v1_che_cr.yaml b/deploy/crds/org_v1_che_cr.yaml index 7fcba80b6..33db9c8d8 100644 --- a/deploy/crds/org_v1_che_cr.yaml +++ b/deploy/crds/org_v1_che_cr.yaml @@ -1,3 +1,4 @@ + # # Copyright (c) 2012-2019 Red Hat, Inc. # This program and the accompanying materials are made @@ -22,6 +23,9 @@ spec: # defaults to `che`. When set to `codeready`, CodeReady Workspaces is deployed # the difference is in images, labels, exec commands cheFlavor: '' + # specifies a custom cluster role to user for the Che workspaces + # Uses the default roles if left blank. + cheWorkspaceClusterRole: '' # when set to true the operator will attempt to get a secret in OpenShift router namespace # to add it to Java trust store of Che server. Requires cluster-admin privileges for operator service account selfSignedCert: diff --git a/pkg/apis/org/v1/che_types.go b/pkg/apis/org/v1/che_types.go index 324190d08..d800685cf 100644 --- a/pkg/apis/org/v1/che_types.go +++ b/pkg/apis/org/v1/che_types.go @@ -22,11 +22,11 @@ import ( type CheClusterSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file - Server CheClusterSpecServer `json:"server"` - Database CheClusterSpecDB `json:"database"` - Auth CheClusterSpecAuth `json:"auth"` - Storage CheClusterSpecStorage `json:"storage"` - K8SOnly CheClusterSpecK8SOnly `json:"k8s"` + Server CheClusterSpecServer `json:"server"` + Database CheClusterSpecDB `json:"database"` + Auth CheClusterSpecAuth `json:"auth"` + Storage CheClusterSpecStorage `json:"storage"` + K8SOnly CheClusterSpecK8SOnly `json:"k8s"` } type CheClusterSpecServer struct { @@ -42,6 +42,9 @@ type CheClusterSpecServer struct { CheLogLevel string `json:"cheLogLevel"` // CheDebug is debug mode for Che server. Defaults to false CheDebug string `json:"cheDebug"` + // CustomClusterRoleName specifies a custom cluster role to user for the Che workspaces + // The default roles are used if this is left blank. + CheWorkspaceClusterRole string `json:"cheWorkspaceClusterRole"` // SelfSignedCert signal about the necessity to get OpenShift router tls secret // and extract certificate to add it to Java trust store for Che server SelfSignedCert bool `json:"selfSignedCert"` diff --git a/pkg/controller/che/che_controller.go b/pkg/controller/che/che_controller.go index 5cab4d0b7..d8ab9f8ab 100644 --- a/pkg/controller/che/che_controller.go +++ b/pkg/controller/che/che_controller.go @@ -281,6 +281,17 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e if err := r.CreateNewRoleBinding(instance, viewRoleBinding); err != nil { return reconcile.Result{}, err } + + // If the user specified an additional cluster role to use for the Che workspace, create a role binding for it + // Use a role binding instead of a cluster role binding to keep the additional access scoped to the workspace's namespace + workspaceClusterRole := instance.Spec.Server.CheWorkspaceClusterRole + if workspaceClusterRole != "" { + customRoleBinding := deploy.NewRoleBinding(instance, "che-workspace-custom", workspaceServiceAccount.Name, workspaceClusterRole, "ClusterRole") + if err = r.CreateNewRoleBinding(instance, customRoleBinding); err != nil { + return reconcile.Result{}, err + } + } + if err := r.GenerateAndSaveFields(instance, request); err != nil { instance, _ = r.GetCR(request) return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err @@ -474,7 +485,7 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e if deployment.Spec.Template.Spec.Containers[0].Image != instance.Spec.Auth.KeycloakImage { keycloakDeployment := deploy.NewKeycloakDeployment(instance, keycloakPostgresPassword, keycloakAdminPassword, cheFlavor) logrus.Infof("Updating Keycloak deployment with an image %s", instance.Spec.Auth.KeycloakImage) - if err := r.client.Update(context.TODO(),keycloakDeployment); err!= nil { + if err := r.client.Update(context.TODO(), keycloakDeployment); err != nil { logrus.Errorf("Failed to update Keycloak deployment: %s", err) } @@ -487,8 +498,6 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e } } - - if isOpenShift { doInstallOpenShiftoAuthProvider := instance.Spec.Auth.OpenShiftOauth if doInstallOpenShiftoAuthProvider { @@ -685,7 +694,7 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e if err := controllerutil.SetControllerReference(instance, cheDeployment, r.scheme); err != nil { logrus.Errorf("An error occurred: %s", err) } - logrus.Infof("Updating deployment %s with new memory settings. Request: %s, limit: %s",cheDeployment.Name, desiredRequest, desiredLimit) + logrus.Infof("Updating deployment %s with new memory settings. Request: %s, limit: %s", cheDeployment.Name, desiredRequest, desiredLimit) if err := r.client.Update(context.TODO(), cheDeployment); err != nil { logrus.Errorf("Failed to update deployment: %s", err) return reconcile.Result{}, err diff --git a/pkg/controller/che/che_controller_test.go b/pkg/controller/che/che_controller_test.go index 4a61be107..de35e466d 100644 --- a/pkg/controller/che/che_controller_test.go +++ b/pkg/controller/che/che_controller_test.go @@ -13,12 +13,15 @@ package che import ( "context" + "time" + orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" oauth "github.com/openshift/api/oauth/v1" routev1 "github.com/openshift/api/route/v1" "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + rbacapi "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -26,7 +29,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" - "time" "testing" ) @@ -63,6 +65,9 @@ func TestCheController(t *testing.T) { }, Spec: orgv1.CheClusterSpec{ // todo add some spec to check controller ifs like external db, ssl etc + Server: orgv1.CheClusterSpecServer{ + CheWorkspaceClusterRole: "cluster-admin", + }, }, } // Objects to track in the fake client. @@ -121,6 +126,12 @@ func TestCheController(t *testing.T) { t.Errorf("ConfigMap %s not found: %s", cm.Name, err) } + // Get the custom role binding that should have been created for the role we passed in + rb := &rbacapi.RoleBinding{} + if err := cl.Get(context.TODO(), types.NamespacedName{Name: "che-workspace-custom", Namespace: cheCR.Namespace}, rb); err != nil { + t.Errorf("Custom role binding %s not found: %s", rb.Name, err) + } + // run a few checks to make sure the operator reconciled tls routes and updated configmap if cm.Data["CHE_INFRA_OPENSHIFT_TLS__ENABLED"] != "true" { t.Errorf("ConfigMap wasn't updated. Extecting true, got: %s", cm.Data["CHE_INFRA_OPENSHIFT_TLS__ENABLED"]) @@ -207,7 +218,7 @@ func TestCheController(t *testing.T) { } actualStorageClassName := pvc.Spec.StorageClassName if len(*actualStorageClassName) != len(fakeStorageClassName) { - t.Fatalf("Expecting %s storageClassName, got %s", fakeStorageClassName, *actualStorageClassName ) + t.Fatalf("Expecting %s storageClassName, got %s", fakeStorageClassName, *actualStorageClassName) } // check if oAuthClient is deleted after CR is deleted (finalizer logic)