feat: native user authentication (#861)
Signed-off-by: Michal Vala <mvala@redhat.com> Co-authored-by: Anatolii Bazko <abazko@redhat.com>pull/889/head
parent
e71703c7f7
commit
b9ccdb73d8
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue