diff --git a/.ci/cico_operator_prcheck.sh b/.ci/cico_operator_prcheck.sh index 432b31d56..92cd41592 100644 --- a/.ci/cico_operator_prcheck.sh +++ b/.ci/cico_operator_prcheck.sh @@ -20,7 +20,7 @@ Catch_Finish() { } init() { - GO_TOOLSET_VERSION="1.11.5-3" + GO_TOOLSET_VERSION="1.12.12-4" SCRIPT=$(readlink -f "$0") # this script's absolute path SCRIPTPATH=$(dirname "$SCRIPT") # /path/to/e2e/ folder if [[ ${WORKSPACE} ]] && [[ -d ${WORKSPACE} ]]; then OPERATOR_REPO=${WORKSPACE}; else OPERATOR_REPO=$(dirname "$SCRIPTPATH"); fi diff --git a/pkg/controller/che/che_controller.go b/pkg/controller/che/che_controller.go index 0a0f63b2b..a7226c0d6 100644 --- a/pkg/controller/che/che_controller.go +++ b/pkg/controller/che/che_controller.go @@ -12,6 +12,7 @@ package che import ( + "reflect" "context" "fmt" "strings" @@ -289,8 +290,8 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e logrus.Errorf("Error getting devfile-registry ConfigMap: %v", err) return reconcile.Result{}, err } - if err == nil && !instance.IsAirGapMode() { - logrus.Info("Found devfile-registry ConfigMap and not in airgap mode. Deleting.") + if err == nil && instance.Spec.Server.ExternalDevfileRegistry { + logrus.Info("Found devfile-registry ConfigMap and while using an external devfile registry. Deleting.") if err = r.client.Delete(context.TODO(), devfileRegistryConfigMap); err != nil { logrus.Errorf("Error deleting devfile-registry ConfigMap: %v", err) return reconcile.Result{}, err @@ -871,37 +872,37 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e if devfileRegistryURL == "" { devfileRegistryURL = guessedDevfileRegistryURL } - if instance.IsAirGapMode() { - devFileRegistryConfigMap := &corev1.ConfigMap{} - err = r.client.Get(context.TODO(), types.NamespacedName{Name: "devfile-registry", Namespace: instance.Namespace}, devFileRegistryConfigMap) - if err != nil { - if errors.IsNotFound(err) { - devFileRegistryConfigMap = deploy.CreateDevfileRegistryConfigMap(instance, devfileRegistryURL) - err = controllerutil.SetControllerReference(instance, devFileRegistryConfigMap, r.scheme) - if err != nil { - logrus.Errorf("An error occurred: %v", err) - return reconcile.Result{}, err - } - logrus.Info("Creating devfile registry airgap configmap") - err = r.client.Create(context.TODO(), devFileRegistryConfigMap) - if err != nil { - logrus.Errorf("Error creating devfile registry configmap: %v", err) - return reconcile.Result{}, err - } - return reconcile.Result{Requeue: true}, nil - } else { - logrus.Errorf("Could not get devfile-registry ConfigMap: %v", err) - return reconcile.Result{}, err - } - } else { + devFileRegistryConfigMap := &corev1.ConfigMap{} + err = r.client.Get(context.TODO(), types.NamespacedName{Name: "devfile-registry", Namespace: instance.Namespace}, devFileRegistryConfigMap) + if err != nil { + if errors.IsNotFound(err) { devFileRegistryConfigMap = deploy.CreateDevfileRegistryConfigMap(instance, devfileRegistryURL) + err = controllerutil.SetControllerReference(instance, devFileRegistryConfigMap, r.scheme) + if err != nil { + logrus.Errorf("An error occurred: %v", err) + return reconcile.Result{}, err + } + logrus.Info("Creating devfile registry airgap configmap") + err = r.client.Create(context.TODO(), devFileRegistryConfigMap) + if err != nil { + logrus.Errorf("Error creating devfile registry configmap: %v", err) + return reconcile.Result{}, err + } + return reconcile.Result{Requeue: true}, nil + } else { + logrus.Errorf("Could not get devfile-registry ConfigMap: %v", err) + return reconcile.Result{}, err + } + } else { + newDevFileRegistryConfigMap := deploy.CreateDevfileRegistryConfigMap(instance, devfileRegistryURL) + if ! reflect.DeepEqual(devFileRegistryConfigMap.Data, newDevFileRegistryConfigMap.Data) { err = controllerutil.SetControllerReference(instance, devFileRegistryConfigMap, r.scheme) if err != nil { logrus.Errorf("An error occurred: %v", err) return reconcile.Result{}, err } logrus.Info("Updating devfile-registry ConfigMap") - err = r.client.Update(context.TODO(), devFileRegistryConfigMap) + err = r.client.Update(context.TODO(), newDevFileRegistryConfigMap) if err != nil { logrus.Errorf("Error updating devfile-registry ConfigMap: %v", err) return reconcile.Result{}, err diff --git a/pkg/controller/che/che_controller_test.go b/pkg/controller/che/che_controller_test.go index 1843dec7b..a914769ef 100644 --- a/pkg/controller/che/che_controller_test.go +++ b/pkg/controller/che/che_controller_test.go @@ -130,6 +130,24 @@ func TestCheController(t *testing.T) { if err != nil { t.Fatalf("reconcile: (%v)", err) } + + // get devfile-registry configmap + devfilecm := &corev1.ConfigMap{} + if err := cl.Get(context.TODO(), types.NamespacedName{Name: "devfile-registry", Namespace: cheCR.Namespace}, devfilecm); err != nil { + t.Errorf("ConfigMap %s not found: %s", devfilecm.Name, err) + } + + // Check the result of reconciliation to make sure it has the desired state. + if ! res.Requeue { + t.Error("Reconcile did not requeue request as expected") + } + + // reconcile again + res, err = r.Reconcile(req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + // Check the result of reconciliation to make sure it has the desired state. if res.Requeue { t.Error("Reconcile did not requeue request as expected") diff --git a/pkg/controller/che/create.go b/pkg/controller/che/create.go index f611362e9..4f41d062a 100644 --- a/pkg/controller/che/create.go +++ b/pkg/controller/che/create.go @@ -338,7 +338,7 @@ func (r *ReconcileChe) CreateTLSSecret(instance *orgv1.CheCluster, url string, n if err := r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.Namespace}, secret); err != nil && errors.IsNotFound(err) { crt, err := r.GetEndpointTlsCrt(instance, url) if err != nil { - logrus.Errorf("Failed to extract crt. Failed to create a secret with a self signed crt: %s", err) + logrus.Errorf("Failed to extract crt for secret %s. Failed to create a secret with a self signed crt: %s", name, err) return err } else { secret := deploy.NewSecret(instance, name, crt) diff --git a/pkg/controller/che/k8s_helpers.go b/pkg/controller/che/k8s_helpers.go index 7207e3024..846d9915c 100644 --- a/pkg/controller/che/k8s_helpers.go +++ b/pkg/controller/che/k8s_helpers.go @@ -18,6 +18,8 @@ import ( "encoding/pem" "io" "net/http" + "net/url" + "strings" "time" orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" @@ -25,6 +27,7 @@ import ( "github.com/eclipse/che-operator/pkg/util" routev1 "github.com/openshift/api/route/v1" "github.com/sirupsen/logrus" + "golang.org/x/net/http/httpproxy" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -286,10 +289,10 @@ func (cl *k8s) GetDeploymentPod(name string, ns string) (podName string, err err // GetEndpointTlsCrt creates a test TLS route and gets it to extract certificate chain // There's an easier way which is to read tls secret in default (3.11) or openshift-ingress (4.0) namespace // which however requires extra privileges for operator service account -func (r *ReconcileChe) GetEndpointTlsCrt(instance *orgv1.CheCluster, url string) (certificate []byte, err error) { +func (r *ReconcileChe) GetEndpointTlsCrt(instance *orgv1.CheCluster, endpointUrl string) (certificate []byte, err error) { testRoute := &routev1.Route{} var requestURL string - if len(url) < 1 { + if len(endpointUrl) < 1 { testRoute = deploy.NewTlsRoute(instance, "test", "test", 8080) logrus.Infof("Creating a test route %s to extract router crt", testRoute.Name) if err := r.CreateNewRoute(instance, testRoute); err != nil { @@ -299,16 +302,26 @@ func (r *ReconcileChe) GetEndpointTlsCrt(instance *orgv1.CheCluster, url string) // sometimes timing conditions apply, and host isn't available right away if len(testRoute.Spec.Host) < 1 { time.Sleep(time.Duration(1) * time.Second) - testRoute := r.GetEffectiveRoute(instance, "test") - requestURL = "https://" + testRoute.Spec.Host + testRoute = r.GetEffectiveRoute(instance, "test") } requestURL = "https://" + testRoute.Spec.Host } else { - requestURL = url + requestURL = endpointUrl + } + + //adding the proxy settings to the Transport object + transport := &http.Transport{} + + if instance.Spec.Server.ProxyURL != "" { + logrus.Infof("Configuring proxy with %s to extract crt from the following URL: %s", instance.Spec.Server.ProxyURL, requestURL) + r.configureProxy(instance, transport) + } + + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + client := &http.Client{ + Transport: transport, } - http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - client := &http.Client{} req, err := http.NewRequest("GET", requestURL, nil) resp, err := client.Do(req) if err != nil { @@ -329,7 +342,7 @@ func (r *ReconcileChe) GetEndpointTlsCrt(instance *orgv1.CheCluster, url string) certificate = append(certificate, crt...) } - if len(url) < 1 { + if len(endpointUrl) < 1 { logrus.Infof("Deleting a test route %s to extract routes crt", testRoute.Name) if err := r.client.Delete(context.TODO(), testRoute); err != nil { logrus.Errorf("Failed to delete test route %s: %s", testRoute.Name, err) @@ -337,3 +350,43 @@ func (r *ReconcileChe) GetEndpointTlsCrt(instance *orgv1.CheCluster, url string) } return certificate, nil } + +func (r *ReconcileChe) configureProxy(instance *orgv1.CheCluster, transport *http.Transport) () { + proxyParts := strings.Split(instance.Spec.Server.ProxyURL, "://") + proxyProtocol := "" + proxyHost := "" + if len(proxyParts) == 1 { + proxyProtocol = "" + proxyHost = proxyParts[0] + } else { + proxyProtocol = proxyParts[0] + proxyHost = proxyParts[1] + + } + + proxyURL := proxyHost + if instance.Spec.Server.ProxyPort != "" { + proxyURL = proxyURL + ":" + instance.Spec.Server.ProxyPort + } + if len(instance.Spec.Server.ProxyUser) > 1 && len(instance.Spec.Server.ProxyPassword) > 1 { + proxyURL = instance.Spec.Server.ProxyUser + ":" + instance.Spec.Server.ProxyPassword + "@" + proxyURL + } + + if proxyProtocol != "" { + proxyURL = proxyProtocol + "://" + proxyURL + } + config := httpproxy.Config{ + HTTPProxy: proxyURL, + HTTPSProxy: proxyURL, + NoProxy: strings.Replace(instance.Spec.Server.NonProxyHosts, "|", ",", -1), + } + proxyFunc := config.ProxyFunc() + transport.Proxy = func(r *http.Request) (*url.URL, error) { + theProxyUrl, err := proxyFunc(r.URL) + if err != nil { + logrus.Warnf("Error when trying to get the proxy to access TLS endpoint URL: %s - %s", r.URL, err) + } + logrus.Infof("Using proxy: %s to access TLS endpoint URL: %s", theProxyUrl, r.URL) + return theProxyUrl, err + } +} diff --git a/pkg/deploy/che_configmap.go b/pkg/deploy/che_configmap.go index 45d8d833d..2a46d1e5c 100644 --- a/pkg/deploy/che_configmap.go +++ b/pkg/deploy/che_configmap.go @@ -123,7 +123,7 @@ func GetConfigMapData(cr *orgv1.CheCluster) (cheEnv map[string]string) { cheWorkspaceHttpProxy := "" cheWorkspaceNoProxy := "" if len(cr.Spec.Server.ProxyURL) > 1 { - cheWorkspaceHttpProxy, cheWorkspaceNoProxy = util.GenerateProxyEnvs(cr.Spec.Server.ProxyURL, cr.Spec.Server.ProxyPort, cr.Spec.Server.NonProxyHosts, proxyUser, proxyPassword) + cheWorkspaceHttpProxy, cheWorkspaceNoProxy = util.GenerateProxyEnvs(cr.Spec.Server.ProxyURL, cr.Spec.Server.ProxyPort, nonProxyHosts, proxyUser, proxyPassword) } ingressDomain := cr.Spec.K8s.IngressDomain diff --git a/pkg/deploy/deployment_keycloak.go b/pkg/deploy/deployment_keycloak.go index a7b7a0486..3600bd906 100644 --- a/pkg/deploy/deployment_keycloak.go +++ b/pkg/deploy/deployment_keycloak.go @@ -12,6 +12,9 @@ package deploy import ( + "regexp" + "strings" + orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" "github.com/eclipse/che-operator/pkg/util" appsv1 "k8s.io/api/apps/v1" @@ -59,7 +62,6 @@ func NewKeycloakDeployment(cr *orgv1.CheCluster, keycloakPostgresPassword string addCertToTrustStoreCommand := addRouterCrt + " && " + addOpenShiftAPICrt + " && " + addMountedCrt + " && " + addMountedServiceCrt + " && " + importJavaCacerts - startCommand := "sed -i 's/WILDCARD/ANY/g' /opt/eap/bin/launch/keycloak-spi.sh && /opt/eap/bin/openshift-launch.sh -b 0.0.0.0" // upstream Keycloak has a bit different mechanism of adding jks changeConfigCommand := "echo Installing certificates into Keycloak && " + "echo -e \"embed-server --server-config=standalone.xml --std-out=echo \n" + @@ -68,6 +70,50 @@ func NewKeycloakDeployment(cr *orgv1.CheCluster, keycloakPostgresPassword string "\"" + jbossDir + "/openshift.jks\", password => \"" + trustpass + "\", disabled => \"false\" },enabled=true) \n" + "stop-embedded-server\" > /scripts/add_openshift_certificate.cli && " + "/opt/jboss/keycloak/bin/jboss-cli.sh --file=/scripts/add_openshift_certificate.cli" + + addProxyCliCommand := "" + applyProxyCliCommand := "" + proxyEnvVars := []corev1.EnvVar{} + + if len(cr.Spec.Server.ProxyURL) > 1 { + cheWorkspaceHttpProxy, cheWorkspaceNoProxy := util.GenerateProxyEnvs(cr.Spec.Server.ProxyURL, cr.Spec.Server.ProxyPort, cr.Spec.Server.NonProxyHosts, cr.Spec.Server.ProxyUser, cr.Spec.Server.ProxyPassword) + + proxyEnvVars = []corev1.EnvVar{ + corev1.EnvVar{ + Name: "HTTP_PROXY", + Value: cheWorkspaceHttpProxy, + }, + corev1.EnvVar{ + Name: "HTTPS_PROXY", + Value: cheWorkspaceHttpProxy, + }, + corev1.EnvVar{ + Name: "NO_PROXY", + Value: cheWorkspaceNoProxy, + }, + } + + cheWorkspaceNoProxy = strings.ReplaceAll(regexp.QuoteMeta(cheWorkspaceNoProxy), "\\", "\\\\\\") + + jbossCli := "/opt/jboss/keycloak/bin/jboss-cli.sh" + serverConfig := "standalone.xml" + if cheFlavor == "codeready" { + jbossCli = "/opt/eap/bin/jboss-cli.sh" + serverConfig = "standalone-openshift.xml" + } + addProxyCliCommand = " && echo Configuring Proxy && " + + "echo -e 'embed-server --server-config=" + serverConfig + " --std-out=echo \n" + + "/subsystem=keycloak-server/spi=connectionsHttpClient/provider=default:write-attribute(name=properties.proxy-mappings,value=[\"" + cheWorkspaceNoProxy + ";NO_PROXY\",\".*;" + cheWorkspaceHttpProxy + "\"]) \n" + + "stop-embedded-server' > " + jbossDir + "/setup-http-proxy.cli" + + applyProxyCliCommand = " && " + jbossCli + " --file=" + jbossDir + "/setup-http-proxy.cli" + if cheFlavor == "codeready" { + applyProxyCliCommand = " && mkdir -p " + jbossDir + "/extensions && echo '#!/bin/bash\n" + + "" + jbossDir + "/bin/jboss-cli.sh --file=" + jbossDir + "/setup-http-proxy.cli' > " + jbossDir + "/extensions/postconfigure.sh && " + + "chmod a+x " + jbossDir + "/extensions/postconfigure.sh " + } + } + keycloakAdminUserName := util.GetValue(cr.Spec.Auth.IdentityProviderAdminUserName, DefaultKeycloakAdminUserName) keycloakEnv := []corev1.EnvVar{ { @@ -215,12 +261,18 @@ func NewKeycloakDeployment(cr *orgv1.CheCluster, keycloakPostgresPassword string }, } } - command := addCertToTrustStoreCommand + " && " + changeConfigCommand + " && /opt/jboss/docker-entrypoint.sh -b 0.0.0.0 -c standalone.xml" + + for _, envvar := range proxyEnvVars { + keycloakEnv = append(keycloakEnv, envvar) + } + + command := addCertToTrustStoreCommand + addProxyCliCommand + applyProxyCliCommand + " && " + changeConfigCommand + + " && /opt/jboss/docker-entrypoint.sh -b 0.0.0.0 -c standalone.xml" command += " -Dkeycloak.profile.feature.token_exchange=enabled -Dkeycloak.profile.feature.admin_fine_grained_authz=enabled" if cheFlavor == "codeready" { - command = addCertToTrustStoreCommand + - " && echo \"feature.token_exchange=enabled\nfeature.admin_fine_grained_authz=enabled\" > /opt/eap/standalone/configuration/profile.properties && " + - startCommand + command = addCertToTrustStoreCommand + addProxyCliCommand + applyProxyCliCommand + + " && echo \"feature.token_exchange=enabled\nfeature.admin_fine_grained_authz=enabled\" > /opt/eap/standalone/configuration/profile.properties" + + " && sed -i 's/WILDCARD/ANY/g' /opt/eap/bin/launch/keycloak-spi.sh && /opt/eap/bin/openshift-launch.sh -b 0.0.0.0" } return &appsv1.Deployment{