diff --git a/deploy/crownlabs/Chart.lock b/deploy/crownlabs/Chart.lock index 69a25850e..8a8b82ebc 100644 --- a/deploy/crownlabs/Chart.lock +++ b/deploy/crownlabs/Chart.lock @@ -8,9 +8,12 @@ dependencies: - name: frontend-storybook repository: file://../../frontend/deploy/frontend-storybook version: 0.1.0 +- name: qlkube + repository: file://../../qlkube/deploy/qlkube + version: 0.1.0 - name: instance-operator repository: file://../../operators/deploy/instance-operator - version: 0.1.0 + version: 0.1.1 - name: tenant-operator repository: file://../../operators/deploy/tenant-operator version: 0.1.0 @@ -26,5 +29,5 @@ dependencies: - name: policies repository: file://../../policies/ version: 0.1.0 -digest: sha256:d370b30247ec7645be2b7541f2e4d7feb154ae1f2c8f271ce98441740b4ce94b -generated: "2021-04-22T12:32:48.064223219+02:00" +digest: sha256:2e3f930d3126953bb8c7ed7d61270a8850dee55716ffca26defae21600d2bd24 +generated: "2021-07-06T19:01:31.090144215+02:00" diff --git a/deploy/crownlabs/Chart.yaml b/deploy/crownlabs/Chart.yaml index 5d6a8defa..b16582eeb 100644 --- a/deploy/crownlabs/Chart.yaml +++ b/deploy/crownlabs/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.4 icon: https://crownlabs.polito.it/images/logo.svg @@ -41,7 +41,7 @@ dependencies: condition: qlkube.enabled - name: instance-operator - version: "0.1.0" + version: "0.1.1" repository: file://../../operators/deploy/instance-operator condition: instance-operator.enabled diff --git a/deploy/crownlabs/values.yaml b/deploy/crownlabs/values.yaml index 204880bf7..1e084a09a 100644 --- a/deploy/crownlabs/values.yaml +++ b/deploy/crownlabs/values.yaml @@ -70,16 +70,12 @@ instance-operator: rbacResourcesName: crownlabs-instance-operator configurations: generic: - oauth2ProxyImage: quay.io/oauth2-proxy/oauth2-proxy whitelistLabels: crownlabs.polito.it/operator-selector=production - websiteBaseUri: crownlabs.example.com + websiteBaseUrl: crownlabs.example.com + instancesAuthUrl: https://crownlabs.example.com/auth nextcloud: baseUrl: https://nextcloud.example.com webdavSecretName: nextcloud-credentials - oidc: - clientId: k8s - clientSecret: - providerUrl: https://auth.example.com/auth/realms/crownlabs containerEnvironmentOptions: tag: "" vncImage: crownlabs/tigervnc diff --git a/operators/cmd/instance-operator/main.go b/operators/cmd/instance-operator/main.go index 574c38454..a0f9d5fd8 100644 --- a/operators/cmd/instance-operator/main.go +++ b/operators/cmd/instance-operator/main.go @@ -44,13 +44,10 @@ func init() { _ = clientgoscheme.AddToScheme(scheme) _ = crownlabsv1alpha1.AddToScheme(scheme) - _ = crownlabsv1alpha2.AddToScheme(scheme) _ = virtv1.AddToScheme(scheme) - _ = cdiv1.AddToScheme(scheme) - // +kubebuilder:scaffold:scheme } func main() { @@ -60,9 +57,7 @@ func main() { var webdavSecret string var websiteBaseURL string var nextcloudBaseURL string - var oauth2ProxyImage string - var oidcClientSecret string - var oidcProviderURL string + var instancesAuthURL string var containerEnvSidecarsTag string var containerEnvVncImg string var containerEnvWebsockifyImg string @@ -83,21 +78,24 @@ func main() { "( e.g. key1=value1&key2=value2") flag.StringVar(&websiteBaseURL, "website-base-url", "crownlabs.polito.it", "Base URL of crownlabs website instance") flag.StringVar(&nextcloudBaseURL, "nextcloud-base-url", "", "Base URL of NextCloud website to use") + flag.StringVar(&instancesAuthURL, "instances-auth-url", "", "The base URL for user instances authentication (i.e., oauth2-proxy)") flag.StringVar(&webdavSecret, "webdav-secret-name", "webdav", "The name of the secret containing webdav credentials") - flag.StringVar(&oauth2ProxyImage, "oauth2-proxy-image", "", "The docker image used for the oauth2-proxy deployment") - flag.StringVar(&oidcClientSecret, "oidc-client-secret", "", "The oidc client secret used by oauth2-proxy") - flag.StringVar(&oidcProviderURL, "oidc-provider-url", "", "The url of the oidc provider used by oauth2-proxy") + flag.StringVar(&containerEnvSidecarsTag, "container-env-sidecars-tag", "latest", "The tag for service containers (such as gui sidecar containers)") flag.StringVar(&containerEnvVncImg, "container-env-vnc-img", "crownlabs/tigervnc", "The image name for the vnc image (sidecar for graphical container environment)") flag.StringVar(&containerEnvWebsockifyImg, "container-env-websockify-img", "crownlabs/websockify", "The image name for the websockify image (sidecar for graphical container environment)") flag.StringVar(&containerEnvNovncImg, "container-env-novnc-img", "crownlabs/novnc", "The image name for the novnc image (sidecar for graphical container environment)") + flag.StringVar(&vmRegistry, "vm-registry", "", "The registry where VMs should be uploaded") flag.StringVar(&vmRegistrySecret, "vm-registry-secret", "", "The name of the secret for the VM registry") + flag.StringVar(&containerImgExport, "container-export-img", "crownlabs/img-exporter", "The image for the img-exporter (container in charge of exporting the disk of a persistent vm)") flag.StringVar(&containerKaniko, "container-kaniko-img", "gcr.io/kaniko-project/executor", "The image for the Kaniko container to be deployed") flag.StringVar(&containerEnvFileBrowserImg, "container-env-filebrowser-img", "filebrowser/filebrowser", "The image name for the filebrowser image (sidecar for gui-based file manager)") flag.StringVar(&containerEnvFileBrowserImgTag, "container-env-filebrowser-img-tag", "latest", "The tag for the FileBrowser container (the gui-based file manager)") + flag.IntVar(&maxConcurrentReconciles, "max-concurrent-reconciles", 8, "The maximum number of concurrent Reconciles which can be run") + klog.InitFlags(nil) flag.Parse() @@ -123,9 +121,7 @@ func main() { NextcloudBaseURL: nextcloudBaseURL, WebsiteBaseURL: websiteBaseURL, WebdavSecretName: webdavSecret, - Oauth2ProxyImage: oauth2ProxyImage, - OidcClientSecret: oidcClientSecret, - OidcProviderURL: oidcProviderURL, + InstancesAuthURL: instancesAuthURL, ContainerEnvOpts: instance_controller.ContainerEnvOpts{ ImagesTag: containerEnvSidecarsTag, VncImg: containerEnvVncImg, diff --git a/operators/deploy/instance-operator/Chart.yaml b/operators/deploy/instance-operator/Chart.yaml index 916c6ef26..172041989 100644 --- a/operators/deploy/instance-operator/Chart.yaml +++ b/operators/deploy/instance-operator/Chart.yaml @@ -15,6 +15,6 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 +version: 0.1.1 icon: https://crownlabs.polito.it/images/logo.svg diff --git a/operators/deploy/instance-operator/templates/deployment.yaml b/operators/deploy/instance-operator/templates/deployment.yaml index ada3cffbe..a644b42d1 100644 --- a/operators/deploy/instance-operator/templates/deployment.yaml +++ b/operators/deploy/instance-operator/templates/deployment.yaml @@ -38,11 +38,9 @@ spec: args: - "--webdav-secret-name={{ .Values.configurations.nextcloud.webdavSecretName }}" - "--namespace-whitelist={{ .Values.configurations.generic.whitelistLabels }}" - - "--website-base-url={{ .Values.configurations.generic.websiteBaseUri }}" + - "--website-base-url={{ .Values.configurations.generic.websiteBaseUrl }}" - "--nextcloud-base-url={{ .Values.configurations.nextcloud.baseUrl }}" - - "--oauth2-proxy-image={{ .Values.configurations.generic.oauth2ProxyImage }}" - - "--oidc-client-secret={{ .Values.configurations.oidc.clientSecret }}" - - "--oidc-provider-url={{ .Values.configurations.oidc.providerUrl }}" + - "--instances-auth-url={{ .Values.configurations.generic.instancesAuthUrl }}" - "--container-env-sidecars-tag={{ include "instance-operator.containerEnvironmentSidecarsTag" . }}" - "--container-env-vnc-img={{ .Values.configurations.containerEnvironmentOptions.vncImage }}" - "--container-env-websockify-img={{ .Values.configurations.containerEnvironmentOptions.websockifyImage }}" diff --git a/operators/deploy/instance-operator/values.yaml b/operators/deploy/instance-operator/values.yaml index 161ebedb2..3b7872782 100644 --- a/operators/deploy/instance-operator/values.yaml +++ b/operators/deploy/instance-operator/values.yaml @@ -6,16 +6,12 @@ replicaCount: 1 configurations: generic: - oauth2ProxyImage: quay.io/oauth2-proxy/oauth2-proxy whitelistLabels: crownlabs.polito.it/operator-selector=production - websiteBaseUri: crownlabs.example.com + websiteBaseUrl: crownlabs.example.com + instancesAuthUrl: https://crownlabs.example.com/auth nextcloud: baseUrl: https://nextcloud.example.com webdavSecretName: nextcloud-credentials - oidc: - clientId: k8s - clientSecret: - providerUrl: https://auth.example.com/auth/realms/crownlabs containerEnvironmentOptions: tag: "" vncImage: crownlabs/tigervnc diff --git a/operators/pkg/instance-controller/common_logic.go b/operators/pkg/instance-controller/common_logic.go index ad5f1edba..6e585c19c 100644 --- a/operators/pkg/instance-controller/common_logic.go +++ b/operators/pkg/instance-controller/common_logic.go @@ -45,7 +45,7 @@ func (r *InstanceReconciler) CreateInstanceExpositionEnvironment( urlUUID := uuid.New().String() // create Ingress to manage the service - ingress := instance_creation.ForgeIngress(name, instance.Namespace, &service, urlUUID, r.WebsiteBaseURL) + ingress := instance_creation.ForgeIngress(name, instance.Namespace, &service, r.WebsiteBaseURL, urlUUID, r.InstancesAuthURL) op, err = ctrl.CreateOrUpdate(ctx, r.Client, &ingress, func() error { return ctrl.SetControllerReference(instance, &ingress, r.Scheme) }) @@ -61,7 +61,7 @@ func (r *InstanceReconciler) CreateInstanceExpositionEnvironment( if hasFileBrowser { // create separate Ingress for FileBrowser to manage the same service - fileBrowserIngress := instance_creation.ForgeFileBrowserIngress(name, instance.Namespace, &service, urlUUID, r.WebsiteBaseURL, fileBrowserPortName) + fileBrowserIngress := instance_creation.ForgeFileBrowserIngress(name, instance.Namespace, &service, urlUUID, r.WebsiteBaseURL, fileBrowserPortName, r.InstancesAuthURL) op, err := ctrl.CreateOrUpdate(ctx, r.Client, &fileBrowserIngress, func() error { return ctrl.SetControllerReference(instance, &fileBrowserIngress, r.Scheme) }) @@ -73,9 +73,5 @@ func (r *InstanceReconciler) CreateInstanceExpositionEnvironment( klog.Infof("Ingress (filebrowser) for instance %s/%s %s", instance.GetNamespace(), instance.GetName(), op) } - if err := r.createOAUTHlogic(name, instance, instance.Namespace, urlUUID); err != nil { - return service, ingress, urlUUID, err - } - return service, ingress, urlUUID, nil } diff --git a/operators/pkg/instance-controller/container_controller_test.go b/operators/pkg/instance-controller/container_controller_test.go index 2ac86b421..015dcd8da 100644 --- a/operators/pkg/instance-controller/container_controller_test.go +++ b/operators/pkg/instance-controller/container_controller_test.go @@ -217,30 +217,6 @@ var _ = Describe("Instance Operator controller for containers", func() { }, &ingr, BeTrue(), timeout, interval) By("Checking that the dedicated FileBrowser ingress has got an OwnerReference") Expect(ingr.ObjectMeta.OwnerReferences).To(ContainElement(expectedOwnerReference)) - - By("Checking that the OAUTH service exists") - doesEventuallyExist(ctx, types.NamespacedName{ - Name: InstanceName + "-oauth2", - Namespace: InstanceNamespace, - }, &svc, BeTrue(), timeout, interval) - By("Checking that the auth service has got an OwnerReference") - Expect(svc.ObjectMeta.OwnerReferences).To(ContainElement(expectedOwnerReference)) - - By("Checking that the OAUTH ingress exists") - doesEventuallyExist(ctx, types.NamespacedName{ - Name: InstanceName + "-oauth2", - Namespace: InstanceNamespace, - }, &ingr, BeTrue(), timeout, interval) - By("Checking that the auth ingress has got an OwnerReference") - Expect(ingr.ObjectMeta.OwnerReferences).To(ContainElement(expectedOwnerReference)) - - By("Checking that the OAUTH deployment exists") - doesEventuallyExist(ctx, types.NamespacedName{ - Name: InstanceName + "-oauth2", - Namespace: InstanceNamespace, - }, &depl, BeTrue(), timeout, interval) - By("Checking that the auth deployment has got an OwnerReference") - Expect(depl.ObjectMeta.OwnerReferences).To(ContainElement(expectedOwnerReference)) }) It("Should create the deployment", func() { diff --git a/operators/pkg/instance-controller/controller.go b/operators/pkg/instance-controller/controller.go index 409a65cad..72d47b29e 100644 --- a/operators/pkg/instance-controller/controller.go +++ b/operators/pkg/instance-controller/controller.go @@ -57,9 +57,7 @@ type InstanceReconciler struct { WebsiteBaseURL string NextcloudBaseURL string WebdavSecretName string - Oauth2ProxyImage string - OidcClientSecret string - OidcProviderURL string + InstancesAuthURL string Concurrency int ContainerEnvOpts ContainerEnvOpts diff --git a/operators/pkg/instance-controller/logic.go b/operators/pkg/instance-controller/logic.go index e82faa02c..a8fbe0aeb 100644 --- a/operators/pkg/instance-controller/logic.go +++ b/operators/pkg/instance-controller/logic.go @@ -96,41 +96,6 @@ func (r *InstanceReconciler) CreateVMEnvironment(instance *crownlabsv1alpha2.Ins return nil } -func (r *InstanceReconciler) createOAUTHlogic(name string, instance *crownlabsv1alpha2.Instance, namespace, urlUUID string) error { - ctx := context.TODO() - - // create Service for oauth2 - oauthService := instance_creation.ForgeOauth2Service(name, namespace) - if _, err := ctrl.CreateOrUpdate(ctx, r.Client, &oauthService, func() error { - return ctrl.SetControllerReference(instance, &oauthService, r.Scheme) - }); err != nil { - r.setInstanceStatus(ctx, "Could not create service "+oauthService.Name+" in namespace "+oauthService.Namespace, "Warning", "Oauth2ServiceNotCreated", instance, "", "") - return err - } - r.setInstanceStatus(ctx, "Service "+oauthService.Name+" correctly created in namespace "+oauthService.Namespace, "Normal", "Oauth2ServiceCreated", instance, "", "") - - // create Ingress to manage the oauth2 service - oauthIngress := instance_creation.ForgeOauth2Ingress(name, namespace, &oauthService, urlUUID, r.WebsiteBaseURL) - if _, err := ctrl.CreateOrUpdate(ctx, r.Client, &oauthIngress, func() error { - return ctrl.SetControllerReference(instance, &oauthIngress, r.Scheme) - }); err != nil { - r.setInstanceStatus(ctx, "Could not create ingress "+oauthIngress.Name+" in namespace "+oauthIngress.Namespace, "Warning", "Oauth2IngressNotCreated", instance, "", "") - return err - } - r.setInstanceStatus(ctx, "Ingress "+oauthIngress.Name+" correctly created in namespace "+oauthIngress.Namespace, "Normal", "Oauth2IngressCreated", instance, "", "") - - // create Deployment for oauth2 - oauthDeploy := instance_creation.ForgeOauth2Deployment(name, namespace, urlUUID, r.Oauth2ProxyImage, r.OidcClientSecret, r.OidcProviderURL) - if _, err := ctrl.CreateOrUpdate(ctx, r.Client, &oauthDeploy, func() error { - return ctrl.SetControllerReference(instance, &oauthDeploy, r.Scheme) - }); err != nil { - r.setInstanceStatus(ctx, "Could not create deployment "+oauthDeploy.Name+" in namespace "+oauthDeploy.Namespace, "Warning", "Oauth2DeployNotCreated", instance, "", "") - return err - } - r.setInstanceStatus(ctx, "Deployment "+oauthDeploy.Name+" correctly created in namespace "+oauthDeploy.Namespace, "Normal", "Oauth2DeployCreated", instance, "", "") - return nil -} - func (r *InstanceReconciler) createPersistentlogic(instance *crownlabsv1alpha2.Instance, environment *crownlabsv1alpha2.Environment, name string) (bool, error) { ctx := context.TODO() // create datavolume diff --git a/operators/pkg/instance-controller/suite_test.go b/operators/pkg/instance-controller/suite_test.go index 502d2de68..95b874b73 100644 --- a/operators/pkg/instance-controller/suite_test.go +++ b/operators/pkg/instance-controller/suite_test.go @@ -94,9 +94,7 @@ var _ = BeforeSuite(func(done Done) { NextcloudBaseURL: "fake.com", WebsiteBaseURL: "fakesite.com", WebdavSecretName: "webdav-secret", - Oauth2ProxyImage: "test-image/test", - OidcClientSecret: "sdad-csad-cdsw-asde", - OidcProviderURL: "provider-url.com", + InstancesAuthURL: "fake.com/auth", ReconcileDeferHook: GinkgoRecover, }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) diff --git a/operators/pkg/instance-creation/exposition.go b/operators/pkg/instance-creation/exposition.go index 833fbe576..4919a2c84 100644 --- a/operators/pkg/instance-creation/exposition.go +++ b/operators/pkg/instance-creation/exposition.go @@ -1,16 +1,10 @@ package instance_creation import ( - "encoding/base64" - - "github.com/google/uuid" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/utils/pointer" ) // ForgeService creates and returns a Kubernetes Service resource providing @@ -47,25 +41,25 @@ func ForgeService(name, namespace string) corev1.Service { // ForgeIngress creates and returns a Kubernetes Ingress resource providing // exposing the remote desktop of a CrownLabs environment. -func ForgeIngress(name, namespace string, svc *corev1.Service, urlUUID, websiteBaseURL string) networkingv1.Ingress { +func ForgeIngress(name, namespace string, svc *corev1.Service, websiteBaseURL, urlUUID, instancesAuthURL string) networkingv1.Ingress { pathType := networkingv1.PathTypePrefix url := websiteBaseURL + "/" + urlUUID + annotations := map[string]string{ + "nginx.ingress.kubernetes.io/rewrite-target": "/$2", + "nginx.ingress.kubernetes.io/proxy-read-timeout": "3600", + "nginx.ingress.kubernetes.io/proxy-send-timeout": "3600", + "crownlabs.polito.it/probe-url": "https://" + url, + "crownlabs.polito.it/url-uuid": urlUUID, + "nginx.ingress.kubernetes.io/configuration-snippet": `sub_filter '' ' ';`, + } + annotations = appendInstancesAuthAnnotations(annotations, instancesAuthURL) + ingress := networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: nil, - Annotations: map[string]string{ - "nginx.ingress.kubernetes.io/rewrite-target": "/$2", - "nginx.ingress.kubernetes.io/proxy-read-timeout": "3600", - "nginx.ingress.kubernetes.io/proxy-send-timeout": "3600", - "nginx.ingress.kubernetes.io/auth-signin": "https://$host/" + urlUUID + "/oauth2/start?rd=$escaped_request_uri", - "nginx.ingress.kubernetes.io/auth-url": "https://$host/" + urlUUID + "/oauth2/auth", - "crownlabs.polito.it/probe-url": "https://" + url, - "crownlabs.polito.it/url-uuid": urlUUID, - "nginx.ingress.kubernetes.io/configuration-snippet": `sub_filter '' ' ';`, - }, + Name: name, + Namespace: namespace, + Annotations: annotations, }, Spec: networkingv1.IngressSpec{ TLS: []networkingv1.IngressTLS{ @@ -107,23 +101,23 @@ func ForgeIngress(name, namespace string, svc *corev1.Service, urlUUID, websiteB // exposing FileBrowser for a CrownLabs container environment. func ForgeFileBrowserIngress( name, namespace string, svc *corev1.Service, - urlUUID, websiteBaseURL, fileBrowserPortName string, + urlUUID, websiteBaseURL, fileBrowserPortName, instancesAuthURL string, ) networkingv1.Ingress { pathType := networkingv1.PathTypePrefix + annotations := map[string]string{ + "nginx.ingress.kubernetes.io/proxy-body-size": "0", + "nginx.ingress.kubernetes.io/proxy-read-timeout": "600", + "nginx.ingress.kubernetes.io/proxy-send-timeout": "600", + "nginx.ingress.kubernetes.io/proxy-max-temp-file-size": "0", + } + annotations = appendInstancesAuthAnnotations(annotations, instancesAuthURL) + ingress := networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ - Name: name + "-filebrowser", - Namespace: namespace, - Labels: nil, - Annotations: map[string]string{ - "nginx.ingress.kubernetes.io/auth-signin": "https://$host/" + urlUUID + "/oauth2/start?rd=$escaped_request_uri", - "nginx.ingress.kubernetes.io/auth-url": "https://$host/" + urlUUID + "/oauth2/auth", - "nginx.ingress.kubernetes.io/proxy-body-size": "0", - "nginx.ingress.kubernetes.io/proxy-read-timeout": "600", - "nginx.ingress.kubernetes.io/proxy-send-timeout": "600", - "nginx.ingress.kubernetes.io/proxy-max-temp-file-size": "0", - }, + Name: name + "-filebrowser", + Namespace: namespace, + Annotations: annotations, }, Spec: networkingv1.IngressSpec{ TLS: []networkingv1.IngressTLS{ @@ -161,166 +155,12 @@ func ForgeFileBrowserIngress( return ingress } -// ForgeOauth2Deployment creates and returns a Kubernetes Deployment resource -// for oauth2-proxy, which is used to enforce authentication when connecting -// to the remote desktop of a CrownLabs environment. -func ForgeOauth2Deployment(name, namespace, urlUUID, image, clientSecret, providerURL string) appsv1.Deployment { - cookieUUID := uuid.New().String() - id, _ := uuid.New().MarshalBinary() - cookieSecret := base64.StdEncoding.EncodeToString(id) - labels := generateOauth2Labels(name) +// appendInstancesAuthAnnotations appends the set of nginx annotations required to enable +// the authentication in front of an ingress resource. instancesAuthURL represents the +// URL of an exposed oauth2-proxy instance properly configured. +func appendInstancesAuthAnnotations(annotations map[string]string, instancesAuthURL string) map[string]string { + annotations["nginx.ingress.kubernetes.io/auth-url"] = instancesAuthURL + "/auth" + annotations["nginx.ingress.kubernetes.io/auth-signin"] = instancesAuthURL + "/start?rd=$escaped_request_uri" - deploy := appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: name + "-oauth2", - Namespace: namespace, - Labels: labels, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: pointer.Int32Ptr(1), - Selector: &metav1.LabelSelector{ - MatchLabels: labels, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: labels, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: name, - Image: image, - Args: []string{ - "--http-address=0.0.0.0:4180", - "--reverse-proxy=true", - "--skip-provider-button=true", - "--cookie-secret=" + cookieSecret, - "--cookie-expire=24h", - "--cookie-name=_oauth2_cookie_" + string([]rune(cookieUUID)[:6]), - "--provider=keycloak", - "--client-id=k8s", - "--client-secret=" + clientSecret, - "--login-url=" + providerURL + "/protocol/openid-connect/auth", - "--redeem-url=" + providerURL + "/protocol/openid-connect/token", - "--validate-url=" + providerURL + "/protocol/openid-connect/userinfo", - "--proxy-prefix=/" + urlUUID + "/oauth2", - "--cookie-path=/" + urlUUID, - "--email-domain=*", - "--session-cookie-minimal=true", - }, - Ports: []corev1.ContainerPort{ - { - ContainerPort: 4180, - Protocol: corev1.ProtocolTCP, - }, - }, - Resources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("50m"), - corev1.ResourceMemory: resource.MustParse("100Mi"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("10m"), - corev1.ResourceMemory: resource.MustParse("25Mi"), - }, - }, - }, - }, - }, - }, - }, - } - - return deploy -} - -// ForgeOauth2Service creates and returns a Kubernetes Service resource -// for oauth2-proxy, which is used to enforce authentication when connecting -// to the remote desktop of a CrownLabs environment. -func ForgeOauth2Service(name, namespace string) corev1.Service { - labels := generateOauth2Labels(name) - service := corev1.Service{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: name + "-oauth2", - Namespace: namespace, - Labels: labels, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: "http", - Protocol: corev1.ProtocolTCP, - Port: 4180, - TargetPort: intstr.IntOrString{IntVal: 4180}, - }, - }, - Selector: labels, - }, - } - - return service -} - -// ForgeOauth2Ingress creates and returns a Kubernetes Ingress resource -// for oauth2-proxy, which is used to enforce authentication when connecting -// to the remote desktop of a CrownLabs environment. -func ForgeOauth2Ingress(name, namespace string, svc *corev1.Service, urlUUID, websiteBaseURL string) networkingv1.Ingress { - pathType := networkingv1.PathTypePrefix - ingress := networkingv1.Ingress{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: name + "-oauth2", - Namespace: namespace, - Labels: generateOauth2Labels(name), - Annotations: map[string]string{ - "nginx.ingress.kubernetes.io/cors-allow-credentials": "true", - "nginx.ingress.kubernetes.io/cors-allow-headers": "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization", - "nginx.ingress.kubernetes.io/cors-allow-methods": "PUT, GET, POST, OPTIONS, DELETE, PATCH", - "nginx.ingress.kubernetes.io/cors-allow-origin": "https://*", - "nginx.ingress.kubernetes.io/enable-cors": "true", - }, - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - { - Hosts: []string{websiteBaseURL}, - SecretName: "crownlabs-ingress-secret", - }, - }, - Rules: []networkingv1.IngressRule{ - { - Host: websiteBaseURL, - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/" + urlUUID + "/oauth2/.*", - PathType: &pathType, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: svc.Name, - Port: networkingv1.ServiceBackendPort{ - Number: svc.Spec.Ports[0].TargetPort.IntVal, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } - - return ingress -} - -// generateOauth2Labels returns a map of labels common to all oauth2-proxy resources. -func generateOauth2Labels(instanceName string) map[string]string { - return map[string]string{ - "app.kubernetes.io/part-of": instanceName, - "app.kubernetes.io/component": "oauth2-proxy", - } + return annotations } diff --git a/operators/pkg/instance-creation/exposition_test.go b/operators/pkg/instance-creation/exposition_test.go index 1ca5e5e7a..d587acc1c 100644 --- a/operators/pkg/instance-creation/exposition_test.go +++ b/operators/pkg/instance-creation/exposition_test.go @@ -31,11 +31,12 @@ func TestForgeService(t *testing.T) { func TestForgeIngress(t *testing.T) { var ( - name = "usertest" - namespace = "namespacetest" - urlUUID = "urlUUIDtest" - websiteBaseURL = "websiteBaseUrlTest" - svc = corev1.Service{ + name = "usertest" + namespace = "namespacetest" + urlUUID = "urlUUIDtest" + websiteBaseURL = "websiteBaseUrlTest" + instancesAuthURL = "fake.com/auth" + svc = corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "svc-test", }, @@ -50,94 +51,46 @@ func TestForgeIngress(t *testing.T) { url = websiteBaseURL + "/" + urlUUID ) - ingress := ForgeIngress(name, namespace, &svc, urlUUID, websiteBaseURL) + instancesAuthAnnotations := appendInstancesAuthAnnotations(map[string]string{}, instancesAuthURL) + ingress := ForgeIngress(name, namespace, &svc, websiteBaseURL, urlUUID, instancesAuthURL) assert.Equal(t, ingress.ObjectMeta.Name, name) assert.Equal(t, ingress.ObjectMeta.Namespace, namespace) assert.Equal(t, ingress.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Backend.Service.Name, svc.Name) assert.Equal(t, ingress.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Backend.Service.Port.Number, svc.Spec.Ports[0].TargetPort.IntVal) - assert.Equal(t, ingress.ObjectMeta.Annotations["nginx.ingress.kubernetes.io/auth-signin"], "https://$host/"+urlUUID+"/oauth2/start?rd=$escaped_request_uri") - assert.Equal(t, ingress.ObjectMeta.Annotations["nginx.ingress.kubernetes.io/auth-url"], "https://$host/"+urlUUID+"/oauth2/auth") assert.Equal(t, ingress.ObjectMeta.Annotations["nginx.ingress.kubernetes.io/configuration-snippet"], `sub_filter '' ' ';`) assert.Equal(t, ingress.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Path, "/"+urlUUID+"(/|$)(.*)") assert.Equal(t, ingress.ObjectMeta.Annotations["crownlabs.polito.it/probe-url"], "https://"+url) assert.Equal(t, ingress.Spec.TLS[0].Hosts[0], websiteBaseURL) assert.Equal(t, ingress.Spec.Rules[0].Host, websiteBaseURL) -} - -func TestForgeOauth2Deployment(t *testing.T) { - var ( - name = "usertest" - namespace = "namespacetest" - urlUUID = "urlUUIDtest" - image = "imagetest" - clientSecret = "secrettest" - providerURL = "urltest" - ) - deploy := ForgeOauth2Deployment(name, namespace, urlUUID, image, clientSecret, providerURL) - - assert.Equal(t, deploy.ObjectMeta.Name, name+"-oauth2") - assert.Equal(t, deploy.ObjectMeta.Namespace, namespace) - assert.Equal(t, deploy.Spec.Template.Spec.Containers[0].Image, image) - assert.Contains(t, deploy.Spec.Template.Spec.Containers[0].Args, "--proxy-prefix=/"+urlUUID+"/oauth2") - assert.Contains(t, deploy.Spec.Template.Spec.Containers[0].Args, "--cookie-path=/"+urlUUID) - assert.Contains(t, deploy.Spec.Template.Spec.Containers[0].Args, "--client-secret="+clientSecret) - assert.Contains(t, deploy.Spec.Template.Spec.Containers[0].Args, "--login-url="+providerURL+"/protocol/openid-connect/auth") - assert.Contains(t, deploy.Spec.Template.Spec.Containers[0].Args, "--redeem-url="+providerURL+"/protocol/openid-connect/token") - assert.Contains(t, deploy.Spec.Template.Spec.Containers[0].Args, "--validate-url="+providerURL+"/protocol/openid-connect/userinfo") + for key, value := range instancesAuthAnnotations { + assert.Contains(t, ingress.GetAnnotations(), key) + assert.Equal(t, ingress.GetAnnotations()[key], value) + } } -func TestForgeOauth2Service(t *testing.T) { - var ( - name = "usertest" - namespace = "namespacetest" +func TestAppendInstancesAuthAnnotations(t *testing.T) { + const ( + instancesAuthURL = "fake.com/auth" + originalKey = "originalKey" + originalValue = "originalValue" ) - service := ForgeOauth2Service(name, namespace) + originalAnnotations := map[string]string{ + originalKey: originalValue, + } - assert.Equal(t, service.ObjectMeta.Name, name+"-oauth2") - assert.Equal(t, service.ObjectMeta.Namespace, namespace) - assert.Equal(t, service.Spec.Selector, generateOauth2Labels(name)) -} + resultingAnnotations := appendInstancesAuthAnnotations(originalAnnotations, instancesAuthURL) -func TestForgeOauth2Ingress(t *testing.T) { - var ( - name = "usertest" - namespace = "namespacetest" - urlUUID = "urlUUIDtest" - websiteBaseURL = "websiteBaseUrlTest" - svc = corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "svc-test", - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - TargetPort: intstr.IntOrString{IntVal: 22}, - }, - }, - }, - } - ) - - ingress := ForgeOauth2Ingress(name, namespace, &svc, urlUUID, websiteBaseURL) - - assert.Equal(t, ingress.ObjectMeta.Name, name+"-oauth2") - assert.Equal(t, ingress.ObjectMeta.Namespace, namespace) - assert.Equal(t, ingress.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Backend.Service.Name, svc.Name) - assert.Equal(t, ingress.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Backend.Service.Port.Number, svc.Spec.Ports[0].TargetPort.IntVal) - assert.Equal(t, ingress.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Path, "/"+urlUUID+"/oauth2/.*") - assert.Equal(t, ingress.Spec.TLS[0].Hosts[0], websiteBaseURL) - assert.Equal(t, ingress.Spec.Rules[0].Host, websiteBaseURL) -} + // The original annotations are unmodified + assert.Contains(t, resultingAnnotations, originalKey) + assert.Equal(t, resultingAnnotations[originalKey], originalValue) -func TestGenerateOauth2Labels(t *testing.T) { - instanceName := "oauth2-foo" - labels := generateOauth2Labels(instanceName) + // The new annotations are added correctly + assert.Contains(t, resultingAnnotations, "nginx.ingress.kubernetes.io/auth-url") + assert.Contains(t, resultingAnnotations, "nginx.ingress.kubernetes.io/auth-signin") - assert.Contains(t, labels, "app.kubernetes.io/part-of") - assert.Contains(t, labels, "app.kubernetes.io/component") - assert.Equal(t, labels["app.kubernetes.io/part-of"], instanceName) - assert.Equal(t, labels["app.kubernetes.io/component"], "oauth2-proxy") + assert.Equal(t, resultingAnnotations["nginx.ingress.kubernetes.io/auth-url"], instancesAuthURL+"/auth") + assert.Equal(t, resultingAnnotations["nginx.ingress.kubernetes.io/auth-signin"], instancesAuthURL+"/start?rd=$escaped_request_uri") }