diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 4719ca358..6c93b2025 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -57,6 +57,7 @@ type impersonatorConfigController struct { namespace string credentialIssuerResourceName string generatedLoadBalancerServiceName string + generatedClusterIPServiceName string tlsSecretName string caSecretName string impersonationSignerSecretName string @@ -90,6 +91,7 @@ func NewImpersonatorConfigController( withInformer pinnipedcontroller.WithInformerOptionFunc, withInitialEvent pinnipedcontroller.WithInitialEventOptionFunc, generatedLoadBalancerServiceName string, + generatedClusterIPServiceName string, tlsSecretName string, caSecretName string, labels map[string]string, @@ -106,6 +108,7 @@ func NewImpersonatorConfigController( namespace: namespace, credentialIssuerResourceName: credentialIssuerResourceName, generatedLoadBalancerServiceName: generatedLoadBalancerServiceName, + generatedClusterIPServiceName: generatedClusterIPServiceName, tlsSecretName: tlsSecretName, caSecretName: caSecretName, impersonationSignerSecretName: impersonationSignerSecretName, @@ -234,6 +237,16 @@ func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v } } + if c.shouldHaveClusterIPService(config) { + if err = c.ensureClusterIPServiceIsStarted(ctx, config); err != nil { + return nil, err + } + } // else { // TODO test stopping the cluster ip service + // if err = c.ensureClusterIPServiceIsStopped(ctx); err != nil { + // return nil, err + // } + //} + nameInfo, err := c.findDesiredTLSCertificateName(config) if err != nil { // Unexpected error while determining the name that should go into the certs, so clear any existing certs. @@ -308,6 +321,10 @@ func (c *impersonatorConfigController) shouldHaveLoadBalancer(config *v1alpha1.I return c.shouldHaveImpersonator(config) && config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeLoadBalancer } +func (c *impersonatorConfigController) shouldHaveClusterIPService(config *v1alpha1.ImpersonationProxySpec) bool { + return c.shouldHaveImpersonator(config) && config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeClusterIP +} + func (c *impersonatorConfigController) shouldHaveTLSSecret(config *v1alpha1.ImpersonationProxySpec) bool { return c.shouldHaveImpersonator(config) } @@ -491,6 +508,52 @@ func (c *impersonatorConfigController) ensureLoadBalancerIsStopped(ctx context.C return c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedLoadBalancerServiceName, metav1.DeleteOptions{}) } +func (c *impersonatorConfigController) ensureClusterIPServiceIsStarted(ctx context.Context, config *v1alpha1.ImpersonationProxySpec) error { + appNameLabel := c.labels[appLabelKey] + clusterIP := v1.Service{ + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeClusterIP, + Ports: []v1.ServicePort{ + { + TargetPort: intstr.FromInt(impersonationProxyPort), + Port: defaultHTTPSPort, + Protocol: v1.ProtocolTCP, + }, + }, + Selector: map[string]string{appLabelKey: appNameLabel}, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: c.generatedClusterIPServiceName, + Namespace: c.namespace, + Labels: c.labels, + Annotations: config.Service.Annotations, + }, + } + //running, err := c.ClusterIPExists() // TODO test that clusterip is only created once + //if err != nil { + // return err + //} + //if running { + // needsUpdate, err := c.ClusterIPNeedsUpdate(config) // TODO test updating annotations on clusterip + // if err != nil { + // return err + // } + // if needsUpdate { + // plog.Info("updating load balancer for impersonation proxy", + // "service", c.generatedLoadBalancerServiceName, + // "namespace", c.namespace) + // _, err = c.k8sClient.CoreV1().Services(c.namespace).Update(ctx, &loadBalancer, metav1.UpdateOptions{}) + // return err + // } + // return nil + //} + plog.Info("creating cluster ip for impersonation proxy", + "service", c.generatedClusterIPServiceName, + "namespace", c.namespace) + _, err := c.k8sClient.CoreV1().Services(c.namespace).Create(ctx, &clusterIP, metav1.CreateOptions{}) + return err +} + func (c *impersonatorConfigController) ensureTLSSecret(ctx context.Context, nameInfo *certNameInfo, ca *certauthority.CA) error { secretFromInformer, err := c.secretsInformer.Lister().Secrets(c.namespace).Get(c.tlsSecretName) notFound := k8serrors.IsNotFound(err) diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 9bd33ca74..87ac14954 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -49,6 +49,7 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { const installedInNamespace = "some-namespace" const credentialIssuerResourceName = "some-credential-issuer-resource-name" const generatedLoadBalancerServiceName = "some-service-resource-name" + const generatedClusterIPServiceName = "some-cluster-ip-resource-name" const tlsSecretName = "some-tls-secret-name" //nolint:gosec // this is not a credential const caSecretName = "some-ca-secret-name" const caSignerName = "some-ca-signer-name" @@ -81,6 +82,7 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) { observableWithInformerOption.WithInformer, observableWithInitialEventOption.WithInitialEvent, generatedLoadBalancerServiceName, + generatedClusterIPServiceName, tlsSecretName, caSecretName, nil, @@ -251,6 +253,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { const installedInNamespace = "some-namespace" const credentialIssuerResourceName = "some-credential-issuer-resource-name" const loadBalancerServiceName = "some-service-resource-name" + const clusterIPServiceName = "some-cluster-ip-resource-name" const tlsSecretName = "some-tls-secret-name" //nolint:gosec // this is not a credential const caSecretName = "some-ca-secret-name" const caSignerName = "some-ca-signer-name" @@ -525,6 +528,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { controllerlib.WithInformer, controllerlib.WithInitialEvent, loadBalancerServiceName, + clusterIPServiceName, tlsSecretName, caSecretName, labels, @@ -890,6 +894,17 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { return updatedLoadBalancerService } + var requireClusterIPWasCreated = func(action coretesting.Action) { + createAction, ok := action.(coretesting.CreateAction) + r.True(ok, "should have been able to cast this action to CreateAction: %v", action) + r.Equal("create", createAction.GetVerb()) + createdClusterIPService := createAction.GetObject().(*corev1.Service) + r.Equal(clusterIPServiceName, createdClusterIPService.Name) + r.Equal(corev1.ServiceTypeClusterIP, createdClusterIPService.Spec.Type) + r.Equal("app-name", createdClusterIPService.Spec.Selector["app"]) + r.Equal(labels, createdClusterIPService.Labels) + } + var requireTLSSecretWasDeleted = func(action coretesting.Action) { deleteAction, ok := action.(coretesting.DeleteAction) r.True(ok, "should have been able to cast this action to DeleteAction: %v", action) @@ -1569,6 +1584,33 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("the CredentialIssuer has a hostname specified and service type clusterip", func() { + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeClusterIP, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("starts the impersonator and creates a clusterip service", func() { + startInformersAndController() + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 3) + requireNodesListed(kubeAPIClient.Actions()[0]) + requireClusterIPWasCreated(kubeAPIClient.Actions()[1]) + requireCASecretWasCreated(kubeAPIClient.Actions()[2]) + // Check that the server is running without certs. + requireTLSServerIsRunningWithoutCerts() + requireCredentialIssuer(newPendingStrategy()) + requireSigningCertProviderIsEmpty() + }) + }) + when("the CredentialIssuer has a endpoint which is an IP address with a port", func() { const fakeIPWithPort = "127.0.0.1:3000" it.Before(func() { diff --git a/internal/controllermanager/prepare_controllers.go b/internal/controllermanager/prepare_controllers.go index 3aa75c393..5b27dd832 100644 --- a/internal/controllermanager/prepare_controllers.go +++ b/internal/controllermanager/prepare_controllers.go @@ -260,6 +260,7 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) { controllerlib.WithInformer, controllerlib.WithInitialEvent, c.NamesConfig.ImpersonationLoadBalancerService, + "impersonation-proxy-cluster-ip", // TODO wire this through from namesConfig c.NamesConfig.ImpersonationTLSCertificateSecret, c.NamesConfig.ImpersonationCACertificateSecret, c.Labels,