diff --git a/Makefile b/Makefile index 6cfed119d..d2c1473e5 100644 --- a/Makefile +++ b/Makefile @@ -303,7 +303,7 @@ vet: ## Run go vet against code. $(GO) vet ./... .PHONY: test -test: manifests generate fmt vet envtest ## Run tests. +test: manifests generate fmt vet envtest images ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" $(GO) test ./... -coverprofile cover.out .PHONY: update-version diff --git a/controllers/operator/configmap.go b/controllers/operator/configmap.go index 3717a5026..830a20d25 100644 --- a/controllers/operator/configmap.go +++ b/controllers/operator/configmap.go @@ -45,6 +45,7 @@ import ( "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/discovery" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -339,6 +340,18 @@ func (r *AuthenticationReconciler) handleConfigMap(instance *operatorv1alpha1.Au currentConfigMap.Data["LDAP_CTX_POOL_PREFERREDSIZE"] = newConfigMap.Data["LDAP_CTX_POOL_PREFERREDSIZE"] cmUpdateRequired = true } + // Indicates an upgrade from a previous + if _, keyExists := currentConfigMap.Data["MASTER_PATH"]; !keyExists { + req := ctrl.Request{NamespacedName: types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}} + var path string + path, err = r.getMasterPath(context.Background(), req) + if err != nil { + reqLogger.Error(err, "Failed to determine if a SAML connection already exists") + return + } + currentConfigMap.Data["MASTER_PATH"] = path + cmUpdateRequired = true + } _, keyExists := currentConfigMap.Data["IS_OPENSHIFT_ENV"] if keyExists { reqLogger.Info("Current configmap", "Current Value", currentConfigMap.Data["IS_OPENSHIFT_ENV"]) @@ -517,6 +530,10 @@ func (r *AuthenticationReconciler) authIdpConfigMap(instance *operatorv1alpha1.A if isPublicCloud { roksUserPrefix = "IAM#" } + + // Set the path for SAML connections + masterPath := "/idauth" + newConfigMap := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "platform-auth-idp", @@ -532,6 +549,7 @@ func (r *AuthenticationReconciler) authIdpConfigMap(instance *operatorv1alpha1.A "IDENTITY_PROVIDER_URL": "https://platform-identity-provider:4300", "IDENTITY_MGMT_URL": "https://platform-identity-management:4500", "MASTER_HOST": clusterAddress, + "MASTER_PATH": masterPath, "NODE_ENV": "production", "AUDIT_ENABLED_IDPROVIDER": "false", "AUDIT_ENABLED_IDMGMT": "false", @@ -1000,3 +1018,22 @@ func readROKSURL(instance *operatorv1alpha1.Authentication) (string, error) { } return issuer, nil } + +func (r *AuthenticationReconciler) getMasterPath(ctx context.Context, req ctrl.Request) (path string, err error) { + p, err := r.getPostgresDB(ctx, req) + if err != nil { + return + } + var has bool + if err = p.Connect(ctx); err != nil { + return + } + defer p.Disconnect(ctx) + + if has, err = p.HasSAML(ctx); err != nil { + return + } else if has { + return "", err + } + return "/idauth", nil +} diff --git a/controllers/operator/containers.go b/controllers/operator/containers.go index 02814364b..f1c5fbbb1 100644 --- a/controllers/operator/containers.go +++ b/controllers/operator/containers.go @@ -277,7 +277,7 @@ func buildAuthServiceContainer(instance *operatorv1alpha1.Authentication, authSe }, } - idpEnvVarList := []string{"NODE_ENV", "MASTER_HOST", "IDENTITY_PROVIDER_URL", "HTTP_ONLY", "SESSION_TIMEOUT", "LDAP_RECURSIVE_SEARCH", "LDAP_ATTR_CACHE_SIZE", "LDAP_ATTR_CACHE_TIMEOUT", "LDAP_ATTR_CACHE_ENABLED", "LDAP_ATTR_CACHE_SIZELIMIT", + idpEnvVarList := []string{"NODE_ENV", "MASTER_PATH", "MASTER_HOST", "IDENTITY_PROVIDER_URL", "HTTP_ONLY", "SESSION_TIMEOUT", "LDAP_RECURSIVE_SEARCH", "LDAP_ATTR_CACHE_SIZE", "LDAP_ATTR_CACHE_TIMEOUT", "LDAP_ATTR_CACHE_ENABLED", "LDAP_ATTR_CACHE_SIZELIMIT", "LDAP_SEARCH_CACHE_SIZE", "LDAP_SEARCH_CACHE_TIMEOUT", "LDAP_CTX_POOL_INITSIZE", "LDAP_CTX_POOL_MAXSIZE", "LDAP_CTX_POOL_TIMEOUT", "LDAP_CTX_POOL_WAITTIME", "LDAP_CTX_POOL_PREFERREDSIZE", "IDENTITY_PROVIDER_URL", "IDENTITY_MGMT_URL", "LDAP_SEARCH_CACHE_ENABLED", "LDAP_SEARCH_CACHE_SIZELIMIT", "IDTOKEN_LIFETIME", "IBMID_CLIENT_ID", "IBMID_CLIENT_ISSUER", "SAML_NAMEID_FORMAT", "FIPS_ENABLED", "LOGJAM_DHKEYSIZE_2048_BITS_ENABLED", "LOG_LEVEL_AUTHSVC", "LIBERTY_DEBUG_ENABLED", "NONCE_ENABLED", "CLAIMS_SUPPORTED", "CLAIMS_MAP", "SCOPE_CLAIM", "OIDC_ISSUER_URL", "DB_CONNECT_TIMEOUT", "DB_IDLE_TIMEOUT", "DB_CONNECT_MAX_RETRIES", "DB_POOL_MIN_SIZE", "DB_POOL_MAX_SIZE", "DB_SSL_MODE"} @@ -601,7 +601,7 @@ func buildIdentityProviderContainer(instance *operatorv1alpha1.Authentication, i }, } - idpEnvVarList := []string{"NODE_ENV", "LOG_LEVEL_IDPROVIDER", "LOG_LEVEL_MW", "PROVIDER_ISSUER_URL", "MASTER_HOST", + idpEnvVarList := []string{"NODE_ENV", "LOG_LEVEL_IDPROVIDER", "LOG_LEVEL_MW", "PROVIDER_ISSUER_URL", "MASTER_HOST", "MASTER_PATH", "PREFERRED_LOGIN", "IDTOKEN_LIFETIME", "SAAS_CLIENT_REDIRECT_URL", "IBM_CLOUD_SAAS", "ROKS_ENABLED", "ROKS_URL", "ROKS_USER_PREFIX", "OS_TOKEN_LENGTH", "LIBERTY_TOKEN_LENGTH", "IDENTITY_PROVIDER_URL", "BASE_AUTH_URL", "BASE_OIDC_URL", "SCOPE_CLAIM", "OIDC_ISSUER_URL", "HTTP_ONLY", "IGNORE_LDAP_FILTERS_VALIDATION", diff --git a/controllers/operator/ingress.go b/controllers/operator/ingress.go index b81e2d3cc..e5db79cff 100644 --- a/controllers/operator/ingress.go +++ b/controllers/operator/ingress.go @@ -34,6 +34,8 @@ var ingressList []string = []string{ "id-mgmt", "idmgmt-v2-api", "platform-auth", + "platform-id-auth-block", + "platform-id-auth", "platform-id-provider", "platform-login", "platform-oidc-block", @@ -94,6 +96,8 @@ func (r *AuthenticationReconciler) handleIngress(instance *operatorv1alpha1.Auth idMgmtIngress, idmgmtV2ApiIngress, platformAuthIngress, + platformIdAuthBlockIngress, + platformIdAuthIngress, platformIdProviderIngress, platformLoginIngress, platformOidcBlockIngress, @@ -345,6 +349,112 @@ func platformAuthIngress(instance *operatorv1alpha1.Authentication, scheme *runt } +func platformIdAuthBlockIngress(instance *operatorv1alpha1.Authentication, scheme *runtime.Scheme) *netv1.Ingress { + pathType := netv1.PathType("ImplementationSpecific") + reqLogger := log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) + newIngress := &netv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "platform-id-auth-block", + Namespace: instance.Namespace, + Labels: map[string]string{"app": "platform-auth-service"}, + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "ibm-icp-management", + "icp.management.ibm.com/location-modifier": "=", + "icp.management.ibm.com/configuration-snippet": ` + add_header 'X-XSS-Protection' '1' always; + `, + }, + }, + Spec: netv1.IngressSpec{ + Rules: []netv1.IngressRule{ + { + IngressRuleValue: netv1.IngressRuleValue{ + HTTP: &netv1.HTTPIngressRuleValue{ + Paths: []netv1.HTTPIngressPath{ + { + Path: "/idauth/oidc/endpoint", + PathType: &pathType, + Backend: netv1.IngressBackend{ + Service: &netv1.IngressServiceBackend{ + Name: "default-http-backend", + Port: netv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + // Set Authentication instance as the owner and controller of the Ingress + err := controllerutil.SetControllerReference(instance, newIngress, scheme) + if err != nil { + reqLogger.Error(err, "Failed to set owner for Ingress") + return nil + } + return newIngress + +} + +func platformIdAuthIngress(instance *operatorv1alpha1.Authentication, scheme *runtime.Scheme) *netv1.Ingress { + pathType := netv1.PathType("ImplementationSpecific") + reqLogger := log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) + newIngress := &netv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "platform-id-auth", + Namespace: instance.Namespace, + Labels: map[string]string{"app": "platform-auth-service"}, + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "ibm-icp-management", + "icp.management.ibm.com/secure-backends": "true", + "icp.management.ibm.com/rewrite-target": "/", + "icp.management.ibm.com/configuration-snippet": ` + add_header 'X-Frame-Options' 'SAMEORIGIN' always; + add_header 'X-Content-Type-Options' 'nosniff'; + `, + }, + }, + Spec: netv1.IngressSpec{ + Rules: []netv1.IngressRule{ + { + IngressRuleValue: netv1.IngressRuleValue{ + HTTP: &netv1.HTTPIngressRuleValue{ + Paths: []netv1.HTTPIngressPath{ + { + Path: "/idauth", + PathType: &pathType, + Backend: netv1.IngressBackend{ + Service: &netv1.IngressServiceBackend{ + Name: "platform-auth-service", + Port: netv1.ServiceBackendPort{ + Number: 9443, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + // Set Authentication instance as the owner and controller of the Ingress + err := controllerutil.SetControllerReference(instance, newIngress, scheme) + if err != nil { + reqLogger.Error(err, "Failed to set owner for Ingress") + return nil + } + return newIngress + +} + func platformIdProviderIngress(instance *operatorv1alpha1.Authentication, scheme *runtime.Scheme) *netv1.Ingress { pathType := netv1.PathType("ImplementationSpecific") reqLogger := log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) diff --git a/controllers/operator/resourcestatus.go b/controllers/operator/resourcestatus.go index b782b2610..f179af198 100644 --- a/controllers/operator/resourcestatus.go +++ b/controllers/operator/resourcestatus.go @@ -230,6 +230,7 @@ func (r *AuthenticationReconciler) getCurrentServiceStatus(ctx context.Context, names: []string{ "id-mgmt", "platform-auth", + "platform-id-auth", "platform-id-provider", "platform-login", "platform-oidc", diff --git a/controllers/operator/routes.go b/controllers/operator/routes.go index d5c793789..18bb2cf88 100644 --- a/controllers/operator/routes.go +++ b/controllers/operator/routes.go @@ -184,6 +184,18 @@ func (r *AuthenticationReconciler) handleRoutes(ctx context.Context, instance *o DestinationCAcert: platformIdentityProviderCert, ServiceName: PlatformIdentityProviderServiceName, }, + "platform-id-auth": { + Annotations: map[string]string{ + "haproxy.router.openshift.io/balance": "source", + "haproxy.router.openshift.io/rewrite-target": "/", + }, + Name: "platform-id-auth", + RouteHost: routeHost, + RoutePath: "/idauth", + RoutePort: 9443, + DestinationCAcert: platformAuthCert, + ServiceName: PlatformAuthServiceName, + }, "platform-id-provider": { Annotations: map[string]string{ "haproxy.router.openshift.io/rewrite-target": "/", @@ -265,29 +277,6 @@ func (r *AuthenticationReconciler) handleRoutes(ctx context.Context, instance *o return } -func (r *AuthenticationReconciler) removeIdauth(ctx context.Context, instance *operatorv1alpha1.Authentication) (err error) { - namespace := instance.Namespace - reqLogger := log.WithValues("func", "ReconcileRoute", "namespace", namespace) - reqLogger.Info("Determined platform-id-auth Route should not exist; removing if present") - observedRoute := &routev1.Route{} - err = r.Get(ctx, types.NamespacedName{Name: "platform-id-auth", Namespace: namespace}, observedRoute) - if errors.IsNotFound(err) { - return nil - } else if err != nil { - reqLogger.Error(err, "Failed to get existing platform-id-auth route for reconciliation") - return - } - err = r.Delete(ctx, observedRoute) - if errors.IsNotFound(err) { - return nil - } else if err != nil { - reqLogger.Error(err, "Failed to delete platform-id-auth Route") - return - } - reqLogger.Info("Successfully deleted platform-id-auth Route") - return -} - func (r *AuthenticationReconciler) reconcileRoute(ctx context.Context, instance *operatorv1alpha1.Authentication, fields *reconcileRouteFields, needToRequeue *bool) (err error) { namespace := instance.Namespace @@ -295,13 +284,6 @@ func (r *AuthenticationReconciler) reconcileRoute(ctx context.Context, instance reqLogger.Info("Reconciling route", "annotations", fields.Annotations, "routeHost", fields.RouteHost, "routePath", fields.RoutePath) - err = r.removeIdauth(ctx, instance) - - if err != nil { - reqLogger.Error(err, "Error deleting platform-id-auth Route") - return - } - calculatedRoute, err := r.newRoute(instance, fields) if err != nil { reqLogger.Error(err, "Error creating desired route for reconcilition") diff --git a/database/connectors/dbconn.go b/database/connectors/dbconn.go index 0923129b9..3137564ee 100644 --- a/database/connectors/dbconn.go +++ b/database/connectors/dbconn.go @@ -17,6 +17,7 @@ package connectors import ( "context" + "errors" "fmt" "github.com/jackc/pgx/v5" @@ -128,6 +129,20 @@ func (p *PostgresDB) HasSchemas(ctx context.Context) (bool, error) { return true, nil } + +func (p *PostgresDB) HasSAML(ctx context.Context) (has bool, err error) { + query := "SELECT 1 FROM platformdb.idp_configs WHERE protocol = 'saml'" + row := p.Conn.QueryRow(ctx, query) + var s any + err = row.Scan(&s) + if errors.Is(err, pgx.ErrNoRows) { + return false, nil + } else if err != nil { + return false, err + } + return true, nil +} + func (p *PostgresDB) HasMetadataSchema(ctx context.Context) (has bool, err error) { query := "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = 'metadata'" var table string