diff --git a/api/checluster_conversion_from_test.go b/api/checluster_conversion_from_test.go index 7b288dd82..c9d0b7fdb 100644 --- a/api/checluster_conversion_from_test.go +++ b/api/checluster_conversion_from_test.go @@ -286,6 +286,8 @@ func TestConvertFrom(t *testing.T) { IdentityProviderURL: "IdentityProviderURL", OAuthClientName: "OAuthClientName", OAuthSecret: "OAuthSecret", + OAuthScope: "OAuthScope", + IdentityToken: "IdentityToken", Gateway: chev2.Gateway{ Deployment: chev2.Deployment{ Containers: []chev2.Container{ @@ -377,6 +379,8 @@ func TestConvertFrom(t *testing.T) { assert.Equal(t, checlusterv1.Spec.Auth.IdentityProviderURL, "IdentityProviderURL") assert.Equal(t, checlusterv1.Spec.Auth.OAuthClientName, "OAuthClientName") assert.Equal(t, checlusterv1.Spec.Auth.OAuthSecret, "OAuthSecret") + assert.Equal(t, checlusterv1.Spec.Auth.OAuthScope, "OAuthScope") + assert.Equal(t, checlusterv1.Spec.Auth.IdentityToken, "IdentityToken") assert.Equal(t, checlusterv1.Spec.Database.ChePostgresContainerResources.Limits.Cpu, "2") assert.Equal(t, checlusterv1.Spec.Database.ChePostgresContainerResources.Limits.Memory, "228Mi") diff --git a/api/checluster_conversion_to_test.go b/api/checluster_conversion_to_test.go index f42b90c63..bab494862 100644 --- a/api/checluster_conversion_to_test.go +++ b/api/checluster_conversion_to_test.go @@ -245,6 +245,8 @@ func TestConvertTo(t *testing.T) { IdentityProviderURL: "IdentityProviderURL", OAuthClientName: "OAuthClientName", OAuthSecret: "OAuthSecret", + OAuthScope: "OAuthScope", + IdentityToken: "IdentityToken", GatewayAuthenticationSidecarImage: "GatewayAuthenticationSidecarImage", GatewayAuthorizationSidecarImage: "GatewayAuthorizationSidecarImage", }, @@ -304,6 +306,8 @@ func TestConvertTo(t *testing.T) { assert.Equal(t, checlusterv2.Spec.Networking.Auth.IdentityProviderURL, "IdentityProviderURL") assert.Equal(t, checlusterv2.Spec.Networking.Auth.OAuthClientName, "OAuthClientName") assert.Equal(t, checlusterv2.Spec.Networking.Auth.OAuthSecret, "OAuthSecret") + assert.Equal(t, checlusterv2.Spec.Networking.Auth.OAuthScope, "OAuthScope") + assert.Equal(t, checlusterv2.Spec.Networking.Auth.IdentityToken, "IdentityToken") assert.Equal(t, checlusterv2.Spec.ContainerRegistry.Hostname, "AirGapContainerRegistryHostname") assert.Equal(t, checlusterv2.Spec.ContainerRegistry.Organization, "AirGapContainerRegistryOrganization") diff --git a/api/checluster_round_conversion_test.go b/api/checluster_round_conversion_test.go index ee8e3ab00..408d17263 100644 --- a/api/checluster_round_conversion_test.go +++ b/api/checluster_round_conversion_test.go @@ -265,6 +265,8 @@ func TestRoundConvertCheClusterV2(t *testing.T) { IdentityProviderURL: "IdentityProviderURL", OAuthClientName: "OAuthClientName", OAuthSecret: "OAuthSecret", + OAuthScope: "OAuthScope", + IdentityToken: "IdentityToken", Gateway: chev2.Gateway{ Deployment: chev2.Deployment{ Containers: []chev2.Container{ @@ -466,6 +468,8 @@ func TestRoundConvertCheClusterV1(t *testing.T) { IdentityProviderURL: "IdentityProviderURL", OAuthClientName: "OAuthClientName", OAuthSecret: "OAuthSecret", + OAuthScope: "OAuthScope", + IdentityToken: "IdentityToken", GatewayAuthenticationSidecarImage: "GatewayAuthenticationSidecarImage", GatewayAuthorizationSidecarImage: "GatewayAuthorizationSidecarImage", }, diff --git a/api/v1/checluster_conversion_from.go b/api/v1/checluster_conversion_from.go index 4c24783b4..3b506b27b 100644 --- a/api/v1/checluster_conversion_from.go +++ b/api/v1/checluster_conversion_from.go @@ -241,6 +241,8 @@ func (dst *CheCluster) convertFrom_Auth(src *chev2.CheCluster) error { dst.Spec.Auth.IdentityProviderURL = src.Spec.Networking.Auth.IdentityProviderURL dst.Spec.Auth.OAuthClientName = src.Spec.Networking.Auth.OAuthClientName dst.Spec.Auth.OAuthSecret = src.Spec.Networking.Auth.OAuthSecret + dst.Spec.Auth.OAuthScope = src.Spec.Networking.Auth.OAuthScope + dst.Spec.Auth.IdentityToken = src.Spec.Networking.Auth.IdentityToken for _, c := range src.Spec.Networking.Auth.Gateway.Deployment.Containers { switch c.Name { diff --git a/api/v1/checluster_conversion_to.go b/api/v1/checluster_conversion_to.go index 197b59c8c..88b195250 100644 --- a/api/v1/checluster_conversion_to.go +++ b/api/v1/checluster_conversion_to.go @@ -182,6 +182,8 @@ func (src *CheCluster) convertTo_Networking_Auth(dst *chev2.CheCluster) error { dst.Spec.Networking.Auth.IdentityProviderURL = src.Spec.Auth.IdentityProviderURL dst.Spec.Networking.Auth.OAuthClientName = src.Spec.Auth.OAuthClientName dst.Spec.Networking.Auth.OAuthSecret = src.Spec.Auth.OAuthSecret + dst.Spec.Networking.Auth.OAuthScope = src.Spec.Auth.OAuthScope + dst.Spec.Networking.Auth.IdentityToken = src.Spec.Auth.IdentityToken if err := src.convertTo_Networking_Auth_Gateway(dst); err != nil { return err diff --git a/api/v1/checluster_types.go b/api/v1/checluster_types.go index f86456414..8527e21f7 100644 --- a/api/v1/checluster_types.go +++ b/api/v1/checluster_types.go @@ -499,6 +499,15 @@ type CheClusterSpecAuth struct { // Name of the secret set in the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated when left blank. See also the `OAuthClientName` field. // +optional OAuthSecret string `json:"oAuthSecret,omitempty"` + // Access Token Scope. + // This field is specific to Che installations made for Kubernetes only and ignored for OpenShift. + // +optional + OAuthScope string `json:"oAuthScope,omitempty"` + // Identity token to be passed to upstream. There are two types of tokens supported: `id_token` and `access_token`. + // Default value is `id_token`. + // This field is specific to Che installations made for Kubernetes only and ignored for OpenShift. + // +optional + IdentityToken string `json:"identityToken,omitempty"` // Deprecated. The value of this flag is ignored. // Overrides the container image used in the Identity Provider, Keycloak or RH-SSO, deployment. // This includes the image tag. Omit it or leave it empty to use the default container image provided by the Operator. diff --git a/api/v2/checluster_types.go b/api/v2/checluster_types.go index cd9c443ab..1bcb4e3a7 100644 --- a/api/v2/checluster_types.go +++ b/api/v2/checluster_types.go @@ -19,6 +19,9 @@ import ( "os" "strings" + "github.com/devfile/devworkspace-operator/pkg/infrastructure" + "github.com/eclipse-che/che-operator/pkg/common/constants" + "k8s.io/apimachinery/pkg/api/resource" imagepullerv1alpha1 "github.com/che-incubator/kubernetes-image-puller-operator/api/v1alpha1" @@ -351,6 +354,16 @@ type Auth struct { OAuthClientName string `json:"oAuthClientName,omitempty"` // Name of the secret set in the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift side. OAuthSecret string `json:"oAuthSecret,omitempty"` + // Access Token Scope. + // This field is specific to Che installations made for Kubernetes only and ignored for OpenShift. + // +optional + OAuthScope string `json:"oAuthScope,omitempty"` + // Identity token to be passed to upstream. There are two types of tokens supported: `id_token` and `access_token`. + // Default value is `id_token`. + // This field is specific to Che installations made for Kubernetes only and ignored for OpenShift. + // +optional + // +kubebuilder:validation:Enum=id_token;access_token + IdentityToken string `json:"identityToken,omitempty"` // Gateway settings. // +optional Gateway Gateway `json:"gateway,omitempty"` @@ -631,3 +644,18 @@ func (c *CheCluster) GetDefaultNamespace() string { return "-" + os.Getenv("CHE_FLAVOR") } + +func (c *CheCluster) GetIdentityToken() string { + if len(c.Spec.Networking.Auth.IdentityToken) > 0 { + return c.Spec.Networking.Auth.IdentityToken + } + + if infrastructure.IsOpenShift() { + return constants.AccessToken + } + return constants.IdToken +} + +func (c *CheCluster) IsAccessTokenConfigured() bool { + return c.GetIdentityToken() == constants.AccessToken +} diff --git a/api/v2/checluster_types_test.go b/api/v2/checluster_types_test.go new file mode 100644 index 000000000..3cd45f613 --- /dev/null +++ b/api/v2/checluster_types_test.go @@ -0,0 +1,153 @@ +// +// Copyright (c) 2019-2021 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 v2 + +import ( + "reflect" + "testing" + + "github.com/devfile/devworkspace-operator/pkg/infrastructure" + "github.com/stretchr/testify/assert" +) + +func TestIsAccesTokenConfigured(t *testing.T) { + t.Run("TestIsAccesTokenConfigured when access_token defined", func(t *testing.T) { + cheCluster := &CheCluster{ + Spec: CheClusterSpec{ + Networking: CheClusterSpecNetworking{ + Auth: Auth{ + IdentityToken: "access_token", + }, + }}, + } + assert.True(t, cheCluster.IsAccessTokenConfigured(), "'access_token' should be activated") + }) + t.Run("TestIsAccesTokenConfigured when id_token defined", func(t *testing.T) { + cheCluster := &CheCluster{ + Spec: CheClusterSpec{ + Networking: CheClusterSpecNetworking{ + Auth: Auth{ + IdentityToken: "id_token", + }, + }}, + } + assert.False(t, cheCluster.IsAccessTokenConfigured(), "'access_token' should not be activated") + }) +} + +func TestGetIdentityToken(t *testing.T) { + t.Run("TestGetIdentityToken when access_token defined in config and k8s", func(t *testing.T) { + cheCluster := &CheCluster{ + Spec: CheClusterSpec{ + Networking: CheClusterSpecNetworking{ + Auth: Auth{ + IdentityToken: "access_token", + }, + }}, + } + infrastructure.InitializeForTesting(infrastructure.Kubernetes) + + assert.Equal(t, "access_token", cheCluster.GetIdentityToken(), + "'access_token' should be used") + }) + + t.Run("TestGetIdentityToken when id_token defined in config and k8s", func(t *testing.T) { + cheCluster := &CheCluster{ + Spec: CheClusterSpec{ + Networking: CheClusterSpecNetworking{ + Auth: Auth{ + IdentityToken: "id_token", + }, + }}, + } + infrastructure.InitializeForTesting(infrastructure.Kubernetes) + + assert.Equal(t, "id_token", cheCluster.GetIdentityToken(), + "'id_token' should be used") + }) + + t.Run("TestGetIdentityToken when no defined token in config and k8s", func(t *testing.T) { + cheCluster := &CheCluster{ + Spec: CheClusterSpec{ + Networking: CheClusterSpecNetworking{ + Auth: Auth{}, + }}, + } + infrastructure.InitializeForTesting(infrastructure.Kubernetes) + + assert.Equal(t, "id_token", cheCluster.GetIdentityToken(), + "'id_token' should be used") + }) + + t.Run("TestGetIdentityToken when access_token defined in config and openshift", func(t *testing.T) { + cheCluster := &CheCluster{ + Spec: CheClusterSpec{ + Networking: CheClusterSpecNetworking{ + Auth: Auth{ + IdentityToken: "access_token", + }, + }}, + } + infrastructure.InitializeForTesting(infrastructure.OpenShiftv4) + + assert.Equal(t, "access_token", cheCluster.GetIdentityToken(), + "'access_token' should be used") + }) + + t.Run("TestGetIdentityToken when id_token defined in config and openshift", func(t *testing.T) { + cheCluster := &CheCluster{ + Spec: CheClusterSpec{ + Networking: CheClusterSpecNetworking{ + Auth: Auth{ + IdentityToken: "id_token", + }, + }}, + } + infrastructure.InitializeForTesting(infrastructure.OpenShiftv4) + + assert.Equal(t, "id_token", cheCluster.GetIdentityToken(), + "'id_token' should be used") + }) + + t.Run("TestGetIdentityToken when no defined token in config and openshift", func(t *testing.T) { + cheCluster := &CheCluster{ + Spec: CheClusterSpec{ + Networking: CheClusterSpecNetworking{ + Auth: Auth{}, + }}, + } + infrastructure.InitializeForTesting(infrastructure.OpenShiftv4) + + assert.Equal(t, "access_token", cheCluster.GetIdentityToken(), + "'access_token' should be used") + }) + +} + +func TestGetDefaultIdentityToken(t *testing.T) { + emptyCheCluster := CheCluster{} + + var tests = []struct { + infrastructure infrastructure.Type + identityToken string + }{ + {infrastructure.OpenShiftv4, "access_token"}, + {infrastructure.Kubernetes, "id_token"}, + {infrastructure.Unsupported, "id_token"}, + } + for _, test := range tests { + infrastructure.InitializeForTesting(test.infrastructure) + if actual := emptyCheCluster.GetIdentityToken(); !reflect.DeepEqual(test.identityToken, actual) { + t.Errorf("Test Failed. Expected '%s', but got '%s'", test.identityToken, actual) + } + } +} diff --git a/bundle/next/eclipse-che-preview-openshift/manifests/org.eclipse.che_checlusters.yaml b/bundle/next/eclipse-che-preview-openshift/manifests/org.eclipse.che_checlusters.yaml index 4e6f3a69a..7d5175ff0 100644 --- a/bundle/next/eclipse-che-preview-openshift/manifests/org.eclipse.che_checlusters.yaml +++ b/bundle/next/eclipse-che-preview-openshift/manifests/org.eclipse.che_checlusters.yaml @@ -256,6 +256,12 @@ spec: field. By default, this will be automatically calculated and set by the Operator. type: string + identityToken: + description: 'Identity token to be passed to upstream. There + are two types of tokens supported: `id_token` and `access_token`. + Default value is `id_token`. This field is specific to Che + installations made for Kubernetes only and ignored for OpenShift.' + type: string initialOpenShiftOAuthUser: description: Deprecated. The value of this flag is ignored. For operating with the OpenShift OAuth authentication, create @@ -279,6 +285,10 @@ spec: to setup identity federation on the OpenShift side. Auto-generated when left blank. See also the `OpenShiftoAuth` field. type: string + oAuthScope: + description: Access Token Scope. This field is specific to Che + installations made for Kubernetes only and ignored for OpenShift. + type: string oAuthSecret: description: Name of the secret set in the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift @@ -2287,10 +2297,25 @@ spec: identityProviderURL: description: Public URL of the Identity Provider server. type: string + identityToken: + description: 'Identity token to be passed to upstream. There + are two types of tokens supported: `id_token` and `access_token`. + Default value is `id_token`. This field is specific to + Che installations made for Kubernetes only and ignored + for OpenShift.' + enum: + - id_token + - access_token + type: string oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift side. type: string + oAuthScope: + description: Access Token Scope. This field is specific + to Che installations made for Kubernetes only and ignored + for OpenShift. + type: string oAuthSecret: description: Name of the secret set in the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift diff --git a/config/crd/bases/org.eclipse.che_checlusters.yaml b/config/crd/bases/org.eclipse.che_checlusters.yaml index 06d6e2b02..878b3a91d 100644 --- a/config/crd/bases/org.eclipse.che_checlusters.yaml +++ b/config/crd/bases/org.eclipse.che_checlusters.yaml @@ -234,6 +234,12 @@ spec: By default, this will be automatically calculated and set by the Operator. type: string + identityToken: + description: 'Identity token to be passed to upstream. There are + two types of tokens supported: `id_token` and `access_token`. + Default value is `id_token`. This field is specific to Che installations + made for Kubernetes only and ignored for OpenShift.' + type: string initialOpenShiftOAuthUser: description: Deprecated. The value of this flag is ignored. For operating with the OpenShift OAuth authentication, create a @@ -257,6 +263,10 @@ spec: to setup identity federation on the OpenShift side. Auto-generated when left blank. See also the `OpenShiftoAuth` field. type: string + oAuthScope: + description: Access Token Scope. This field is specific to Che + installations made for Kubernetes only and ignored for OpenShift. + type: string oAuthSecret: description: Name of the secret set in the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift @@ -2232,10 +2242,24 @@ spec: identityProviderURL: description: Public URL of the Identity Provider server. type: string + identityToken: + description: 'Identity token to be passed to upstream. There + are two types of tokens supported: `id_token` and `access_token`. + Default value is `id_token`. This field is specific to Che + installations made for Kubernetes only and ignored for OpenShift.' + enum: + - id_token + - access_token + type: string oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift side. type: string + oAuthScope: + description: Access Token Scope. This field is specific to + Che installations made for Kubernetes only and ignored for + OpenShift. + type: string oAuthSecret: description: Name of the secret set in the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift diff --git a/controllers/devworkspace/solver/che_routing.go b/controllers/devworkspace/solver/che_routing.go index 0c1fb74a3..5cc1826e0 100644 --- a/controllers/devworkspace/solver/che_routing.go +++ b/controllers/devworkspace/solver/che_routing.go @@ -475,9 +475,7 @@ func provisionMainWorkspaceRoute(cheCluster *chev2.CheCluster, routing *dwo.DevW getServiceURL(wsGatewayPort, dwId, dwNamespace), []string{"/" + dwId}) - if infrastructure.IsOpenShift() { - // on OpenShift, we need to set authorization header. - // This MUST come before Auth, because Auth needs Authorization header to be properly set. + if cheCluster.IsAccessTokenConfigured() { cfg.AddAuthHeaderRewrite(dwId) } diff --git a/deploy/deployment/kubernetes/combined.yaml b/deploy/deployment/kubernetes/combined.yaml index c557723bf..061c8ac11 100644 --- a/deploy/deployment/kubernetes/combined.yaml +++ b/deploy/deployment/kubernetes/combined.yaml @@ -161,6 +161,9 @@ spec: identityProviderURL: description: Public URL of the Identity Provider server (Keycloak / RH-SSO server). Set this ONLY when a use of an external Identity Provider is needed. See the `externalIdentityProvider` field. By default, this will be automatically calculated and set by the Operator. type: string + identityToken: + description: 'Identity token to be passed to upstream. There are two types of tokens supported: `id_token` and `access_token`. Default value is `id_token`. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift.' + type: string initialOpenShiftOAuthUser: description: Deprecated. The value of this flag is ignored. For operating with the OpenShift OAuth authentication, create a new user account since the kubeadmin can not be used. If the value is true, then a new OpenShift OAuth user will be created for the HTPasswd identity provider. If the value is false and the user has already been created, then it will be removed. If value is an empty, then do nothing. The user's credentials are stored in the `openshift-oauth-user-credentials` secret in 'openshift-config' namespace by Operator. Note that this solution is Openshift 4 platform-specific. type: boolean @@ -170,6 +173,9 @@ spec: oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated when left blank. See also the `OpenShiftoAuth` field. type: string + oAuthScope: + description: Access Token Scope. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift. + type: string oAuthSecret: description: Name of the secret set in the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated when left blank. See also the `OAuthClientName` field. type: string @@ -1581,9 +1587,18 @@ spec: identityProviderURL: description: Public URL of the Identity Provider server. type: string + identityToken: + description: 'Identity token to be passed to upstream. There are two types of tokens supported: `id_token` and `access_token`. Default value is `id_token`. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift.' + enum: + - id_token + - access_token + type: string oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift side. type: string + oAuthScope: + description: Access Token Scope. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift. + type: string oAuthSecret: description: Name of the secret set in the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift side. type: string diff --git a/deploy/deployment/kubernetes/objects/checlusters.org.eclipse.che.CustomResourceDefinition.yaml b/deploy/deployment/kubernetes/objects/checlusters.org.eclipse.che.CustomResourceDefinition.yaml index ae77b4065..a1d522d43 100644 --- a/deploy/deployment/kubernetes/objects/checlusters.org.eclipse.che.CustomResourceDefinition.yaml +++ b/deploy/deployment/kubernetes/objects/checlusters.org.eclipse.che.CustomResourceDefinition.yaml @@ -156,6 +156,9 @@ spec: identityProviderURL: description: Public URL of the Identity Provider server (Keycloak / RH-SSO server). Set this ONLY when a use of an external Identity Provider is needed. See the `externalIdentityProvider` field. By default, this will be automatically calculated and set by the Operator. type: string + identityToken: + description: 'Identity token to be passed to upstream. There are two types of tokens supported: `id_token` and `access_token`. Default value is `id_token`. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift.' + type: string initialOpenShiftOAuthUser: description: Deprecated. The value of this flag is ignored. For operating with the OpenShift OAuth authentication, create a new user account since the kubeadmin can not be used. If the value is true, then a new OpenShift OAuth user will be created for the HTPasswd identity provider. If the value is false and the user has already been created, then it will be removed. If value is an empty, then do nothing. The user's credentials are stored in the `openshift-oauth-user-credentials` secret in 'openshift-config' namespace by Operator. Note that this solution is Openshift 4 platform-specific. type: boolean @@ -165,6 +168,9 @@ spec: oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated when left blank. See also the `OpenShiftoAuth` field. type: string + oAuthScope: + description: Access Token Scope. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift. + type: string oAuthSecret: description: Name of the secret set in the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated when left blank. See also the `OAuthClientName` field. type: string @@ -1576,9 +1582,18 @@ spec: identityProviderURL: description: Public URL of the Identity Provider server. type: string + identityToken: + description: 'Identity token to be passed to upstream. There are two types of tokens supported: `id_token` and `access_token`. Default value is `id_token`. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift.' + enum: + - id_token + - access_token + type: string oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift side. type: string + oAuthScope: + description: Access Token Scope. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift. + type: string oAuthSecret: description: Name of the secret set in the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift side. type: string diff --git a/deploy/deployment/openshift/combined.yaml b/deploy/deployment/openshift/combined.yaml index afe896273..c99853ae4 100644 --- a/deploy/deployment/openshift/combined.yaml +++ b/deploy/deployment/openshift/combined.yaml @@ -161,6 +161,9 @@ spec: identityProviderURL: description: Public URL of the Identity Provider server (Keycloak / RH-SSO server). Set this ONLY when a use of an external Identity Provider is needed. See the `externalIdentityProvider` field. By default, this will be automatically calculated and set by the Operator. type: string + identityToken: + description: 'Identity token to be passed to upstream. There are two types of tokens supported: `id_token` and `access_token`. Default value is `id_token`. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift.' + type: string initialOpenShiftOAuthUser: description: Deprecated. The value of this flag is ignored. For operating with the OpenShift OAuth authentication, create a new user account since the kubeadmin can not be used. If the value is true, then a new OpenShift OAuth user will be created for the HTPasswd identity provider. If the value is false and the user has already been created, then it will be removed. If value is an empty, then do nothing. The user's credentials are stored in the `openshift-oauth-user-credentials` secret in 'openshift-config' namespace by Operator. Note that this solution is Openshift 4 platform-specific. type: boolean @@ -170,6 +173,9 @@ spec: oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated when left blank. See also the `OpenShiftoAuth` field. type: string + oAuthScope: + description: Access Token Scope. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift. + type: string oAuthSecret: description: Name of the secret set in the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated when left blank. See also the `OAuthClientName` field. type: string @@ -1581,9 +1587,18 @@ spec: identityProviderURL: description: Public URL of the Identity Provider server. type: string + identityToken: + description: 'Identity token to be passed to upstream. There are two types of tokens supported: `id_token` and `access_token`. Default value is `id_token`. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift.' + enum: + - id_token + - access_token + type: string oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift side. type: string + oAuthScope: + description: Access Token Scope. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift. + type: string oAuthSecret: description: Name of the secret set in the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift side. type: string diff --git a/deploy/deployment/openshift/objects/checlusters.org.eclipse.che.CustomResourceDefinition.yaml b/deploy/deployment/openshift/objects/checlusters.org.eclipse.che.CustomResourceDefinition.yaml index c9f8ae381..f5b13ee84 100644 --- a/deploy/deployment/openshift/objects/checlusters.org.eclipse.che.CustomResourceDefinition.yaml +++ b/deploy/deployment/openshift/objects/checlusters.org.eclipse.che.CustomResourceDefinition.yaml @@ -156,6 +156,9 @@ spec: identityProviderURL: description: Public URL of the Identity Provider server (Keycloak / RH-SSO server). Set this ONLY when a use of an external Identity Provider is needed. See the `externalIdentityProvider` field. By default, this will be automatically calculated and set by the Operator. type: string + identityToken: + description: 'Identity token to be passed to upstream. There are two types of tokens supported: `id_token` and `access_token`. Default value is `id_token`. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift.' + type: string initialOpenShiftOAuthUser: description: Deprecated. The value of this flag is ignored. For operating with the OpenShift OAuth authentication, create a new user account since the kubeadmin can not be used. If the value is true, then a new OpenShift OAuth user will be created for the HTPasswd identity provider. If the value is false and the user has already been created, then it will be removed. If value is an empty, then do nothing. The user's credentials are stored in the `openshift-oauth-user-credentials` secret in 'openshift-config' namespace by Operator. Note that this solution is Openshift 4 platform-specific. type: boolean @@ -165,6 +168,9 @@ spec: oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated when left blank. See also the `OpenShiftoAuth` field. type: string + oAuthScope: + description: Access Token Scope. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift. + type: string oAuthSecret: description: Name of the secret set in the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated when left blank. See also the `OAuthClientName` field. type: string @@ -1576,9 +1582,18 @@ spec: identityProviderURL: description: Public URL of the Identity Provider server. type: string + identityToken: + description: 'Identity token to be passed to upstream. There are two types of tokens supported: `id_token` and `access_token`. Default value is `id_token`. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift.' + enum: + - id_token + - access_token + type: string oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift side. type: string + oAuthScope: + description: Access Token Scope. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift. + type: string oAuthSecret: description: Name of the secret set in the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift side. type: string diff --git a/helmcharts/next/crds/checlusters.org.eclipse.che.CustomResourceDefinition.yaml b/helmcharts/next/crds/checlusters.org.eclipse.che.CustomResourceDefinition.yaml index ae77b4065..a1d522d43 100644 --- a/helmcharts/next/crds/checlusters.org.eclipse.che.CustomResourceDefinition.yaml +++ b/helmcharts/next/crds/checlusters.org.eclipse.che.CustomResourceDefinition.yaml @@ -156,6 +156,9 @@ spec: identityProviderURL: description: Public URL of the Identity Provider server (Keycloak / RH-SSO server). Set this ONLY when a use of an external Identity Provider is needed. See the `externalIdentityProvider` field. By default, this will be automatically calculated and set by the Operator. type: string + identityToken: + description: 'Identity token to be passed to upstream. There are two types of tokens supported: `id_token` and `access_token`. Default value is `id_token`. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift.' + type: string initialOpenShiftOAuthUser: description: Deprecated. The value of this flag is ignored. For operating with the OpenShift OAuth authentication, create a new user account since the kubeadmin can not be used. If the value is true, then a new OpenShift OAuth user will be created for the HTPasswd identity provider. If the value is false and the user has already been created, then it will be removed. If value is an empty, then do nothing. The user's credentials are stored in the `openshift-oauth-user-credentials` secret in 'openshift-config' namespace by Operator. Note that this solution is Openshift 4 platform-specific. type: boolean @@ -165,6 +168,9 @@ spec: oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated when left blank. See also the `OpenShiftoAuth` field. type: string + oAuthScope: + description: Access Token Scope. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift. + type: string oAuthSecret: description: Name of the secret set in the OpenShift `OAuthClient` resource used to setup identity federation on the OpenShift side. Auto-generated when left blank. See also the `OAuthClientName` field. type: string @@ -1576,9 +1582,18 @@ spec: identityProviderURL: description: Public URL of the Identity Provider server. type: string + identityToken: + description: 'Identity token to be passed to upstream. There are two types of tokens supported: `id_token` and `access_token`. Default value is `id_token`. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift.' + enum: + - id_token + - access_token + type: string oAuthClientName: description: Name of the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift side. type: string + oAuthScope: + description: Access Token Scope. This field is specific to Che installations made for Kubernetes only and ignored for OpenShift. + type: string oAuthSecret: description: Name of the secret set in the OpenShift `OAuthClient` resource used to set up identity federation on the OpenShift side. type: string diff --git a/pkg/common/constants/constants.go b/pkg/common/constants/constants.go index 2f9115d18..a935a1f53 100644 --- a/pkg/common/constants/constants.go +++ b/pkg/common/constants/constants.go @@ -71,6 +71,8 @@ const ( GitLabOAuthConfigClientIdFileName = "id" GitLabOAuthConfigClientSecretFileName = "secret" OAuthScmConfiguration = "oauth-scm-configuration" + AccessToken = "access_token" + IdToken = "id_token" // Labels KubernetesComponentLabelKey = "app.kubernetes.io/component" diff --git a/pkg/common/utils/utils.go b/pkg/common/utils/utils.go index 2279116f7..a677a3379 100644 --- a/pkg/common/utils/utils.go +++ b/pkg/common/utils/utils.go @@ -259,3 +259,13 @@ func FormatLabels(m map[string]string) string { return labels.FormatLabels(m) } + +// Whitelists the host. +// Sample: Whitelist("che.yourcompany.com") -> ".yourcompany.com" +func Whitelist(hostname string) (value string) { + i := strings.Index(hostname, ".") + if i > -1 { + return hostname[i:] + } + return hostname +} diff --git a/pkg/common/utils/utils_test.go b/pkg/common/utils/utils_test.go index 3ff589b35..f43985c49 100644 --- a/pkg/common/utils/utils_test.go +++ b/pkg/common/utils/utils_test.go @@ -63,3 +63,21 @@ func TestGetImageNameAndTag(t *testing.T) { } } } + +func TestWhitelist(t *testing.T) { + var tests = []struct { + host string + whitelistedHost string + }{ + {"che.qwruwqlrj.com", ".qwruwqlrj.com"}, + {"one.two.three.four", ".two.three.four"}, + {"abraCadabra-KvakaZybra", "abraCadabra-KvakaZybra"}, + {".", "."}, + {"", ""}, + } + for _, test := range tests { + if actual := Whitelist(test.host); !reflect.DeepEqual(test.whitelistedHost, actual) { + t.Errorf("Test Failed. Expected '%s', but got '%s'", test.whitelistedHost, actual) + } + } +} diff --git a/pkg/deploy/dashboard/dashboard.go b/pkg/deploy/dashboard/dashboard.go index b19340ee3..68b2aca7c 100644 --- a/pkg/deploy/dashboard/dashboard.go +++ b/pkg/deploy/dashboard/dashboard.go @@ -18,7 +18,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "github.com/devfile/devworkspace-operator/pkg/infrastructure" "github.com/eclipse-che/che-operator/pkg/common/chetypes" defaults "github.com/eclipse-che/che-operator/pkg/common/operator-defaults" "github.com/eclipse-che/che-operator/pkg/deploy" @@ -126,7 +125,7 @@ func (d *DashboardReconciler) createGatewayConfig(ctx *chetypes.DeployContext) * 10, "http://"+d.getComponentName(ctx)+":8080", []string{}) - if infrastructure.IsOpenShift() { + if ctx.CheCluster.IsAccessTokenConfigured() { cfg.AddAuthHeaderRewrite(d.getComponentName(ctx)) } return cfg diff --git a/pkg/deploy/gateway/gateway.go b/pkg/deploy/gateway/gateway.go index 1e27c9a30..1b44c1e20 100644 --- a/pkg/deploy/gateway/gateway.go +++ b/pkg/deploy/gateway/gateway.go @@ -127,7 +127,7 @@ func syncAll(deployContext *chetypes.DeployContext) error { return err } - if infrastructure.IsOpenShift() { + if instance.IsAccessTokenConfigured() { if headerRewritePluginConfig, err := getGatewayHeaderRewritePluginConfigSpec(instance); err == nil { if _, err := deploy.Sync(deployContext, headerRewritePluginConfig, configMapDiffOpts); err != nil { return err @@ -238,8 +238,10 @@ func getGatewayServerConfigSpec(deployContext *chetypes.DeployContext) (corev1.C "http://"+deploy.CheServiceName+":8080", []string{}) - if infrastructure.IsOpenShift() { + if deployContext.CheCluster.IsAccessTokenConfigured() { cfg.AddAuthHeaderRewrite(serverComponentName) + } + if infrastructure.IsOpenShift() { // native user mode is currently only available on OpenShift but let's be defensive here so that // this doesn't break once we enable it on Kubernetes, too. Token check will have to work // differently on Kuberentes. @@ -398,7 +400,7 @@ providers: log: level: "INFO"`, traefikPort) - if infrastructure.IsOpenShift() { + if instance.IsAccessTokenConfigured() { data += ` experimental: localPlugins: @@ -546,7 +548,7 @@ func getTraefikContainerVolumeMounts(instance *chev2.CheCluster) []corev1.Volume MountPath: "/dynamic-config", }, } - if infrastructure.IsOpenShift() { + if instance.IsAccessTokenConfigured() { mounts = append(mounts, corev1.VolumeMount{ Name: "header-rewrite-traefik-plugin", MountPath: "/plugins-local/src/github.com/che-incubator/header-rewrite-traefik-plugin", @@ -580,7 +582,7 @@ func getVolumesSpec(instance *chev2.CheCluster) []corev1.Volume { getOauthProxyConfigVolume(), getKubeRbacProxyConfigVolume()) - if infrastructure.IsOpenShift() { + if instance.IsAccessTokenConfigured() { volumes = append(volumes, corev1.Volume{ Name: "header-rewrite-traefik-plugin", VolumeSource: corev1.VolumeSource{ diff --git a/pkg/deploy/gateway/oauth_proxy.go b/pkg/deploy/gateway/oauth_proxy.go index 27cf0dd18..7bbea41c4 100644 --- a/pkg/deploy/gateway/oauth_proxy.go +++ b/pkg/deploy/gateway/oauth_proxy.go @@ -26,6 +26,7 @@ import ( "github.com/eclipse-che/che-operator/pkg/common/chetypes" "github.com/eclipse-che/che-operator/pkg/common/constants" defaults "github.com/eclipse-che/che-operator/pkg/common/operator-defaults" + "github.com/eclipse-che/che-operator/pkg/common/utils" "github.com/eclipse-che/che-operator/pkg/deploy" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -114,8 +115,11 @@ cookie_secret = "%s" cookie_expire = "24h0m0s" email_domains = "*" cookie_httponly = false -pass_authorization_header = true skip_provider_button = true +whitelist_domains = "%s" +cookie_domains = "%s" +%s +%s %s `, GatewayServicePort, ctx.CheHost, @@ -123,7 +127,11 @@ skip_provider_button = true ctx.CheCluster.Spec.Networking.Auth.OAuthClientName, ctx.CheCluster.Spec.Networking.Auth.OAuthSecret, cookieSecret, - skipAuthConfig(ctx.CheCluster)) + utils.Whitelist(ctx.CheHost), + utils.Whitelist(ctx.CheHost), + skipAuthConfig(ctx.CheCluster), + identityTokenConfig(ctx.CheCluster), + oauthScopeConfig(ctx.CheCluster)) } func skipAuthConfig(instance *chev2.CheCluster) string { @@ -147,6 +155,23 @@ func skipAuthConfig(instance *chev2.CheCluster) string { return "" } +func identityTokenConfig(instance *chev2.CheCluster) string { + if instance.IsAccessTokenConfigured() { + // pass OAuth access_token to upstream via X-Forwarded-Access-Token header + return "pass_access_token = true" + } + // pass OIDC IDToken to upstream via Authorization Bearer header + return "pass_authorization_header = true" +} + +func oauthScopeConfig(instance *chev2.CheCluster) string { + scope := instance.Spec.Networking.Auth.OAuthScope + if len(scope) > 1 { + return fmt.Sprintf("scope = \"%s\"", scope) + } + return "" +} + func getOauthProxyContainerSpec(ctx *chetypes.DeployContext) corev1.Container { // append env var with ConfigMap revision to restore pod automatically when config has been changed cm := &corev1.ConfigMap{} diff --git a/pkg/deploy/gateway/oauth_proxy_test.go b/pkg/deploy/gateway/oauth_proxy_test.go new file mode 100644 index 000000000..8f009eceb --- /dev/null +++ b/pkg/deploy/gateway/oauth_proxy_test.go @@ -0,0 +1,83 @@ +// +// Copyright (c) 2019-2021 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 ( + "testing" + + "github.com/devfile/devworkspace-operator/pkg/infrastructure" + chev2 "github.com/eclipse-che/che-operator/api/v2" + "github.com/eclipse-che/che-operator/pkg/common/test" + "github.com/stretchr/testify/assert" +) + +func TestKubernetesOauthProxyConfig(t *testing.T) { + ctx := test.GetDeployContext( + &chev2.CheCluster{ + Spec: chev2.CheClusterSpec{ + Networking: chev2.CheClusterSpecNetworking{ + Auth: chev2.Auth{ + IdentityProviderURL: "http://bla.bla.bla/idp", + OAuthClientName: "client name", + OAuthSecret: "secret", + }, + }}, + }, nil) + ctx.CheHost = "che-site.che-domain.com" + infrastructure.InitializeForTesting(infrastructure.Kubernetes) + + config := kubernetesOauthProxyConfig(ctx, "blabol") + assert.Contains(t, config, "pass_authorization_header = true") + assert.Contains(t, config, "whitelist_domains = \".che-domain.com\"") + assert.Contains(t, config, "cookie_domains = \".che-domain.com\"") + assert.NotContains(t, config, "scope = ") + assert.NotContains(t, config, "pass_access_token = true") +} + +func TestScopeDefinedForKubernetesOauthProxyConfig(t *testing.T) { + ctx := test.GetDeployContext( + &chev2.CheCluster{ + Spec: chev2.CheClusterSpec{ + Networking: chev2.CheClusterSpecNetworking{ + Auth: chev2.Auth{ + IdentityProviderURL: "http://bla.bla.bla/idp", + OAuthClientName: "client name", + OAuthSecret: "secret", + OAuthScope: "scope1 scope2 scope3 scope4 scope5", + }, + }}, + }, nil) + infrastructure.InitializeForTesting(infrastructure.Kubernetes) + + config := kubernetesOauthProxyConfig(ctx, "blabol") + assert.Contains(t, config, "scope = \"scope1 scope2 scope3 scope4 scope5\"") +} + +func TestAccessTokenDefinedForKubernetesOauthProxyConfig(t *testing.T) { + ctx := test.GetDeployContext( + &chev2.CheCluster{ + Spec: chev2.CheClusterSpec{ + Networking: chev2.CheClusterSpecNetworking{ + Auth: chev2.Auth{ + IdentityProviderURL: "http://bla.bla.bla/idp", + OAuthClientName: "client name", + OAuthSecret: "secret", + IdentityToken: "access_token", + }, + }}, + }, nil) + infrastructure.InitializeForTesting(infrastructure.Kubernetes) + + config := kubernetesOauthProxyConfig(ctx, "blabol") + assert.Contains(t, config, "pass_access_token = true") + assert.NotContains(t, config, "pass_authorization_header = true") +}