diff --git a/deploy/cluster_role.yaml b/deploy/cluster_role.yaml index b688df8dd..7feb23223 100644 --- a/deploy/cluster_role.yaml +++ b/deploy/cluster_role.yaml @@ -636,31 +636,13 @@ rules: - update - watch - apiGroups: - - che.eclipse.org + - org.eclipse.che resources: - - '*' + - checlusters + - checlusters/status + - checlusters/finalizers verbs: - '*' - - apiGroups: - - che.eclipse.org - resources: - - chemanagers - verbs: - - '*' - - apiGroups: - - che.eclipse.org - resources: - - chemanagers/status - verbs: - - get - - patch - - update - - apiGroups: - - che.eclipse.org - resources: - - chemanagers/finalizers - verbs: - - update - apiGroups: - controller.devfile.io resources: diff --git a/deploy/crds/org_v1_che_crd-v1beta1.yaml b/deploy/crds/org_v1_che_crd-v1beta1.yaml index 0c3a0bbc4..846f643dd 100644 --- a/deploy/crds/org_v1_che_crd-v1beta1.yaml +++ b/deploy/crds/org_v1_che_crd-v1beta1.yaml @@ -61,6 +61,20 @@ spec: details about the external identity provider you are about to use. See also all the other fields starting with: `identityProvider`.' type: boolean + gatewayAuthenticationSidecarImage: + description: Gateway sidecar responsible for authentication when + NativeUserMode is enabled. See link:https://github.com/oauth2-proxy/oauth2-proxy[oauth2-proxy] + or link:https://github.com/openshift/oauth-proxy[openshift/oauth-proxy]. + type: string + gatewayAuthorizationSidecarImage: + description: Gateway sidecar responsible for authorization when + NativeUserMode is enabled. See link:https://github.com/brancz/kube-rbac-proxy[kube-rbac-proxy] + or link:https://github.com/openshift/kube-rbac-proxy[openshift/kube-rbac-proxy] + type: string + gatewayHeaderRewriteSidecarImage: + description: Header Rewrite Proxy sidecar image is used to properly + set authorization header. See link:https://github.com/che-incubator/header-rewrite-proxy[header-rewrite-proxy] + type: string identityProviderAdminUserName: description: Overrides the name of the Identity Provider administrator user. Defaults to `admin`. @@ -208,6 +222,11 @@ spec: 'openshift-config' namespace by Operator. Note that this solution is Openshift 4 platform-specific. type: boolean + nativeUserMode: + description: Enables native user mode. Currently works only on OpenShift + and DevWorkspace engine. Native User mode uses OpenShift OAuth + directly as identity provider, without Keycloak. + type: boolean oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated diff --git a/deploy/crds/org_v1_che_crd.yaml b/deploy/crds/org_v1_che_crd.yaml index a2e664e72..fa350cf29 100644 --- a/deploy/crds/org_v1_che_crd.yaml +++ b/deploy/crds/org_v1_che_crd.yaml @@ -62,6 +62,20 @@ spec: are about to use. See also all the other fields starting with: `identityProvider`.' type: boolean + gatewayAuthenticationSidecarImage: + description: Gateway sidecar responsible for authentication when + NativeUserMode is enabled. See link:https://github.com/oauth2-proxy/oauth2-proxy[oauth2-proxy] + or link:https://github.com/openshift/oauth-proxy[openshift/oauth-proxy]. + type: string + gatewayAuthorizationSidecarImage: + description: Gateway sidecar responsible for authorization when + NativeUserMode is enabled. See link:https://github.com/brancz/kube-rbac-proxy[kube-rbac-proxy] + or link:https://github.com/openshift/kube-rbac-proxy[openshift/kube-rbac-proxy] + type: string + gatewayHeaderRewriteSidecarImage: + description: Header Rewrite Proxy sidecar image is used to properly + set authorization header. See link:https://github.com/che-incubator/header-rewrite-proxy[header-rewrite-proxy] + type: string identityProviderAdminUserName: description: Overrides the name of the Identity Provider administrator user. Defaults to `admin`. @@ -209,6 +223,11 @@ spec: in 'openshift-config' namespace by Operator. Note that this solution is Openshift 4 platform-specific. type: boolean + nativeUserMode: + description: Enables native user mode. Currently works only on + OpenShift and DevWorkspace engine. Native User mode uses OpenShift + OAuth directly as identity provider, without Keycloak. + type: boolean oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated diff --git a/deploy/olm-catalog/nightly/eclipse-che-preview-kubernetes/manifests/che-operator.clusterserviceversion.yaml b/deploy/olm-catalog/nightly/eclipse-che-preview-kubernetes/manifests/che-operator.clusterserviceversion.yaml index 308afef7c..b5b769a4c 100644 --- a/deploy/olm-catalog/nightly/eclipse-che-preview-kubernetes/manifests/che-operator.clusterserviceversion.yaml +++ b/deploy/olm-catalog/nightly/eclipse-che-preview-kubernetes/manifests/che-operator.clusterserviceversion.yaml @@ -86,13 +86,13 @@ metadata: categories: Developer Tools certified: "false" containerImage: quay.io/eclipse/che-operator:next - createdAt: "2021-07-01T21:02:20Z" + createdAt: "2021-07-02T08:14:01Z" description: A Kube-native development solution that delivers portable and collaborative developer workspaces. operatorframework.io/suggested-namespace: eclipse-che repository: https://github.com/eclipse-che/che-operator support: Eclipse Foundation - name: eclipse-che-preview-kubernetes.v7.33.0-246.nightly + name: eclipse-che-preview-kubernetes.v7.33.0-248.nightly namespace: placeholder spec: apiservicedefinitions: {} @@ -792,31 +792,13 @@ spec: - update - watch - apiGroups: - - che.eclipse.org + - org.eclipse.che resources: - - '*' + - checlusters + - checlusters/status + - checlusters/finalizers verbs: - '*' - - apiGroups: - - che.eclipse.org - resources: - - chemanagers - verbs: - - '*' - - apiGroups: - - che.eclipse.org - resources: - - chemanagers/status - verbs: - - get - - patch - - update - - apiGroups: - - che.eclipse.org - resources: - - chemanagers/finalizers - verbs: - - update - apiGroups: - controller.devfile.io resources: @@ -985,6 +967,12 @@ spec: value: quay.io/devfile/devworkspace-controller:next - name: RELATED_IMAGE_internal_rest_backup_server value: quay.io/eclipse/che-backup-server-rest:eeacd92 + - name: RELATED_IMAGE_gateway_authentication_sidecar + value: quay.io/openshift/origin-oauth-proxy:4.7 + - name: RELATED_IMAGE_gateway_authorization_sidecar + value: quay.io/openshift/origin-kube-rbac-proxy:4.7 + - name: RELATED_IMAGE_gateway_header_sidecar + value: quay.io/che-incubator/header-rewrite-proxy:latest - name: CHE_FLAVOR value: che - name: CONSOLE_LINK_NAME @@ -1057,7 +1045,7 @@ spec: fieldRef: fieldPath: metadata.name - name: OPERATOR_NAME - value: devworkspace-operator + value: devworkspace-che-operator - name: MAX_CONCURRENT_RECONCILES value: "1" - name: CONTROLLER_SERVICE_ACCOUNT_NAME @@ -1253,4 +1241,4 @@ spec: maturity: stable provider: name: Eclipse Foundation - version: 7.33.0-246.nightly + version: 7.33.0-248.nightly diff --git a/deploy/olm-catalog/nightly/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml b/deploy/olm-catalog/nightly/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml index a2e664e72..fa350cf29 100644 --- a/deploy/olm-catalog/nightly/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml +++ b/deploy/olm-catalog/nightly/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml @@ -62,6 +62,20 @@ spec: are about to use. See also all the other fields starting with: `identityProvider`.' type: boolean + gatewayAuthenticationSidecarImage: + description: Gateway sidecar responsible for authentication when + NativeUserMode is enabled. See link:https://github.com/oauth2-proxy/oauth2-proxy[oauth2-proxy] + or link:https://github.com/openshift/oauth-proxy[openshift/oauth-proxy]. + type: string + gatewayAuthorizationSidecarImage: + description: Gateway sidecar responsible for authorization when + NativeUserMode is enabled. See link:https://github.com/brancz/kube-rbac-proxy[kube-rbac-proxy] + or link:https://github.com/openshift/kube-rbac-proxy[openshift/kube-rbac-proxy] + type: string + gatewayHeaderRewriteSidecarImage: + description: Header Rewrite Proxy sidecar image is used to properly + set authorization header. See link:https://github.com/che-incubator/header-rewrite-proxy[header-rewrite-proxy] + type: string identityProviderAdminUserName: description: Overrides the name of the Identity Provider administrator user. Defaults to `admin`. @@ -209,6 +223,11 @@ spec: in 'openshift-config' namespace by Operator. Note that this solution is Openshift 4 platform-specific. type: boolean + nativeUserMode: + description: Enables native user mode. Currently works only on + OpenShift and DevWorkspace engine. Native User mode uses OpenShift + OAuth directly as identity provider, without Keycloak. + type: boolean oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated diff --git a/deploy/olm-catalog/nightly/eclipse-che-preview-openshift/manifests/che-operator.clusterserviceversion.yaml b/deploy/olm-catalog/nightly/eclipse-che-preview-openshift/manifests/che-operator.clusterserviceversion.yaml index 3657775c6..1ce159ca1 100644 --- a/deploy/olm-catalog/nightly/eclipse-che-preview-openshift/manifests/che-operator.clusterserviceversion.yaml +++ b/deploy/olm-catalog/nightly/eclipse-che-preview-openshift/manifests/che-operator.clusterserviceversion.yaml @@ -77,13 +77,13 @@ metadata: categories: Developer Tools, OpenShift Optional certified: "false" containerImage: quay.io/eclipse/che-operator:next - createdAt: "2021-07-01T21:02:26Z" + createdAt: "2021-07-02T08:14:06Z" description: A Kube-native development solution that delivers portable and collaborative developer workspaces in OpenShift. operatorframework.io/suggested-namespace: eclipse-che repository: https://github.com/eclipse-che/che-operator support: Eclipse Foundation - name: eclipse-che-preview-openshift.v7.33.0-246.nightly + name: eclipse-che-preview-openshift.v7.33.0-248.nightly namespace: placeholder spec: apiservicedefinitions: {} @@ -861,31 +861,13 @@ spec: - update - watch - apiGroups: - - che.eclipse.org + - org.eclipse.che resources: - - '*' + - checlusters + - checlusters/status + - checlusters/finalizers verbs: - '*' - - apiGroups: - - che.eclipse.org - resources: - - chemanagers - verbs: - - '*' - - apiGroups: - - che.eclipse.org - resources: - - chemanagers/status - verbs: - - get - - patch - - update - - apiGroups: - - che.eclipse.org - resources: - - chemanagers/finalizers - verbs: - - update - apiGroups: - controller.devfile.io resources: @@ -1052,6 +1034,12 @@ spec: value: quay.io/devfile/devworkspace-controller:next - name: RELATED_IMAGE_internal_rest_backup_server value: quay.io/eclipse/che-backup-server-rest:eeacd92 + - name: RELATED_IMAGE_gateway_authentication_sidecar + value: quay.io/openshift/origin-oauth-proxy:4.7 + - name: RELATED_IMAGE_gateway_authorization_sidecar + value: quay.io/openshift/origin-kube-rbac-proxy:4.7 + - name: RELATED_IMAGE_gateway_header_sidecar + value: quay.io/che-incubator/header-rewrite-proxy:latest - name: CHE_FLAVOR value: che - name: CONSOLE_LINK_NAME @@ -1126,7 +1114,7 @@ spec: fieldRef: fieldPath: metadata.name - name: OPERATOR_NAME - value: devworkspace-operator + value: devworkspace-che-operator - name: MAX_CONCURRENT_RECONCILES value: "1" - name: CONTROLLER_SERVICE_ACCOUNT_NAME @@ -1330,4 +1318,4 @@ spec: maturity: stable provider: name: Eclipse Foundation - version: 7.33.0-246.nightly + version: 7.33.0-248.nightly diff --git a/deploy/olm-catalog/nightly/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml b/deploy/olm-catalog/nightly/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml index a466c5c9c..594128667 100644 --- a/deploy/olm-catalog/nightly/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml +++ b/deploy/olm-catalog/nightly/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml @@ -64,6 +64,20 @@ spec: are about to use. See also all the other fields starting with: `identityProvider`.' type: boolean + gatewayAuthenticationSidecarImage: + description: Gateway sidecar responsible for authentication + when NativeUserMode is enabled. See link:https://github.com/oauth2-proxy/oauth2-proxy[oauth2-proxy] + or link:https://github.com/openshift/oauth-proxy[openshift/oauth-proxy]. + type: string + gatewayAuthorizationSidecarImage: + description: Gateway sidecar responsible for authorization when + NativeUserMode is enabled. See link:https://github.com/brancz/kube-rbac-proxy[kube-rbac-proxy] + or link:https://github.com/openshift/kube-rbac-proxy[openshift/kube-rbac-proxy] + type: string + gatewayHeaderRewriteSidecarImage: + description: Header Rewrite Proxy sidecar image is used to properly + set authorization header. See link:https://github.com/che-incubator/header-rewrite-proxy[header-rewrite-proxy] + type: string identityProviderAdminUserName: description: Overrides the name of the Identity Provider administrator user. Defaults to `admin`. @@ -214,6 +228,11 @@ spec: secret in 'openshift-config' namespace by Operator. Note that this solution is Openshift 4 platform-specific. type: boolean + nativeUserMode: + description: Enables native user mode. Currently works only + on OpenShift and DevWorkspace engine. Native User mode uses + OpenShift OAuth directly as identity provider, without Keycloak. + type: boolean oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated diff --git a/deploy/operator.yaml b/deploy/operator.yaml index 9ed93b91a..8e50b65ac 100644 --- a/deploy/operator.yaml +++ b/deploy/operator.yaml @@ -85,6 +85,12 @@ spec: value: quay.io/devfile/devworkspace-controller:next - name: RELATED_IMAGE_internal_rest_backup_server value: quay.io/eclipse/che-backup-server-rest:eeacd92 + - name: RELATED_IMAGE_gateway_authentication_sidecar + value: quay.io/openshift/origin-oauth-proxy:4.7 + - name: RELATED_IMAGE_gateway_authorization_sidecar + value: quay.io/openshift/origin-kube-rbac-proxy:4.7 + - name: RELATED_IMAGE_gateway_header_sidecar + value: quay.io/che-incubator/header-rewrite-proxy:latest - name: CHE_FLAVOR value: che - name: CONSOLE_LINK_NAME @@ -151,7 +157,7 @@ spec: fieldRef: fieldPath: metadata.name - name: OPERATOR_NAME - value: devworkspace-operator + value: devworkspace-che-operator - name: MAX_CONCURRENT_RECONCILES value: "1" - name: CONTROLLER_SERVICE_ACCOUNT_NAME diff --git a/pkg/apis/org/v1/che_types.go b/pkg/apis/org/v1/che_types.go index a2456255f..a287168d6 100644 --- a/pkg/apis/org/v1/che_types.go +++ b/pkg/apis/org/v1/che_types.go @@ -457,6 +457,21 @@ type CheClusterSpecAuth struct { // Identity provider container custom settings. // +optional IdentityProviderContainerResources ResourcesCustomSettings `json:"identityProviderContainerResources,omitempty"` + // Enables native user mode. Currently works only on OpenShift and DevWorkspace engine. + // Native User mode uses OpenShift OAuth directly as identity provider, without Keycloak. + // +optional + NativeUserMode *bool `json:"nativeUserMode,omitempty"` + // Gateway sidecar responsible for authentication when NativeUserMode is enabled. + // See link:https://github.com/oauth2-proxy/oauth2-proxy[oauth2-proxy] or link:https://github.com/openshift/oauth-proxy[openshift/oauth-proxy]. + // +optional + GatewayAuthenticationSidecarImage string `json:"gatewayAuthenticationSidecarImage,omitempty"` + // Gateway sidecar responsible for authorization when NativeUserMode is enabled. + // See link:https://github.com/brancz/kube-rbac-proxy[kube-rbac-proxy] or link:https://github.com/openshift/kube-rbac-proxy[openshift/kube-rbac-proxy] + // +optional + GatewayAuthorizationSidecarImage string `json:"gatewayAuthorizationSidecarImage,omitempty"` + // Header Rewrite Proxy sidecar image is used to properly set authorization header. + // See link:https://github.com/che-incubator/header-rewrite-proxy[header-rewrite-proxy] + GatewayHeaderRewriteSidecarImage string `json:"gatewayHeaderRewriteSidecarImage,omitempty"` } // Ingress custom settings, can be extended in the future diff --git a/pkg/apis/org/v1/zz_generated.deepcopy.go b/pkg/apis/org/v1/zz_generated.deepcopy.go index 217bbae15..a841b29f5 100644 --- a/pkg/apis/org/v1/zz_generated.deepcopy.go +++ b/pkg/apis/org/v1/zz_generated.deepcopy.go @@ -420,6 +420,11 @@ func (in *CheClusterSpecAuth) DeepCopyInto(out *CheClusterSpecAuth) { in.IdentityProviderIngress.DeepCopyInto(&out.IdentityProviderIngress) in.IdentityProviderRoute.DeepCopyInto(&out.IdentityProviderRoute) out.IdentityProviderContainerResources = in.IdentityProviderContainerResources + if in.NativeUserMode != nil { + in, out := &in.NativeUserMode, &out.NativeUserMode + *out = new(bool) + **out = **in + } return } diff --git a/pkg/controller/che/che_controller.go b/pkg/controller/che/che_controller.go index a32939591..8f630ed8a 100644 --- a/pkg/controller/che/che_controller.go +++ b/pkg/controller/che/che_controller.go @@ -519,6 +519,14 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e return reconcile.Result{RequeueAfter: time.Second}, err } + if done, err = r.reconcileGatewayPermissions(deployContext); !done { + if err != nil { + logrus.Error(err) + } + // reconcile after 1 seconds since we deal with cluster objects + return reconcile.Result{RequeueAfter: time.Second}, err + } + done, err = r.reconcileWorkspacePermissions(deployContext) if !done { if err != nil { @@ -846,6 +854,12 @@ func (r *ReconcileChe) reconcileFinalizers(deployContext *deploy.DeployContext) } } + if util.IsNativeUserModeEnabled(deployContext.CheCluster) { + if _, err := r.reconcileGatewayPermissionsFinalizers(deployContext); err != nil { + logrus.Error(err) + } + } + if _, err := r.reconcileWorkspacePermissionsFinalizers(deployContext); err != nil { logrus.Error(err) } diff --git a/pkg/controller/che/create.go b/pkg/controller/che/create.go index 2683c2731..f4e171ac9 100644 --- a/pkg/controller/che/create.go +++ b/pkg/controller/che/create.go @@ -139,19 +139,22 @@ func (r *ReconcileChe) GenerateAndSaveFields(deployContext *deploy.DeployContext return err } } - keycloakRealm := util.GetValue(deployContext.CheCluster.Spec.Auth.IdentityProviderRealm, cheFlavor) - if len(deployContext.CheCluster.Spec.Auth.IdentityProviderRealm) < 1 { - deployContext.CheCluster.Spec.Auth.IdentityProviderRealm = keycloakRealm - if err := deploy.UpdateCheCRSpec(deployContext, "Keycloak realm", keycloakRealm); err != nil { - return err - } - } - keycloakClientId := util.GetValue(deployContext.CheCluster.Spec.Auth.IdentityProviderClientId, cheFlavor+"-public") - if len(deployContext.CheCluster.Spec.Auth.IdentityProviderClientId) < 1 { - deployContext.CheCluster.Spec.Auth.IdentityProviderClientId = keycloakClientId - if err := deploy.UpdateCheCRSpec(deployContext, "Keycloak client ID", keycloakClientId); err != nil { - return err + if !util.IsNativeUserModeEnabled(deployContext.CheCluster) { + keycloakRealm := util.GetValue(deployContext.CheCluster.Spec.Auth.IdentityProviderRealm, cheFlavor) + if len(deployContext.CheCluster.Spec.Auth.IdentityProviderRealm) < 1 { + deployContext.CheCluster.Spec.Auth.IdentityProviderRealm = keycloakRealm + if err := deploy.UpdateCheCRSpec(deployContext, "Keycloak realm", keycloakRealm); err != nil { + return err + } + } + keycloakClientId := util.GetValue(deployContext.CheCluster.Spec.Auth.IdentityProviderClientId, cheFlavor+"-public") + if len(deployContext.CheCluster.Spec.Auth.IdentityProviderClientId) < 1 { + deployContext.CheCluster.Spec.Auth.IdentityProviderClientId = keycloakClientId + + if err := deploy.UpdateCheCRSpec(deployContext, "Keycloak client ID", keycloakClientId); err != nil { + return err + } } } } diff --git a/pkg/controller/che/gateway_permission.go b/pkg/controller/che/gateway_permission.go new file mode 100644 index 000000000..f60ca96c5 --- /dev/null +++ b/pkg/controller/che/gateway_permission.go @@ -0,0 +1,79 @@ +package che + +import ( + orgv1 "github.com/eclipse-che/che-operator/pkg/apis/org/v1" + "github.com/eclipse-che/che-operator/pkg/deploy" + "github.com/eclipse-che/che-operator/pkg/deploy/gateway" + "github.com/eclipse-che/che-operator/pkg/util" + rbac "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/types" +) + +const ( + CheGatewayClusterPermissionsFinalizerName = "cheGateway.clusterpermissions.finalizers.che.eclipse.org" +) + +func (r *ReconcileChe) reconcileGatewayPermissions(deployContext *deploy.DeployContext) (bool, error) { + if util.IsNativeUserModeEnabled(deployContext.CheCluster) { + name := gatewayPermisisonsName(deployContext.CheCluster) + if _, err := deploy.SyncClusterRoleToCluster(deployContext, name, getGatewayClusterRoleRules()); err != nil { + return false, err + } + + if _, err := deploy.SyncClusterRoleBindingToCluster(deployContext, name, gateway.GatewayServiceName, name); err != nil { + return false, err + } + + if err := deploy.AppendFinalizer(deployContext, CheGatewayClusterPermissionsFinalizerName); err != nil { + return false, err + } + } else { + return deleteGatewayPermissions(deployContext) + } + + return true, nil +} + +func (r *ReconcileChe) reconcileGatewayPermissionsFinalizers(deployContext *deploy.DeployContext) (bool, error) { + if !deployContext.CheCluster.ObjectMeta.DeletionTimestamp.IsZero() { + return deleteGatewayPermissions(deployContext) + } + + return true, nil +} + +func deleteGatewayPermissions(deployContext *deploy.DeployContext) (bool, error) { + name := gatewayPermisisonsName(deployContext.CheCluster) + if done, err := deploy.Delete(deployContext, types.NamespacedName{Name: name}, &rbac.ClusterRole{}); !done { + return false, err + } + + if done, err := deploy.Delete(deployContext, types.NamespacedName{Name: name}, &rbac.ClusterRoleBinding{}); !done { + return false, err + } + + if err := deploy.DeleteFinalizer(deployContext, CheGatewayClusterPermissionsFinalizerName); err != nil { + return false, err + } + + return true, nil +} + +func gatewayPermisisonsName(instance *orgv1.CheCluster) string { + return instance.Namespace + "-" + gateway.GatewayServiceName +} + +func getGatewayClusterRoleRules() []rbac.PolicyRule { + return []rbac.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"authentication.k8s.io"}, + Resources: []string{"tokenreviews"}, + }, + { + Verbs: []string{"create"}, + APIGroups: []string{"authorization.k8s.io"}, + Resources: []string{"subjectaccessreviews"}, + }, + } +} diff --git a/pkg/deploy/defaults.go b/pkg/deploy/defaults.go index 15ca0485f..db589a0be 100644 --- a/pkg/deploy/defaults.go +++ b/pkg/deploy/defaults.go @@ -41,6 +41,9 @@ var ( defaultSingleHostGatewayImage string defaultSingleHostGatewayConfigSidecarImage string defaultInternalRestBackupServerImage string + defaultGatewayAuthenticationSidecarImage string + defaultGatewayAuthorizationSidecarImage string + defaultGatewayHeaderProxySidecarImage string defaultCheWorkspacePluginBrokerMetadataImage string defaultCheWorkspacePluginBrokerArtifactsImage string @@ -181,6 +184,8 @@ func InitDefaultsFromFile(defaultsPath string) { defaultKeycloakImage = util.GetDeploymentEnv(operatorDeployment, util.GetArchitectureDependentEnv("RELATED_IMAGE_keycloak")) defaultSingleHostGatewayImage = util.GetDeploymentEnv(operatorDeployment, util.GetArchitectureDependentEnv("RELATED_IMAGE_single_host_gateway")) defaultSingleHostGatewayConfigSidecarImage = util.GetDeploymentEnv(operatorDeployment, util.GetArchitectureDependentEnv("RELATED_IMAGE_single_host_gateway_config_sidecar")) + defaultGatewayAuthenticationSidecarImage = util.GetDeploymentEnv(operatorDeployment, util.GetArchitectureDependentEnv("RELATED_IMAGE_gateway_authentication_sidecar")) + defaultGatewayAuthorizationSidecarImage = util.GetDeploymentEnv(operatorDeployment, util.GetArchitectureDependentEnv("RELATED_IMAGE_gateway_authorization_sidecar")) defaultCheWorkspacePluginBrokerMetadataImage = util.GetDeploymentEnv(operatorDeployment, util.GetArchitectureDependentEnv("RELATED_IMAGE_che_workspace_plugin_broker_metadata")) defaultCheWorkspacePluginBrokerArtifactsImage = util.GetDeploymentEnv(operatorDeployment, util.GetArchitectureDependentEnv("RELATED_IMAGE_che_workspace_plugin_broker_artifacts")) defaultCheServerSecureExposerJwtProxyImage = util.GetDeploymentEnv(operatorDeployment, util.GetArchitectureDependentEnv("RELATED_IMAGE_che_server_secure_exposer_jwt_proxy_image")) @@ -335,6 +340,18 @@ func DefaultInternalBackupServerImage(cr *orgv1.CheCluster) string { return patchDefaultImageName(cr, defaultInternalRestBackupServerImage) } +func DefaultGatewayAuthenticationSidecarImage(cr *orgv1.CheCluster) string { + return patchDefaultImageName(cr, defaultGatewayAuthenticationSidecarImage) +} + +func DefaultGatewayAuthorizationSidecarImage(cr *orgv1.CheCluster) string { + return patchDefaultImageName(cr, defaultGatewayAuthorizationSidecarImage) +} + +func DefaultGatewayHeaderProxySidecarImage(cr *orgv1.CheCluster) string { + return patchDefaultImageName(cr, defaultGatewayHeaderProxySidecarImage) +} + func DefaultKubernetesImagePullerOperatorCSV() string { return KubernetesImagePullerOperatorCSV } @@ -440,6 +457,9 @@ func InitDefaultsFromEnv() { defaultSingleHostGatewayImage = getDefaultFromEnv(util.GetArchitectureDependentEnv("RELATED_IMAGE_single_host_gateway")) defaultSingleHostGatewayConfigSidecarImage = getDefaultFromEnv(util.GetArchitectureDependentEnv("RELATED_IMAGE_single_host_gateway_config_sidecar")) defaultInternalRestBackupServerImage = getDefaultFromEnv(util.GetArchitectureDependentEnv("RELATED_IMAGE_internal_rest_backup_server")) + defaultGatewayAuthenticationSidecarImage = getDefaultFromEnv(util.GetArchitectureDependentEnv("RELATED_IMAGE_gateway_authentication_sidecar")) + defaultGatewayAuthorizationSidecarImage = getDefaultFromEnv(util.GetArchitectureDependentEnv("RELATED_IMAGE_gateway_authorization_sidecar")) + defaultGatewayHeaderProxySidecarImage = getDefaultFromEnv(util.GetArchitectureDependentEnv("RELATED_IMAGE_gateway_header_sidecar")) // CRW images for that are mentioned in the Che server che.properties // For CRW these should be synced by hand with images stored in RH registries diff --git a/pkg/deploy/gateway/gateway.go b/pkg/deploy/gateway/gateway.go index 363e43752..9df4603bd 100644 --- a/pkg/deploy/gateway/gateway.go +++ b/pkg/deploy/gateway/gateway.go @@ -13,8 +13,12 @@ package gateway import ( "context" + "encoding/base64" + "fmt" "strconv" + "github.com/sirupsen/logrus" + "github.com/eclipse-che/che-operator/pkg/deploy" orgv1 "github.com/eclipse-che/che-operator/pkg/apis/org/v1" @@ -39,6 +43,7 @@ const ( gatewayServerConfigName = "che-gateway-route-server" gatewayConfigComponentName = "che-gateway-config" + gatewayOauthSecretName = "che-gateway-oauth-secret" ) var ( @@ -50,6 +55,7 @@ var ( cmpopts.IgnoreFields(corev1.ServiceSpec{}, "ClusterIP"), } configMapDiffOpts = cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta", "ObjectMeta") + secretDiffOpts = cmpopts.IgnoreFields(corev1.Secret{}, "TypeMeta", "ObjectMeta") ) // SyncGatewayToCluster installs or deletes the gateway based on the custom resource configuration @@ -79,6 +85,25 @@ func syncAll(deployContext *deploy.DeployContext) error { return err } + if util.IsNativeUserModeEnabled(instance) { + if oauthSecret, err := getGatewaySecretSpec(deployContext); err == nil { + if _, err := deploy.Sync(deployContext, oauthSecret, secretDiffOpts); err != nil { + return err + } + oauthProxyConfig := getGatewayOauthProxyConfigSpec(instance, string(oauthSecret.Data["cookie_secret"])) + if _, err := deploy.Sync(deployContext, &oauthProxyConfig, configMapDiffOpts); err != nil { + return err + } + } else { + return err + } + + headerRewriteProxyConfig := getGatewayHeaderRewriteProxyConfigSpec(instance) + if _, err := deploy.Sync(deployContext, &headerRewriteProxyConfig, configMapDiffOpts); err != nil { + return err + } + } + traefikConfig := getGatewayTraefikConfigSpec(instance) if _, err := deploy.Sync(deployContext, &traefikConfig, configMapDiffOpts); err != nil { return err @@ -102,6 +127,39 @@ func syncAll(deployContext *deploy.DeployContext) error { return nil } +func getGatewaySecretSpec(deployContext *deploy.DeployContext) (*corev1.Secret, error) { + secret := &corev1.Secret{} + exists, err := deploy.GetNamespacedObject(deployContext, gatewayOauthSecretName, secret) + if err == nil && exists { + if _, ok := secret.Data["cookie_secret"]; !ok { + logrus.Info("che-gateway-secret found, but does not contain `cookie_secret` value. Regenerating...") + return generateOauthSecretSpec(deployContext), nil + } + return secret, nil + } else if err == nil && !exists { + return generateOauthSecretSpec(deployContext), nil + } else { + return nil, err + } +} + +func generateOauthSecretSpec(deployContext *deploy.DeployContext) *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: gatewayOauthSecretName, + Namespace: deployContext.CheCluster.Namespace, + Labels: deploy.GetLabels(deployContext.CheCluster, GatewayServiceName), + }, + Data: map[string][]byte{ + "cookie_secret": generateRandomCookieSecret(), + }, + } +} + func deleteAll(deployContext *deploy.DeployContext) error { instance := deployContext.CheCluster clusterAPI := deployContext.ClusterAPI @@ -318,7 +376,70 @@ func getGatewayRoleBindingSpec(instance *orgv1.CheCluster) rbac.RoleBinding { } } +func getGatewayOauthProxyConfigSpec(instance *orgv1.CheCluster, cookieSecret string) corev1.ConfigMap { + return corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "che-gateway-config-oauth-proxy", + Namespace: instance.Namespace, + Labels: deploy.GetLabels(instance, GatewayServiceName), + }, + Data: map[string]string{ + "oauth-proxy.cfg": fmt.Sprintf(` +http_address = ":8080" +https_address = "" +provider = "openshift" +redirect_url = "https://%s/oauth/callback" +upstreams = [ + "http://127.0.0.1:8081/" +] +client_id = "%s" +client_secret = "%s" +scope = "user:full" +openshift_service_account = "%s" +cookie_secret = "%s" +email_domains = "*" +cookie_httponly = false +pass_access_token = true +skip_provider_button = true`, instance.Spec.Server.CheHost, instance.Spec.Auth.OAuthClientName, instance.Spec.Auth.OAuthSecret, GatewayServiceName, cookieSecret), + }, + } +} + +func generateRandomCookieSecret() []byte { + return []byte(base64.StdEncoding.EncodeToString([]byte(util.GeneratePasswd(16)))) +} + +func getGatewayHeaderRewriteProxyConfigSpec(instance *orgv1.CheCluster) corev1.ConfigMap { + return corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "che-gateway-config-header-rewrite-proxy", + Namespace: instance.Namespace, + Labels: deploy.GetLabels(instance, GatewayServiceName), + }, + Data: map[string]string{ + "rules.yaml": ` +rules: +- from: X-Forwarded-Access-Token + to: Authorization + prefix: 'Bearer ' +`, + }, + } +} + func getGatewayTraefikConfigSpec(instance *orgv1.CheCluster) corev1.ConfigMap { + traefikPort := 8080 + if util.IsNativeUserModeEnabled(instance) { + traefikPort = 8088 + } return corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ APIVersion: corev1.SchemeGroupVersion.String(), @@ -330,16 +451,16 @@ func getGatewayTraefikConfigSpec(instance *orgv1.CheCluster) corev1.ConfigMap { Labels: deploy.GetLabels(instance, GatewayServiceName), }, Data: map[string]string{ - "traefik.yml": ` + "traefik.yml": fmt.Sprintf(` entrypoints: http: - address: ":8080" - forwardedHeaders: - insecure: true - https: - address: ":8443" + address: ":%d" forwardedHeaders: insecure: true + sink: + address: ":8090" +ping: + entryPoint: "sink" global: checkNewVersion: false sendAnonymousUsage: false @@ -348,19 +469,15 @@ providers: directory: "/dynamic-config" watch: true log: - level: "INFO"`, + level: "INFO"`, traefikPort), }, } } func getGatewayDeploymentSpec(instance *orgv1.CheCluster) appsv1.Deployment { - gatewayImage := util.GetValue(instance.Spec.Server.SingleHostGatewayImage, deploy.DefaultSingleHostGatewayImage(instance)) - sidecarImage := util.GetValue(instance.Spec.Server.SingleHostGatewayConfigSidecarImage, deploy.DefaultSingleHostGatewayConfigSidecarImage(instance)) - configLabelsMap := util.GetMapValue(instance.Spec.Server.SingleHostGatewayConfigMapLabels, deploy.DefaultSingleHostGatewayConfigMapLabels) terminationGracePeriodSeconds := int64(10) - configLabels := labels.FormatLabels(configLabelsMap) - labels, labelsSelector := deploy.GetLabelsAndSelector(instance, GatewayServiceName) + deployLabels, labelsSelector := deploy.GetLabelsAndSelector(instance, GatewayServiceName) return appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ @@ -370,7 +487,7 @@ func getGatewayDeploymentSpec(instance *orgv1.CheCluster) appsv1.Deployment { ObjectMeta: metav1.ObjectMeta{ Name: GatewayServiceName, Namespace: instance.Namespace, - Labels: labels, + Labels: deployLabels, }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ @@ -381,81 +498,169 @@ func getGatewayDeploymentSpec(instance *orgv1.CheCluster) appsv1.Deployment { }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: labels, + Labels: deployLabels, }, Spec: corev1.PodSpec{ TerminationGracePeriodSeconds: &terminationGracePeriodSeconds, ServiceAccountName: GatewayServiceName, RestartPolicy: corev1.RestartPolicyAlways, - Containers: []corev1.Container{ - { - Name: "gateway", - Image: gatewayImage, - ImagePullPolicy: corev1.PullAlways, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "static-config", - MountPath: "/etc/traefik", - }, - { - Name: "dynamic-config", - MountPath: "/dynamic-config", - }, - }, - }, - { - Name: "configbump", - Image: sidecarImage, - ImagePullPolicy: corev1.PullAlways, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "dynamic-config", - MountPath: "/dynamic-config", - }, - }, - Env: []corev1.EnvVar{ - { - Name: "CONFIG_BUMP_DIR", - Value: "/dynamic-config", - }, - { - Name: "CONFIG_BUMP_LABELS", - Value: configLabels, - }, - { - Name: "CONFIG_BUMP_NAMESPACE", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.namespace", - }, - }, - }, - }, - }, - }, - Volumes: []corev1.Volume{ - { - Name: "static-config", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "che-gateway-config", - }, - }, - }, - }, - { - Name: "dynamic-config", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, + Containers: getContainersSpec(instance), + Volumes: getVolumesSpec(instance), + }, + }, + }, + } +} + +func getContainersSpec(instance *orgv1.CheCluster) []corev1.Container { + configLabelsMap := util.GetMapValue(instance.Spec.Server.SingleHostGatewayConfigMapLabels, deploy.DefaultSingleHostGatewayConfigMapLabels) + gatewayImage := util.GetValue(instance.Spec.Server.SingleHostGatewayImage, deploy.DefaultSingleHostGatewayImage(instance)) + configSidecarImage := util.GetValue(instance.Spec.Server.SingleHostGatewayConfigSidecarImage, deploy.DefaultSingleHostGatewayConfigSidecarImage(instance)) + authnImage := util.GetValue(instance.Spec.Auth.GatewayAuthenticationSidecarImage, deploy.DefaultGatewayAuthenticationSidecarImage(instance)) + authzImage := util.GetValue(instance.Spec.Auth.GatewayAuthorizationSidecarImage, deploy.DefaultGatewayAuthorizationSidecarImage(instance)) + headerProxyImage := util.GetValue(instance.Spec.Auth.GatewayHeaderRewriteSidecarImage, deploy.DefaultGatewayHeaderProxySidecarImage(instance)) + configLabels := labels.FormatLabels(configLabelsMap) + + containers := []corev1.Container{ + { + Name: "gateway", + Image: gatewayImage, + ImagePullPolicy: corev1.PullAlways, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "static-config", + MountPath: "/etc/traefik", + }, + { + Name: "dynamic-config", + MountPath: "/dynamic-config", + }, + }, + }, + { + Name: "configbump", + Image: configSidecarImage, + ImagePullPolicy: corev1.PullAlways, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "dynamic-config", + MountPath: "/dynamic-config", + }, + }, + Env: []corev1.EnvVar{ + { + Name: "CONFIG_BUMP_DIR", + Value: "/dynamic-config", + }, + { + Name: "CONFIG_BUMP_LABELS", + Value: configLabels, + }, + { + Name: "CONFIG_BUMP_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.namespace", }, }, }, }, }, } + + if util.IsNativeUserModeEnabled(instance) { + containers = append(containers, + corev1.Container{ + Name: "oauth-proxy", + Image: authnImage, + ImagePullPolicy: corev1.PullAlways, + Args: []string{ + "--config=/etc/oauth-proxy/oauth-proxy.cfg", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "oauth-proxy-config", + MountPath: "/etc/oauth-proxy", + }, + }, + Ports: []corev1.ContainerPort{ + {ContainerPort: 8080}, + }, + }, + corev1.Container{ + Name: "header-rewrite-proxy", + Image: headerProxyImage, + ImagePullPolicy: corev1.PullAlways, + Args: []string{"--upstream=http://127.0.0.1:8088", "--bind=127.0.0.1:8081", "--rules=/etc/rules/rules.yaml"}, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "header-rewrite-proxy-rules", + MountPath: "/etc/rules", + }, + }, + }, + corev1.Container{ + Name: "kube-rbac-proxy", + Image: authzImage, + ImagePullPolicy: corev1.PullAlways, + Args: []string{ + "--insecure-listen-address=127.0.0.1:8089", + "--upstream=http://127.0.0.1:8090/ping", + "--logtostderr=true", + "--v=10", + }, + }) + } + + return containers +} + +func getVolumesSpec(instance *orgv1.CheCluster) []corev1.Volume { + volumes := []corev1.Volume{ + { + Name: "static-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "che-gateway-config", + }, + }, + }, + }, + { + Name: "dynamic-config", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + } + + if util.IsNativeUserModeEnabled(instance) { + volumes = append(volumes, corev1.Volume{ + Name: "oauth-proxy-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "che-gateway-config-oauth-proxy", + }, + }, + }, + }) + + volumes = append(volumes, corev1.Volume{ + Name: "header-rewrite-proxy-rules", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "che-gateway-config-header-rewrite-proxy", + }, + }, + }, + }) + } + + return volumes } func getGatewayServiceSpec(instance *orgv1.CheCluster) corev1.Service { @@ -480,12 +685,6 @@ func getGatewayServiceSpec(instance *orgv1.CheCluster) corev1.Service { Protocol: corev1.ProtocolTCP, TargetPort: intstr.FromInt(8080), }, - { - Name: "gateway-https", - Port: 8443, - Protocol: corev1.ProtocolTCP, - TargetPort: intstr.FromInt(8443), - }, }, }, } diff --git a/pkg/deploy/gateway/gateway_test.go b/pkg/deploy/gateway/gateway_test.go new file mode 100644 index 000000000..41955469c --- /dev/null +++ b/pkg/deploy/gateway/gateway_test.go @@ -0,0 +1,166 @@ +package gateway + +import ( + "context" + "encoding/base64" + "testing" + + orgv1 "github.com/eclipse-che/che-operator/pkg/apis/org/v1" + "github.com/eclipse-che/che-operator/pkg/deploy" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestSyncAllToCluster(t *testing.T) { + orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) + corev1.SchemeBuilder.AddToScheme(scheme.Scheme) + cli := fake.NewFakeClientWithScheme(scheme.Scheme) + deployContext := &deploy.DeployContext{ + CheCluster: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: orgv1.CheClusterSpec{ + Server: orgv1.CheClusterSpecServer{ + ServerExposureStrategy: "single-host", + }, + }, + }, + ClusterAPI: deploy.ClusterAPI{ + Client: cli, + NonCachedClient: cli, + Scheme: scheme.Scheme, + }, + } + + err := SyncGatewayToCluster(deployContext) + if err != nil { + t.Fatalf("Failed to sync Gateway: %v", err) + } + + deployment := &appsv1.Deployment{} + err = cli.Get(context.TODO(), types.NamespacedName{Name: GatewayServiceName, Namespace: "eclipse-che"}, deployment) + if err != nil { + t.Fatalf("Failed to get deployment: %v", err) + } + + if len(deployment.Spec.Template.Spec.Containers) != 2 { + t.Fatalf("With classic multi-user, there should be 2 containers in the gateway, traefik and configbump. But it has '%d' containers.", len(deployment.Spec.Template.Spec.Containers)) + } + + service := &corev1.Service{} + err = cli.Get(context.TODO(), types.NamespacedName{Name: GatewayServiceName, Namespace: "eclipse-che"}, service) + if err != nil { + t.Fatalf("Failed to get service: %v", err) + } +} + +func TestNativeUserGateway(t *testing.T) { + orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) + corev1.SchemeBuilder.AddToScheme(scheme.Scheme) + cli := fake.NewFakeClientWithScheme(scheme.Scheme) + nativeUserMode := true + deployContext := &deploy.DeployContext{ + CheCluster: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: orgv1.CheClusterSpec{ + Auth: orgv1.CheClusterSpecAuth{ + NativeUserMode: &nativeUserMode, + }, + Server: orgv1.CheClusterSpecServer{ + ServerExposureStrategy: "single-host", + }, + }, + }, + ClusterAPI: deploy.ClusterAPI{ + Client: cli, + NonCachedClient: cli, + Scheme: scheme.Scheme, + }, + } + + err := SyncGatewayToCluster(deployContext) + if err != nil { + t.Fatalf("Failed to sync Gateway: %v", err) + } + + deployment := &appsv1.Deployment{} + err = cli.Get(context.TODO(), types.NamespacedName{Name: GatewayServiceName, Namespace: "eclipse-che"}, deployment) + if err != nil { + t.Fatalf("Failed to get deployment: %v", err) + } + + if len(deployment.Spec.Template.Spec.Containers) != 5 { + t.Fatalf("With native user mode, there should be 5 containers in the gateway.. But it has '%d' containers.", len(deployment.Spec.Template.Spec.Containers)) + } + + service := &corev1.Service{} + err = cli.Get(context.TODO(), types.NamespacedName{Name: GatewayServiceName, Namespace: "eclipse-che"}, service) + if err != nil { + t.Fatalf("Failed to get service: %v", err) + } +} + +func TestNoGatewayForMultiHost(t *testing.T) { + orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) + corev1.SchemeBuilder.AddToScheme(scheme.Scheme) + cli := fake.NewFakeClientWithScheme(scheme.Scheme) + deployContext := &deploy.DeployContext{ + CheCluster: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + Name: "eclipse-che", + }, + Spec: orgv1.CheClusterSpec{ + Server: orgv1.CheClusterSpecServer{ + ServerExposureStrategy: "multi-host", + }, + }, + }, + ClusterAPI: deploy.ClusterAPI{ + Client: cli, + NonCachedClient: cli, + Scheme: scheme.Scheme, + }, + } + + err := SyncGatewayToCluster(deployContext) + if err != nil { + t.Fatalf("Failed to sync Gateway: %v", err) + } + + deployment := &appsv1.Deployment{} + err = cli.Get(context.TODO(), types.NamespacedName{Name: GatewayServiceName, Namespace: "eclipse-che"}, deployment) + if err == nil { + t.Fatalf("Failed to get deployment: %v", err) + } else { + if v, ok := err.(errors.APIStatus); ok { + if v.Status().Code != 404 { + t.Fatalf("Deployment should not be found, thus code 404, but got '%d'", v.Status().Code) + } + } else { + t.Fatalf("Wrong error returned.") + } + } +} + +func TestRandomCookieSecret(t *testing.T) { + secret := generateRandomCookieSecret() + if len(secret) != 24 { + t.Fatalf("lenght of the secret should be 24") + } + + _, err := base64.StdEncoding.Decode(make([]byte, 24), secret) + if err != nil { + t.Fatalf("Failed to decode the secret '%s'", err) + } +} diff --git a/pkg/deploy/gateway/init_test.go b/pkg/deploy/gateway/init_test.go new file mode 100644 index 000000000..84cf07c22 --- /dev/null +++ b/pkg/deploy/gateway/init_test.go @@ -0,0 +1,21 @@ +// +// Copyright (c) 2020-2020 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 gateway + +import "github.com/eclipse-che/che-operator/pkg/deploy" + +func init() { + err := deploy.InitTestDefaultsFromDeployment("../../../deploy/operator.yaml") + if err != nil { + panic(err) + } +} diff --git a/pkg/deploy/identity-provider/identity_provider.go b/pkg/deploy/identity-provider/identity_provider.go index fe6d6848d..0d920b4aa 100644 --- a/pkg/deploy/identity-provider/identity_provider.go +++ b/pkg/deploy/identity-provider/identity_provider.go @@ -16,13 +16,14 @@ import ( "errors" "strings" + "github.com/sirupsen/logrus" + orgv1 "github.com/eclipse-che/che-operator/pkg/apis/org/v1" "github.com/eclipse-che/che-operator/pkg/deploy" "github.com/eclipse-che/che-operator/pkg/deploy/expose" "github.com/eclipse-che/che-operator/pkg/util" "github.com/google/go-cmp/cmp/cmpopts" oauth "github.com/openshift/api/oauth/v1" - "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" @@ -47,7 +48,9 @@ var ( // the provisioning is complete, false if requeue of the reconcile request is needed. func SyncIdentityProviderToCluster(deployContext *deploy.DeployContext) (bool, error) { cr := deployContext.CheCluster - if cr.Spec.Auth.ExternalIdentityProvider { + if util.IsNativeUserModeEnabled(cr) { + return syncNativeIdentityProviderItems(deployContext) + } else if cr.Spec.Auth.ExternalIdentityProvider { return true, nil } @@ -154,31 +157,41 @@ func syncOpenShiftIdentityProvider(deployContext *deploy.DeployContext) (bool, e return true, nil } +func syncNativeIdentityProviderItems(deployContext *deploy.DeployContext) (bool, error) { + cr := deployContext.CheCluster + + if err := resolveOpenshiftOAuthClientName(deployContext); err != nil { + return false, err + } + if err := resolveOpenshiftOAuthClientSecret(deployContext); err != nil { + return false, err + } + + redirectURIs := []string{"https://" + cr.Spec.Server.CheHost + "/oauth/callback"} + + oAuthClient := deploy.GetOAuthClientSpec(cr.Spec.Auth.OAuthClientName, cr.Spec.Auth.OAuthSecret, redirectURIs) + provisioned, err := deploy.Sync(deployContext, oAuthClient, oAuthClientDiffOpts) + if !provisioned { + return false, err + } + + return true, nil +} + func SyncOpenShiftIdentityProviderItems(deployContext *deploy.DeployContext) (bool, error) { cr := deployContext.CheCluster - oAuthClientName := cr.Spec.Auth.OAuthClientName - if len(oAuthClientName) < 1 { - oAuthClientName = cr.Name + "-openshift-identity-provider-" + strings.ToLower(util.GeneratePasswd(6)) - cr.Spec.Auth.OAuthClientName = oAuthClientName - if err := deploy.UpdateCheCRSpec(deployContext, "oAuthClient name", oAuthClientName); err != nil { - return false, err - } + if err := resolveOpenshiftOAuthClientName(deployContext); err != nil { + return false, err } - - oauthSecret := cr.Spec.Auth.OAuthSecret - if len(oauthSecret) < 1 { - oauthSecret = util.GeneratePasswd(12) - cr.Spec.Auth.OAuthSecret = oauthSecret - if err := deploy.UpdateCheCRSpec(deployContext, "oAuth secret name", oauthSecret); err != nil { - return false, err - } + if err := resolveOpenshiftOAuthClientSecret(deployContext); err != nil { + return false, err } keycloakURL := cr.Spec.Auth.IdentityProviderURL cheFlavor := deploy.DefaultCheFlavor(cr) keycloakRealm := util.GetValue(cr.Spec.Auth.IdentityProviderRealm, cheFlavor) - oAuthClient := deploy.GetOAuthClientSpec(oAuthClientName, oauthSecret, keycloakURL, keycloakRealm, util.IsOpenShift4) + oAuthClient := deploy.GetKeycloakOAuthClientSpec(cr.Spec.Auth.OAuthClientName, cr.Spec.Auth.OAuthSecret, keycloakURL, keycloakRealm, util.IsOpenShift4) provisioned, err := deploy.Sync(deployContext, oAuthClient, oAuthClientDiffOpts) if !provisioned { return false, err @@ -192,7 +205,7 @@ func SyncOpenShiftIdentityProviderItems(deployContext *deploy.DeployContext) (bo cr, deploy.IdentityProviderName, func(cr *orgv1.CheCluster) (string, error) { - return GetOpenShiftIdentityProviderProvisionCommand(cr, oAuthClientName, oauthSecret) + return GetOpenShiftIdentityProviderProvisionCommand(cr, cr.Spec.Auth.OAuthClientName, cr.Spec.Auth.OAuthSecret) }, "Create OpenShift identity provider") if err != nil { @@ -214,6 +227,32 @@ func SyncOpenShiftIdentityProviderItems(deployContext *deploy.DeployContext) (bo return true, nil } +func resolveOpenshiftOAuthClientName(deployContext *deploy.DeployContext) error { + cr := deployContext.CheCluster + oAuthClientName := cr.Spec.Auth.OAuthClientName + if len(oAuthClientName) < 1 { + oAuthClientName = cr.Name + "-openshift-identity-provider-" + strings.ToLower(util.GeneratePasswd(6)) + cr.Spec.Auth.OAuthClientName = oAuthClientName + if err := deploy.UpdateCheCRSpec(deployContext, "oAuthClient name", oAuthClientName); err != nil { + return err + } + } + return nil +} + +func resolveOpenshiftOAuthClientSecret(deployContext *deploy.DeployContext) error { + cr := deployContext.CheCluster + oauthSecret := cr.Spec.Auth.OAuthSecret + if len(oauthSecret) < 1 { + oauthSecret = util.GeneratePasswd(12) + cr.Spec.Auth.OAuthSecret = oauthSecret + if err := deploy.UpdateCheCRSpec(deployContext, "oAuth secret name", oauthSecret); err != nil { + return err + } + } + return nil +} + // SyncGitHubOAuth provisions GitHub OAuth if secret with annotation // `che.eclipse.org/github-oauth-credentials=true` or `che.eclipse.org/oauth-scm-configuration=github` // is mounted into a container diff --git a/pkg/deploy/oauthclient.go b/pkg/deploy/oauthclient.go index 8a0c5e938..c62536c62 100644 --- a/pkg/deploy/oauthclient.go +++ b/pkg/deploy/oauthclient.go @@ -23,7 +23,7 @@ const ( OAuthFinalizerName = "oauthclients.finalizers.che.eclipse.org" ) -func GetOAuthClientSpec(name string, oauthSecret string, keycloakURL string, keycloakRealm string, isOpenShift4 bool) *oauth.OAuthClient { +func GetKeycloakOAuthClientSpec(name string, oauthSecret string, keycloakURL string, keycloakRealm string, isOpenShift4 bool) *oauth.OAuthClient { providerName := "openshift-v3" if isOpenShift4 { providerName = "openshift-v4" @@ -41,6 +41,10 @@ func GetOAuthClientSpec(name string, oauthSecret string, keycloakURL string, key "https://" + keycloakURL + redirectURLSuffix, } } + return GetOAuthClientSpec(name, oauthSecret, redirectURIs) +} + +func GetOAuthClientSpec(name string, oauthSecret string, redirectURIs []string) *oauth.OAuthClient { return &oauth.OAuthClient{ TypeMeta: metav1.TypeMeta{ Kind: "OAuthClient", diff --git a/pkg/deploy/server/server_deployment.go b/pkg/deploy/server/server_deployment.go index cfef69fd4..543e97c63 100644 --- a/pkg/deploy/server/server_deployment.go +++ b/pkg/deploy/server/server_deployment.go @@ -161,6 +161,13 @@ func (s Server) getDeploymentSpec() (*appsv1.Deployment, error) { FieldPath: "metadata.namespace"}}, }) + if util.IsNativeUserModeEnabled(s.deployContext.CheCluster) { + cheEnv = append(cheEnv, corev1.EnvVar{ + Name: "CHE_AUTH_NATIVEUSER", + Value: "true", + }) + } + cheImageAndTag := GetFullCheServerImageLink(s.deployContext.CheCluster) pullPolicy := corev1.PullPolicy(util.GetValue(string(s.deployContext.CheCluster.Spec.Server.CheImagePullPolicy), deploy.DefaultPullPolicyFromDockerImage(cheImageAndTag))) diff --git a/pkg/util/util.go b/pkg/util/util.go index 90e8d1dc7..7cb32eeb1 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -447,6 +447,11 @@ func NewBoolPointer(value bool) *bool { return &variable } +func IsNativeUserModeEnabled(c *orgv1.CheCluster) bool { + // Native user mode is now available only on openshift + return IsOpenShift && c.Spec.Auth.NativeUserMode != nil && *c.Spec.Auth.NativeUserMode +} + // IsOAuthEnabled returns true when oAuth is enable for CheCluster resource, otherwise false. func IsOAuthEnabled(c *orgv1.CheCluster) bool { return IsOpenShift && c.Spec.Auth.OpenShiftoAuth != nil && *c.Spec.Auth.OpenShiftoAuth