feat: native auth kube (#1185)

Signed-off-by: Michal Vala <mvala@redhat.com>
pull/1219/head
Michal Vala 2021-12-03 12:20:46 +01:00 committed by GitHub
parent 25c151ba56
commit 88cb1ed7ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 412 additions and 252 deletions

View File

@ -126,7 +126,7 @@ metadata:
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
repository: https://github.com/eclipse-che/che-operator
support: Eclipse Foundation
name: eclipse-che-preview-openshift.v7.40.0-380.next-all-namespaces
name: eclipse-che-preview-openshift.v7.40.0-381.next-all-namespaces
namespace: placeholder
spec:
apiservicedefinitions: {}
@ -1134,6 +1134,10 @@ spec:
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_authentication_sidecar_k8s
value: quay.io/oauth2-proxy/oauth2-proxy:v7.2.0
- name: RELATED_IMAGE_gateway_authorization_sidecar_k8s
value: quay.io/brancz/kube-rbac-proxy:v0.11.0
- name: RELATED_IMAGE_gateway_header_sidecar
value: quay.io/che-incubator/header-rewrite-proxy:latest
- name: CHE_FLAVOR
@ -1439,4 +1443,4 @@ spec:
maturity: stable
provider:
name: Eclipse Foundation
version: 7.40.0-380.next-all-namespaces
version: 7.40.0-381.next-all-namespaces

View File

@ -133,7 +133,7 @@ metadata:
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
repository: https://github.com/eclipse-che/che-operator
support: Eclipse Foundation
name: eclipse-che-preview-kubernetes.v7.40.0-380.next
name: eclipse-che-preview-kubernetes.v7.40.0-381.next
namespace: placeholder
spec:
apiservicedefinitions: {}
@ -1123,6 +1123,10 @@ spec:
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_authentication_sidecar_k8s
value: quay.io/oauth2-proxy/oauth2-proxy:v7.2.0
- name: RELATED_IMAGE_gateway_authorization_sidecar_k8s
value: quay.io/brancz/kube-rbac-proxy:v0.11.0
- name: RELATED_IMAGE_gateway_header_sidecar
value: quay.io/che-incubator/header-rewrite-proxy:latest
- name: CHE_FLAVOR
@ -1406,4 +1410,4 @@ spec:
maturity: stable
provider:
name: Eclipse Foundation
version: 7.40.0-380.next
version: 7.40.0-381.next

View File

@ -126,7 +126,7 @@ metadata:
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
repository: https://github.com/eclipse-che/che-operator
support: Eclipse Foundation
name: eclipse-che-preview-openshift.v7.40.0-380.next
name: eclipse-che-preview-openshift.v7.40.0-381.next
namespace: placeholder
spec:
apiservicedefinitions: {}
@ -1134,6 +1134,10 @@ spec:
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_authentication_sidecar_k8s
value: quay.io/oauth2-proxy/oauth2-proxy:v7.2.0
- name: RELATED_IMAGE_gateway_authorization_sidecar_k8s
value: quay.io/brancz/kube-rbac-proxy:v0.11.0
- name: RELATED_IMAGE_gateway_header_sidecar
value: quay.io/che-incubator/header-rewrite-proxy:latest
- name: CHE_FLAVOR
@ -1439,4 +1443,4 @@ spec:
maturity: stable
provider:
name: Eclipse Foundation
version: 7.40.0-380.next
version: 7.40.0-381.next

View File

@ -97,6 +97,10 @@ spec:
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_authentication_sidecar_k8s
value: quay.io/oauth2-proxy/oauth2-proxy:v7.2.0
- name: RELATED_IMAGE_gateway_authorization_sidecar_k8s
value: quay.io/brancz/kube-rbac-proxy:v0.11.0
- name: RELATED_IMAGE_gateway_header_sidecar
value: quay.io/che-incubator/header-rewrite-proxy:latest
- name: CHE_FLAVOR

View File

@ -523,7 +523,7 @@ func (r *CheClusterReconciler) reconcileFinalizers(deployContext *deploy.DeployC
}
}
if util.IsOpenShift && deployContext.CheCluster.IsNativeUserModeEnabled() {
if deployContext.CheCluster.IsNativeUserModeEnabled() {
if _, err := r.reconcileGatewayPermissionsFinalizers(deployContext); err != nil {
logrus.Error(err)
}

View File

@ -106,11 +106,11 @@ func TestNativeUserModeEnabled(t *testing.T) {
expectedNativeUserValue: pointer.BoolPtr(true),
},
{
name: "che-operator should not modify nativeUserMode when not on openshift",
name: "che-operator should use nativeUserMode when devworkspaces on kubernetes and no initial value in CR for nativeUserMode",
isOpenshift: false,
devworkspaceEnabled: true,
initialNativeUserValue: nil,
expectedNativeUserValue: nil,
expectedNativeUserValue: pointer.BoolPtr(true),
},
{
name: "che-operator not modify nativeUserMode when devworkspace not enabled",

View File

@ -140,7 +140,7 @@ func (r *CheClusterReconciler) GenerateAndSaveFields(deployContext *deploy.Deplo
}
}
if !util.IsOpenShift || !deployContext.CheCluster.IsNativeUserModeEnabled() {
if !deployContext.CheCluster.IsNativeUserModeEnabled() {
keycloakRealm := util.GetValue(deployContext.CheCluster.Spec.Auth.IdentityProviderRealm, cheFlavor)
if len(deployContext.CheCluster.Spec.Auth.IdentityProviderRealm) < 1 {
deployContext.CheCluster.Spec.Auth.IdentityProviderRealm = keycloakRealm
@ -249,7 +249,7 @@ func (r *CheClusterReconciler) GenerateAndSaveFields(deployContext *deploy.Deplo
}
}
if util.IsOpenShift && deployContext.CheCluster.Spec.DevWorkspace.Enable && deployContext.CheCluster.Spec.Auth.NativeUserMode == nil {
if deployContext.CheCluster.Spec.DevWorkspace.Enable && deployContext.CheCluster.Spec.Auth.NativeUserMode == nil {
newNativeUserModeValue := util.NewBoolPointer(true)
deployContext.CheCluster.Spec.Auth.NativeUserMode = newNativeUserModeValue
if err := deploy.UpdateCheCRSpec(deployContext, "nativeUserMode", strconv.FormatBool(*newNativeUserModeValue)); err != nil {

View File

@ -16,7 +16,6 @@ import (
orgv1 "github.com/eclipse-che/che-operator/api/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"
)
@ -26,7 +25,7 @@ const (
)
func (r *CheClusterReconciler) reconcileGatewayPermissions(deployContext *deploy.DeployContext) (bool, error) {
if util.IsOpenShift && deployContext.CheCluster.IsNativeUserModeEnabled() {
if deployContext.CheCluster.IsNativeUserModeEnabled() {
name := gatewayPermisisonsName(deployContext.CheCluster)
if _, err := deploy.SyncClusterRoleToCluster(deployContext, name, getGatewayClusterRoleRules()); err != nil {
return false, err

View File

@ -449,14 +449,14 @@ func provisionMainWorkspaceRoute(cheCluster *v2alpha1.CheCluster, routing *dwo.D
// on OpenShift, we need to set authorization header.
// This MUST come before Auth, because Auth needs Authorization header to be properly set.
cfg.AddAuthHeaderRewrite(dwId)
// authorize against kube-rbac-proxy in che-gateway. This will be needed for k8s native auth as well.
cfg.AddAuth(dwId, "http://127.0.0.1:8089?namespace="+dwNamespace)
// make '/healthz' path of main endpoints reachable from outside
routeForHealthzEndpoint(cfg, dwId, routing.Spec.Endpoints)
}
// authorize against kube-rbac-proxy in che-gateway. This will be needed for k8s native auth as well.
cfg.AddAuth(dwId, "http://127.0.0.1:8089?namespace="+dwNamespace)
// make '/healthz' path of main endpoints reachable from outside
routeForHealthzEndpoint(cfg, dwId, routing.Spec.Endpoints)
if contents, err := yaml.Marshal(cfg); err != nil {
return nil, err
} else {
@ -514,13 +514,10 @@ func addEndpointToTraefikConfig(componentName string, e dw.Endpoint, cfg *gatewa
100,
fmt.Sprintf("http://127.0.0.1:%d", e.TargetPort),
[]string{prefix})
if util.IsOpenShift4 {
cfg.AddAuth(name, fmt.Sprintf("http://%s.%s:8089?namespace=%s", gateway.GatewayServiceName, cheCluster.Namespace, routing.Namespace))
}
cfg.AddAuth(name, fmt.Sprintf("http://%s.%s:8089?namespace=%s", gateway.GatewayServiceName, cheCluster.Namespace, routing.Namespace))
// we need to disable auth for '/healthz' path in main endpoint, for now only on OpenShift
if util.IsOpenShift4 &&
e.Attributes.GetString(string(dwo.TypeEndpointAttribute), nil) == string(dwo.MainEndpointType) {
if e.Attributes.GetString(string(dwo.TypeEndpointAttribute), nil) == string(dwo.MainEndpointType) {
healthzName := name + "-healthz"
healthzPath := prefix + "/healthz"
cfg.AddComponent(

View File

@ -267,9 +267,7 @@ func TestCreateRelocatedObjectsK8S(t *testing.T) {
cms := &corev1.ConfigMapList{}
cl.List(context.TODO(), cms)
if len(cms.Items) != 2 {
t.Errorf("there should be 2 configmaps created for the gateway config of the workspace but there were: %d", len(cms.Items))
}
assert.Len(t, cms.Items, 2)
var workspaceMainCfg *corev1.ConfigMap
var workspaceCfg *corev1.ConfigMap
@ -282,59 +280,74 @@ func TestCreateRelocatedObjectsK8S(t *testing.T) {
}
}
if workspaceMainCfg == nil {
t.Fatalf("traefik configuration for the workspace not found")
}
assert.NotNil(t, workspaceMainCfg)
traefikMainWorkspaceConfig := workspaceMainCfg.Data["wsid.yml"]
if len(traefikMainWorkspaceConfig) == 0 {
t.Fatal("No traefik config file found in the main workspace config configmap")
}
assert.NotEmpty(t, traefikMainWorkspaceConfig)
traefikWorkspaceConfig := workspaceCfg.Data["workspace.yml"]
if len(traefikWorkspaceConfig) == 0 {
t.Fatal("No traefik config file found in the workspace config configmap")
}
assert.NotEmpty(t, traefikWorkspaceConfig)
workspaceConfig := gateway.TraefikConfig{}
if err := yaml.Unmarshal([]byte(traefikWorkspaceConfig), &workspaceConfig); err != nil {
t.Fatal(err)
}
if len(workspaceConfig.HTTP.Routers) != 1 {
t.Fatalf("Expected 1 traefik router but got %d", len(workspaceConfig.HTTP.Routers))
}
assert.NoError(t, yaml.Unmarshal([]byte(traefikWorkspaceConfig), &workspaceConfig))
assert.Len(t, workspaceConfig.HTTP.Routers, 2)
wsid := "wsid-m1-9999"
if _, ok := workspaceConfig.HTTP.Routers[wsid]; !ok {
t.Fatal("traefik config doesn't contain expected workspace configuration")
}
if len(workspaceConfig.HTTP.Routers[wsid].Middlewares) != 1 {
t.Fatalf("Expected 1 middlewares in router but got '%d'", len(workspaceConfig.HTTP.Routers[wsid].Middlewares))
}
if len(workspaceConfig.HTTP.Middlewares) != 1 {
t.Fatalf("Expected 1 middlewares set but got '%d'", len(workspaceConfig.HTTP.Middlewares))
}
assert.Contains(t, workspaceConfig.HTTP.Routers, wsid)
assert.Len(t, workspaceConfig.HTTP.Routers[wsid].Middlewares, 2)
assert.Len(t, workspaceConfig.HTTP.Middlewares, 3)
mwares := []string{wsid + gateway.StripPrefixMiddlewareSuffix}
for _, mware := range mwares {
if _, ok := workspaceConfig.HTTP.Middlewares[mware]; !ok {
t.Fatalf("traefik config doesn't set middleware '%s'", mware)
}
assert.Contains(t, workspaceConfig.HTTP.Middlewares, mware)
found := false
for _, r := range workspaceConfig.HTTP.Routers[wsid].Middlewares {
if r == mware {
found = true
}
}
if !found {
t.Fatalf("traefik config route doesn't set middleware '%s'", mware)
}
assert.True(t, found)
}
workspaceMainConfig := gateway.TraefikConfig{}
assert.NoError(t, yaml.Unmarshal([]byte(traefikMainWorkspaceConfig), &workspaceMainConfig))
assert.Len(t, workspaceMainConfig.HTTP.Middlewares, 2)
wsid = "wsid"
mwares = []string{
wsid + gateway.AuthMiddlewareSuffix,
wsid + gateway.StripPrefixMiddlewareSuffix}
for _, mware := range mwares {
assert.Contains(t, workspaceMainConfig.HTTP.Middlewares, mware)
found := false
for _, r := range workspaceMainConfig.HTTP.Routers[wsid].Middlewares {
if r == mware {
found = true
}
}
assert.Truef(t, found, "traefik config route doesn't set middleware '%s'", mware)
}
t.Run("testHealthzEndpointInMainWorkspaceRoute", func(t *testing.T) {
healthzName := "9999-healthz"
assert.Contains(t, workspaceMainConfig.HTTP.Routers, healthzName)
assert.Equal(t, workspaceMainConfig.HTTP.Routers[healthzName].Service, wsid)
assert.Equal(t, workspaceMainConfig.HTTP.Routers[healthzName].Rule, "Path(`/wsid/m1/9999/healthz`)")
assert.NotContains(t, workspaceMainConfig.HTTP.Routers[healthzName].Middlewares, "wsid"+gateway.AuthMiddlewareSuffix)
assert.Contains(t, workspaceMainConfig.HTTP.Routers[healthzName].Middlewares, "wsid"+gateway.StripPrefixMiddlewareSuffix)
assert.NotContains(t, workspaceMainConfig.HTTP.Routers[healthzName].Middlewares, "wsid"+gateway.HeaderRewriteMiddlewareSuffix)
})
t.Run("testHealthzEndpointInWorkspaceRoute", func(t *testing.T) {
healthzName := "wsid-m1-9999-healthz"
assert.Contains(t, workspaceConfig.HTTP.Routers, healthzName)
assert.Equal(t, workspaceConfig.HTTP.Routers[healthzName].Service, healthzName)
assert.Equal(t, workspaceConfig.HTTP.Routers[healthzName].Rule, "Path(`/m1/9999/healthz`)")
assert.NotContains(t, workspaceConfig.HTTP.Routers[healthzName].Middlewares, healthzName+gateway.AuthMiddlewareSuffix)
assert.Contains(t, workspaceConfig.HTTP.Routers[healthzName].Middlewares, healthzName+gateway.StripPrefixMiddlewareSuffix)
})
})
}

View File

@ -97,6 +97,10 @@ spec:
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_authentication_sidecar_k8s
value: quay.io/oauth2-proxy/oauth2-proxy:v7.2.0
- name: RELATED_IMAGE_gateway_authorization_sidecar_k8s
value: quay.io/brancz/kube-rbac-proxy:v0.11.0
- name: RELATED_IMAGE_gateway_header_sidecar
value: quay.io/che-incubator/header-rewrite-proxy:latest
- name: CHE_FLAVOR

View File

@ -211,6 +211,8 @@ func InitDefaultsFromFile(defaultsPath string) {
// Don't get some k8s specific env
if !util.IsOpenShift {
defaultCheTLSSecretsCreationJobImage = util.GetDeploymentEnv(operatorDeployment, util.GetArchitectureDependentEnv("RELATED_IMAGE_che_tls_secrets_creation_job"))
defaultGatewayAuthenticationSidecarImage = util.GetDeploymentEnv(operatorDeployment, util.GetArchitectureDependentEnv("RELATED_IMAGE_gateway_authentication_sidecar_k8s"))
defaultGatewayAuthorizationSidecarImage = util.GetDeploymentEnv(operatorDeployment, util.GetArchitectureDependentEnv("RELATED_IMAGE_gateway_authorization_sidecar_k8s"))
}
}
@ -494,6 +496,8 @@ func InitDefaultsFromEnv() {
// Don't get some k8s specific env
if !util.IsOpenShift {
defaultCheTLSSecretsCreationJobImage = getDefaultFromEnv(util.GetArchitectureDependentEnv("RELATED_IMAGE_che_tls_secrets_creation_job"))
defaultGatewayAuthenticationSidecarImage = getDefaultFromEnv(util.GetArchitectureDependentEnv("RELATED_IMAGE_gateway_authentication_sidecar_k8s"))
defaultGatewayAuthorizationSidecarImage = getDefaultFromEnv(util.GetArchitectureDependentEnv("RELATED_IMAGE_gateway_authorization_sidecar_k8s"))
}
}

View File

@ -86,6 +86,7 @@ func TestReconcileDevWorkspace(t *testing.T) {
},
Server: orgv1.CheClusterSpecServer{
ServerExposureStrategy: "multi-host",
CustomCheProperties: map[string]string{"CHE_INFRA_KUBERNETES_ENABLE__UNSUPPORTED__K8S": "true"},
},
K8s: orgv1.CheClusterSpecK8SOnly{
IngressDomain: "che.domain",
@ -110,6 +111,7 @@ func TestReconcileDevWorkspace(t *testing.T) {
},
Server: orgv1.CheClusterSpecServer{
ServerExposureStrategy: "single-host",
CustomCheProperties: map[string]string{"CHE_INFRA_KUBERNETES_ENABLE__UNSUPPORTED__K8S": "true"},
},
K8s: orgv1.CheClusterSpecK8SOnly{
IngressDomain: "che.domain",

View File

@ -16,7 +16,6 @@ import (
"encoding/base64"
"fmt"
"io/ioutil"
"strings"
"sigs.k8s.io/yaml"
@ -87,7 +86,7 @@ func syncAll(deployContext *deploy.DeployContext) error {
return err
}
if util.IsOpenShift && deployContext.CheCluster.IsNativeUserModeEnabled() {
if deployContext.CheCluster.IsNativeUserModeEnabled() {
if oauthSecret, err := getGatewaySecretSpec(deployContext); err == nil {
if _, err := deploy.Sync(deployContext, oauthSecret, secretDiffOpts); err != nil {
return err
@ -100,18 +99,20 @@ func syncAll(deployContext *deploy.DeployContext) error {
return err
}
if headerRewritePluginConfig, err := getGatewayHeaderRewritePluginConfigSpec(instance); err == nil {
if _, err := deploy.Sync(deployContext, headerRewritePluginConfig, configMapDiffOpts); err != nil {
return err
}
} else {
return err
}
kubeRbacProxyConfig := getGatewayKubeRbacProxyConfigSpec(instance)
if _, err := deploy.Sync(deployContext, &kubeRbacProxyConfig, configMapDiffOpts); err != nil {
return err
}
if util.IsOpenShift {
if headerRewritePluginConfig, err := getGatewayHeaderRewritePluginConfigSpec(instance); err == nil {
if _, err := deploy.Sync(deployContext, headerRewritePluginConfig, configMapDiffOpts); err != nil {
return err
}
} else {
return err
}
}
}
traefikConfig := getGatewayTraefikConfigSpec(instance)
@ -378,90 +379,6 @@ 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 = ":%d"
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"
cookie_expire = "24h0m0s"
email_domains = "*"
cookie_httponly = false
pass_access_token = true
skip_provider_button = true
%s
`, GatewayServicePort,
instance.Spec.Server.CheHost,
instance.Spec.Auth.OAuthClientName,
instance.Spec.Auth.OAuthSecret,
GatewayServiceName,
cookieSecret,
skipAuthConfig(instance)),
},
}
}
func skipAuthConfig(instance *orgv1.CheCluster) string {
var skipAuthPaths []string
if !instance.Spec.Server.ExternalPluginRegistry {
skipAuthPaths = append(skipAuthPaths, "^/"+deploy.PluginRegistryName)
}
if !instance.Spec.Server.ExternalDevfileRegistry {
skipAuthPaths = append(skipAuthPaths, "^/"+deploy.DevfileRegistryName)
}
if util.IsOpenShift && instance.IsNativeUserModeEnabled() {
skipAuthPaths = append(skipAuthPaths, "/healthz$")
}
if len(skipAuthPaths) > 0 {
return fmt.Sprintf("skip_auth_regex = \"%s\"", strings.Join(skipAuthPaths, "|"))
}
return ""
}
func getGatewayKubeRbacProxyConfigSpec(instance *orgv1.CheCluster) corev1.ConfigMap {
return corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "che-gateway-config-kube-rbac-proxy",
Namespace: instance.Namespace,
Labels: deploy.GetLabels(instance, GatewayServiceName),
},
Data: map[string]string{
"authorization-config.yaml": `
authorization:
rewrites:
byQueryParameter:
name: "namespace"
resourceAttributes:
apiVersion: v1
resource: services
namespace: "{{ .Value }}"`,
},
}
}
func generateRandomCookieSecret() []byte {
return []byte(base64.StdEncoding.EncodeToString([]byte(util.GeneratePasswd(16))))
}
@ -499,7 +416,7 @@ func getGatewayHeaderRewritePluginConfigSpec(instance *orgv1.CheCluster) (*corev
func getGatewayTraefikConfigSpec(instance *orgv1.CheCluster) corev1.ConfigMap {
traefikPort := GatewayServicePort
if util.IsOpenShift && instance.IsNativeUserModeEnabled() {
if instance.IsNativeUserModeEnabled() {
traefikPort = 8081
}
data := fmt.Sprintf(`
@ -588,8 +505,6 @@ 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))
configLabels := labels.FormatLabels(configLabelsMap)
containers := []corev1.Container{
@ -631,42 +546,10 @@ func getContainersSpec(instance *orgv1.CheCluster) []corev1.Container {
},
}
if util.IsOpenShift && instance.IsNativeUserModeEnabled() {
if instance.IsNativeUserModeEnabled() {
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: GatewayServicePort, Protocol: "TCP"},
},
},
corev1.Container{
Name: "kube-rbac-proxy",
Image: authzImage,
ImagePullPolicy: corev1.PullAlways,
Args: []string{
"--insecure-listen-address=0.0.0.0:8089",
"--upstream=http://127.0.0.1:8090/ping",
"--logtostderr=true",
"--config-file=/etc/kube-rbac-proxy/authorization-config.yaml",
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "kube-rbac-proxy-config",
MountPath: "/etc/kube-rbac-proxy",
},
},
})
getOauthProxyContainerSpec(instance),
getKubeRbacProxyContainerSpec(instance))
}
return containers
@ -713,39 +596,23 @@ func getVolumesSpec(instance *orgv1.CheCluster) []corev1.Volume {
},
}
if util.IsOpenShift && instance.IsNativeUserModeEnabled() {
volumes = append(volumes, corev1.Volume{
Name: "oauth-proxy-config",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "che-gateway-config-oauth-proxy",
},
},
},
})
if instance.IsNativeUserModeEnabled() {
volumes = append(volumes,
getOauthProxyConfigVolume(),
getKubeRbacProxyConfigVolume())
volumes = append(volumes, corev1.Volume{
Name: "header-rewrite-traefik-plugin",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "che-gateway-config-header-rewrite-traefik-plugin",
if util.IsOpenShift {
volumes = append(volumes, corev1.Volume{
Name: "header-rewrite-traefik-plugin",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "che-gateway-config-header-rewrite-traefik-plugin",
},
},
},
},
})
volumes = append(volumes, corev1.Volume{
Name: "kube-rbac-proxy-config",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "che-gateway-config-kube-rbac-proxy",
},
},
},
})
})
}
}
return volumes

View File

@ -205,6 +205,7 @@ func TestOauthProxyConfigUnauthorizedPaths(t *testing.T) {
})
t.Run("no devfile-registry auth", func(t *testing.T) {
util.IsOpenShift = true
configmap := getGatewayOauthProxyConfigSpec(&orgv1.CheCluster{
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{

View File

@ -0,0 +1,81 @@
//
// 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 (
orgv1 "github.com/eclipse-che/che-operator/api/v1"
"github.com/eclipse-che/che-operator/pkg/deploy"
"github.com/eclipse-che/che-operator/pkg/util"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func getGatewayKubeRbacProxyConfigSpec(instance *orgv1.CheCluster) corev1.ConfigMap {
return corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "che-gateway-config-kube-rbac-proxy",
Namespace: instance.Namespace,
Labels: deploy.GetLabels(instance, GatewayServiceName),
},
Data: map[string]string{
"authorization-config.yaml": `
authorization:
rewrites:
byQueryParameter:
name: "namespace"
resourceAttributes:
apiVersion: v1
apiGroup: workspace.devfile.io
resource: devworkspaces
namespace: "{{ .Value }}"`,
},
}
}
func getKubeRbacProxyContainerSpec(instance *orgv1.CheCluster) corev1.Container {
authzImage := util.GetValue(instance.Spec.Auth.GatewayAuthorizationSidecarImage, deploy.DefaultGatewayAuthorizationSidecarImage(instance))
return corev1.Container{
Name: "kube-rbac-proxy",
Image: authzImage,
ImagePullPolicy: corev1.PullAlways,
Args: []string{
"--insecure-listen-address=0.0.0.0:8089",
"--upstream=http://127.0.0.1:8090/ping",
"--logtostderr=true",
"--config-file=/etc/kube-rbac-proxy/authorization-config.yaml",
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "kube-rbac-proxy-config",
MountPath: "/etc/kube-rbac-proxy",
},
},
}
}
func getKubeRbacProxyConfigVolume() corev1.Volume {
return corev1.Volume{
Name: "kube-rbac-proxy-config",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "che-gateway-config-kube-rbac-proxy",
},
},
},
}
}

View File

@ -0,0 +1,162 @@
//
// 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 (
"fmt"
"strings"
orgv1 "github.com/eclipse-che/che-operator/api/v1"
"github.com/eclipse-che/che-operator/pkg/deploy"
"github.com/eclipse-che/che-operator/pkg/util"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func getGatewayOauthProxyConfigSpec(instance *orgv1.CheCluster, cookieSecret string) corev1.ConfigMap {
var config string
if util.IsOpenShift {
config = openshiftOauthProxyConfig(instance, cookieSecret)
} else {
config = kubernetesOauthProxyconfig(instance, cookieSecret)
}
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": config,
},
}
}
func openshiftOauthProxyConfig(instance *orgv1.CheCluster, cookieSecret string) string {
return fmt.Sprintf(`
http_address = ":%d"
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"
cookie_expire = "24h0m0s"
email_domains = "*"
cookie_httponly = false
pass_access_token = true
skip_provider_button = true
%s
`, GatewayServicePort,
instance.Spec.Server.CheHost,
instance.Spec.Auth.OAuthClientName,
instance.Spec.Auth.OAuthSecret,
GatewayServiceName,
cookieSecret,
skipAuthConfig(instance))
}
func kubernetesOauthProxyconfig(instance *orgv1.CheCluster, cookieSecret string) string {
return fmt.Sprintf(`
proxy_prefix = "/oauth"
http_address = ":%d"
https_address = ""
provider = "oidc"
redirect_url = "https://%s/oauth/callback"
oidc_issuer_url = "%s"
insecure_oidc_skip_issuer_verification = true
ssl_insecure_skip_verify = true
upstreams = [
"http://127.0.0.1:8081/"
]
client_id = "%s"
client_secret = "%s"
cookie_secret = "%s"
cookie_expire = "24h0m0s"
email_domains = "*"
cookie_httponly = false
pass_authorization_header = true
skip_provider_button = true
%s
`, GatewayServicePort,
instance.Spec.Server.CheHost,
instance.Spec.Auth.IdentityProviderURL,
instance.Spec.Auth.OAuthClientName,
instance.Spec.Auth.OAuthSecret,
cookieSecret,
skipAuthConfig(instance))
}
func skipAuthConfig(instance *orgv1.CheCluster) string {
var skipAuthPaths []string
if !instance.Spec.Server.ExternalPluginRegistry {
skipAuthPaths = append(skipAuthPaths, "^/"+deploy.PluginRegistryName)
}
if !instance.Spec.Server.ExternalDevfileRegistry {
skipAuthPaths = append(skipAuthPaths, "^/"+deploy.DevfileRegistryName)
}
if instance.IsNativeUserModeEnabled() {
skipAuthPaths = append(skipAuthPaths, "/healthz$")
}
if len(skipAuthPaths) > 0 {
propName := "skip_auth_routes"
if util.IsOpenShift {
propName = "skip_auth_regex"
}
return fmt.Sprintf("%s = \"%s\"", propName, strings.Join(skipAuthPaths, "|"))
}
return ""
}
func getOauthProxyContainerSpec(instance *orgv1.CheCluster) corev1.Container {
authnImage := util.GetValue(instance.Spec.Auth.GatewayAuthenticationSidecarImage, deploy.DefaultGatewayAuthenticationSidecarImage(instance))
return 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: GatewayServicePort, Protocol: "TCP"},
},
}
}
func getOauthProxyConfigVolume() corev1.Volume {
return corev1.Volume{
Name: "oauth-proxy-config",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "che-gateway-config-oauth-proxy",
},
},
},
}
}

View File

@ -49,7 +49,7 @@ 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 util.IsOpenShift && deployContext.CheCluster.IsNativeUserModeEnabled() {
if deployContext.CheCluster.IsNativeUserModeEnabled() {
return syncNativeIdentityProviderItems(deployContext)
} else if cr.Spec.Auth.ExternalIdentityProvider {
return true, nil
@ -164,12 +164,13 @@ func syncNativeIdentityProviderItems(deployContext *deploy.DeployContext) (bool,
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
if util.IsOpenShift {
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

View File

@ -49,6 +49,7 @@ type CheConfigMap struct {
CheMetricsEnabled string `json:"CHE_METRICS_ENABLED"`
CheInfrastructureActive string `json:"CHE_INFRASTRUCTURE_ACTIVE"`
CheInfraKubernetesServiceAccountName string `json:"CHE_INFRA_KUBERNETES_SERVICE__ACCOUNT__NAME"`
CheInfraKubernetesUserClusterRoles string `json:"CHE_INFRA_KUBERNETES_USER__CLUSTER__ROLES"`
DefaultTargetNamespace string `json:"CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"`
PvcStrategy string `json:"CHE_INFRA_KUBERNETES_PVC_STRATEGY"`
PvcClaimSize string `json:"CHE_INFRA_KUBERNETES_PVC_QUANTITY"`
@ -61,8 +62,8 @@ type CheConfigMap struct {
DbUserName string `json:"CHE_JDBC_USERNAME,omitempty"`
DbPassword string `json:"CHE_JDBC_PASSWORD,omitempty"`
CheLogLevel string `json:"CHE_LOG_LEVEL"`
KeycloakURL string `json:"CHE_KEYCLOAK_AUTH__SERVER__URL,omitempty"`
KeycloakInternalURL string `json:"CHE_KEYCLOAK_AUTH__INTERNAL__SERVER__URL,omitempty"`
IdentityProviderUrl string `json:"CHE_OIDC_AUTH__SERVER__URL,omitempty"`
IdentityProviderInternalURL string `json:"CHE_OIDC_AUTH__INTERNAL__SERVER__URL,omitempty"`
KeycloakRealm string `json:"CHE_KEYCLOAK_REALM,omitempty"`
KeycloakClientId string `json:"CHE_KEYCLOAK_CLIENT__ID,omitempty"`
OpenShiftIdentityProvider string `json:"CHE_INFRA_OPENSHIFT_OAUTH__IDENTITY__PROVIDER"`
@ -92,15 +93,17 @@ type CheConfigMap struct {
// which is used in CheCluster ConfigMap to configure CheCluster master behavior
func (s *Server) getCheConfigMapData() (cheEnv map[string]string, err error) {
cheHost := s.deployContext.CheCluster.Spec.Server.CheHost
keycloakURL := s.deployContext.CheCluster.Spec.Auth.IdentityProviderURL
identityProviderURL := s.deployContext.CheCluster.Spec.Auth.IdentityProviderURL
// Adds `/auth` for external identity providers.
// If identity provide is deployed by operator then `/auth` is already added.
if s.deployContext.CheCluster.Spec.Auth.ExternalIdentityProvider && !strings.HasSuffix(keycloakURL, "/auth") {
if strings.HasSuffix(keycloakURL, "/") {
keycloakURL = keycloakURL + "auth"
if !s.deployContext.CheCluster.IsNativeUserModeEnabled() &&
s.deployContext.CheCluster.Spec.Auth.ExternalIdentityProvider &&
!strings.HasSuffix(identityProviderURL, "/auth") {
if strings.HasSuffix(identityProviderURL, "/") {
identityProviderURL = identityProviderURL + "auth"
} else {
keycloakURL = keycloakURL + "/auth"
identityProviderURL = identityProviderURL + "/auth"
}
}
@ -186,7 +189,9 @@ func (s *Server) getCheConfigMapData() (cheEnv map[string]string, err error) {
cheAPI := protocol + "://" + cheHost + "/api"
var keycloakInternalURL, pluginRegistryInternalURL, devfileRegistryInternalURL, cheInternalAPI, webSocketInternalEndpoint string
if s.deployContext.CheCluster.IsInternalClusterSVCNamesEnabled() && !s.deployContext.CheCluster.Spec.Auth.ExternalIdentityProvider {
if !s.deployContext.CheCluster.IsNativeUserModeEnabled() &&
s.deployContext.CheCluster.IsInternalClusterSVCNamesEnabled() &&
!s.deployContext.CheCluster.Spec.Auth.ExternalIdentityProvider {
keycloakInternalURL = fmt.Sprintf("%s://%s.%s.svc:8080/auth", "http", deploy.IdentityProviderName, s.deployContext.CheCluster.Namespace)
}
@ -210,6 +215,13 @@ func (s *Server) getCheConfigMapData() (cheEnv map[string]string, err error) {
}
webSocketEndpoint := wsprotocol + "://" + cheHost + "/api/websocket"
cheWorkspaceServiceAccount := "che-workspace"
cheUserClusterRoleNames := "NULL"
if s.deployContext.CheCluster.IsNativeUserModeEnabled() {
cheWorkspaceServiceAccount = "NULL"
cheUserClusterRoleNames = fmt.Sprintf("%s-cheworkspaces-clusterrole, %s-cheworkspaces-devworkspace-clusterrole", s.deployContext.CheCluster.Namespace, s.deployContext.CheCluster.Namespace)
}
data := &CheConfigMap{
CheMultiUser: "true",
CheHost: cheHost,
@ -220,7 +232,8 @@ func (s *Server) getCheConfigMapData() (cheEnv map[string]string, err error) {
CheWebSocketInternalEndpoint: webSocketInternalEndpoint,
CheDebugServer: cheDebug,
CheInfrastructureActive: infra,
CheInfraKubernetesServiceAccountName: "che-workspace",
CheInfraKubernetesServiceAccountName: cheWorkspaceServiceAccount,
CheInfraKubernetesUserClusterRoles: cheUserClusterRoleNames,
DefaultTargetNamespace: workspaceNamespaceDefault,
PvcStrategy: pvcStrategy,
PvcClaimSize: pvcClaimSize,
@ -254,8 +267,8 @@ func (s *Server) getCheConfigMapData() (cheEnv map[string]string, err error) {
CheDevWorkspacesEnabled: strconv.FormatBool(s.deployContext.CheCluster.Spec.DevWorkspace.Enable),
}
data.KeycloakURL = keycloakURL
data.KeycloakInternalURL = keycloakInternalURL
data.IdentityProviderUrl = identityProviderURL
data.IdentityProviderInternalURL = keycloakInternalURL
data.KeycloakRealm = keycloakRealm
data.KeycloakClientId = keycloakClientId
data.DatabaseURL = "jdbc:postgresql://" + chePostgresHostName + ":" + chePostgresPort + "/" + chePostgresDb

View File

@ -806,8 +806,8 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
},
},
expectedData: map[string]string{
"CHE_KEYCLOAK_AUTH__INTERNAL__SERVER__URL": "",
"CHE_KEYCLOAK_AUTH__SERVER__URL": "http://external-keycloak/auth",
"CHE_OIDC_AUTH__INTERNAL__SERVER__URL": "",
"CHE_OIDC_AUTH__SERVER__URL": "http://external-keycloak/auth",
},
},
{
@ -829,8 +829,8 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
},
},
expectedData: map[string]string{
"CHE_KEYCLOAK_AUTH__INTERNAL__SERVER__URL": "",
"CHE_KEYCLOAK_AUTH__SERVER__URL": "http://external-keycloak/auth",
"CHE_OIDC_AUTH__INTERNAL__SERVER__URL": "",
"CHE_OIDC_AUTH__SERVER__URL": "http://external-keycloak/auth",
},
},
{
@ -855,8 +855,8 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
},
},
expectedData: map[string]string{
"CHE_KEYCLOAK_AUTH__INTERNAL__SERVER__URL": "",
"CHE_KEYCLOAK_AUTH__SERVER__URL": "http://external-keycloak/auth",
"CHE_OIDC_AUTH__INTERNAL__SERVER__URL": "",
"CHE_OIDC_AUTH__SERVER__URL": "http://external-keycloak/auth",
},
},
{
@ -881,8 +881,8 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
},
},
expectedData: map[string]string{
"CHE_KEYCLOAK_AUTH__INTERNAL__SERVER__URL": "",
"CHE_KEYCLOAK_AUTH__SERVER__URL": "http://keycloak/auth",
"CHE_OIDC_AUTH__INTERNAL__SERVER__URL": "",
"CHE_OIDC_AUTH__SERVER__URL": "http://keycloak/auth",
},
},
{
@ -904,8 +904,8 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
},
},
expectedData: map[string]string{
"CHE_KEYCLOAK_AUTH__INTERNAL__SERVER__URL": "http://keycloak.eclipse-che.svc:8080/auth",
"CHE_KEYCLOAK_AUTH__SERVER__URL": "http://keycloak/auth",
"CHE_OIDC_AUTH__INTERNAL__SERVER__URL": "http://keycloak.eclipse-che.svc:8080/auth",
"CHE_OIDC_AUTH__SERVER__URL": "http://keycloak/auth",
},
},
}

View File

@ -162,7 +162,7 @@ func (s Server) getDeploymentSpec() (*appsv1.Deployment, error) {
FieldPath: "metadata.namespace"}},
})
if util.IsOpenShift && s.deployContext.CheCluster.IsNativeUserModeEnabled() {
if s.deployContext.CheCluster.IsNativeUserModeEnabled() {
cheEnv = append(cheEnv, corev1.EnvVar{
Name: "CHE_AUTH_NATIVEUSER",
Value: "true",