diff --git a/internal/controller/impersonatorconfig/impersonator_config.go b/internal/controller/impersonatorconfig/impersonator_config.go index 41337f90a..1d29fdd1e 100644 --- a/internal/controller/impersonatorconfig/impersonator_config.go +++ b/internal/controller/impersonatorconfig/impersonator_config.go @@ -656,8 +656,7 @@ func (c *impersonatorConfigController) findDesiredTLSCertificateName(config *v1a // - you have a loadbalancer AND an external endpoint -> either should work since they should be the same // - external endpoint no loadbalancer or other service -> use the endpoint config // - external endpoint and ClusterIP -> use external endpoint? - // - // - is it legal to have a clusterip and no external endpoint??? + // - clusterip and no external endpoint if config.ExternalEndpoint != "" { return c.findTLSCertificateNameFromEndpointConfig(config), nil } else if config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeClusterIP { @@ -874,7 +873,11 @@ func (c *impersonatorConfigController) doSyncResult(nameInfo *certNameInfo, conf func validateCredentialIssuerSpec(credIssuer *v1alpha1.CredentialIssuer) error { // TODO check external endpoint for valid ip or hostname - // TODO if service type is none and externalendpoint is "" return error + impersonationProxySpec := credIssuer.Spec.ImpersonationProxy + if impersonationProxySpec.Mode != v1alpha1.ImpersonationProxyModeDisabled && + impersonationProxySpec.ExternalEndpoint == "" && impersonationProxySpec.Service.Type == v1alpha1.ImpersonationProxyServiceTypeNone { + return fmt.Errorf("invalid impersonation proxy configuration: must specify an external endpoint or set a service type") + } switch mode := credIssuer.Spec.ImpersonationProxy.Mode; mode { case v1alpha1.ImpersonationProxyModeAuto: case v1alpha1.ImpersonationProxyModeDisabled: diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index ebe45038b..08acd916f 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -1498,6 +1498,36 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("the CredentialIssuer has a hostname specified and service type loadbalancer", func() { + const fakeHostname = "fake.example.com" + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostname, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("starts the impersonator, generates a valid cert for the specified hostname, starts a loadbalancer", func() { + startInformersAndController() + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 4) + requireNodesListed(kubeAPIClient.Actions()[0]) + requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[2]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[3], ca) + // Check that the server is running and that TLS certs that are being served are are for fakeHostname. + requireTLSServerIsRunning(ca, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeHostname, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + }) + 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() { @@ -1556,6 +1586,36 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) + when("the CredentialIssuer has a endpoint which is a hostname with a port, service type loadbalancer", func() { + const fakeHostnameWithPort = "fake.example.com:3000" + it.Before(func() { + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: fakeHostnameWithPort, + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeLoadBalancer, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("worker", kubeAPIClient) + }) + + it("starts the impersonator, starts the loadbalancer, generates a valid cert for the specified hostname", func() { + startInformersAndController() + r.NoError(runControllerSync()) + r.Len(kubeAPIClient.Actions(), 4) + requireNodesListed(kubeAPIClient.Actions()[0]) + requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1]) + ca := requireCASecretWasCreated(kubeAPIClient.Actions()[2]) + requireTLSSecretWasCreated(kubeAPIClient.Actions()[3], ca) + // Check that the server is running and that TLS certs that are being served are are for fakeHostnameWithPort. + requireTLSServerIsRunning(ca, fakeHostnameWithPort, map[string]string{fakeHostnameWithPort: testServerAddr()}) + requireCredentialIssuer(newSuccessStrategy(fakeHostnameWithPort, ca)) + requireSigningCertProviderHasLoadedCerts(signingCACertPEM, signingCAKeyPEM) + }) + }) + when("switching the CredentialIssuer from ip address endpoint to hostname endpoint and back to ip address", func() { const fakeHostname = "fake.example.com" const fakeIP = "127.0.0.42" @@ -2711,6 +2771,28 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { }) }) }) + + when("the impersonator is enabled but the service type is none and the external endpoint is empty", func() { + it.Before(func() { + addSecretToTrackers(signingCASecret, kubeInformerClient) + addCredentialIssuerToTracker(credentialIssuerResourceName, v1alpha1.CredentialIssuerSpec{ + ImpersonationProxy: v1alpha1.ImpersonationProxySpec{ + Mode: v1alpha1.ImpersonationProxyModeEnabled, + ExternalEndpoint: "", + Service: v1alpha1.ImpersonationProxyServiceSpec{ + Type: v1alpha1.ImpersonationProxyServiceTypeNone, + }, + }, + }, pinnipedInformerClient) + addNodeWithRoleToTracker("control-plane", kubeAPIClient) + }) + + it("returns a validation error", func() { + startInformersAndController() + r.EqualError(runControllerSync(), "invalid impersonator configuration: invalid impersonation proxy configuration: must specify an external endpoint or set a service type") + r.Len(kubeAPIClient.Actions(), 0) + }) + }) }, spec.Report(report.Terminal{})) }