multiple cluster ips

This commit is contained in:
Margo Crawford 2021-05-25 17:01:42 -07:00
parent 75dd98a965
commit e2fad6932f
2 changed files with 68 additions and 22 deletions

View File

@ -193,7 +193,7 @@ type certNameInfo struct {
// The IP address or hostname which was selected to be used as the name in the cert. // The IP address or hostname which was selected to be used as the name in the cert.
// Either selectedIP or selectedHostname will be set, but not both. // Either selectedIP or selectedHostname will be set, but not both.
selectedIP net.IP selectedIPs []net.IP
selectedHostname string selectedHostname string
// The name of the endpoint to which a client should connect to talk to the impersonator. // The name of the endpoint to which a client should connect to talk to the impersonator.
@ -636,14 +636,14 @@ func (c *impersonatorConfigController) deleteTLSSecretWhenCertificateDoesNotMatc
actualIPs := actualCertFromSecret.IPAddresses actualIPs := actualCertFromSecret.IPAddresses
actualHostnames := actualCertFromSecret.DNSNames actualHostnames := actualCertFromSecret.DNSNames
plog.Info("Checking TLS certificate names", plog.Info("Checking TLS certificate names",
"desiredIP", nameInfo.selectedIP, "desiredIPs", nameInfo.selectedIPs,
"desiredHostname", nameInfo.selectedHostname, "desiredHostname", nameInfo.selectedHostname,
"actualIPs", actualIPs, "actualIPs", actualIPs,
"actualHostnames", actualHostnames, "actualHostnames", actualHostnames,
"secret", c.tlsSecretName, "secret", c.tlsSecretName,
"namespace", c.namespace) "namespace", c.namespace)
if certHostnameAndIPMatchDesiredState(nameInfo.selectedIP, actualIPs, nameInfo.selectedHostname, actualHostnames) { if certHostnameAndIPMatchDesiredState(nameInfo.selectedIPs, actualIPs, nameInfo.selectedHostname, actualHostnames) {
// The cert already matches the desired state, so there is no need to delete/recreate it. // The cert already matches the desired state, so there is no need to delete/recreate it.
return false, nil return false, nil
} }
@ -654,8 +654,13 @@ func (c *impersonatorConfigController) deleteTLSSecretWhenCertificateDoesNotMatc
return true, nil return true, nil
} }
func certHostnameAndIPMatchDesiredState(desiredIP net.IP, actualIPs []net.IP, desiredHostname string, actualHostnames []string) bool { func certHostnameAndIPMatchDesiredState(desiredIPs []net.IP, actualIPs []net.IP, desiredHostname string, actualHostnames []string) bool {
if desiredIP != nil && len(actualIPs) == 1 && desiredIP.Equal(actualIPs[0]) && len(actualHostnames) == 0 { if len(desiredIPs) > 0 && len(actualIPs) > 0 && len(actualIPs) == len(desiredIPs) && len(actualHostnames) == 0 {
for i := range desiredIPs {
if !actualIPs[i].Equal(desiredIPs[i]) {
return false
}
}
return true return true
} }
if desiredHostname != "" && len(actualHostnames) == 1 && desiredHostname == actualHostnames[0] && len(actualIPs) == 0 { if desiredHostname != "" && len(actualHostnames) == 1 && desiredHostname == actualHostnames[0] && len(actualIPs) == 0 {
@ -677,7 +682,7 @@ func (c *impersonatorConfigController) ensureTLSSecretIsCreatedAndLoaded(ctx con
return nil return nil
} }
newTLSSecret, err := c.createNewTLSSecret(ctx, ca, nameInfo.selectedIP, nameInfo.selectedHostname) newTLSSecret, err := c.createNewTLSSecret(ctx, ca, nameInfo.selectedIPs, nameInfo.selectedHostname)
if err != nil { if err != nil {
return err return err
} }
@ -746,12 +751,6 @@ func (c *impersonatorConfigController) createCASecret(ctx context.Context) (*cer
} }
func (c *impersonatorConfigController) findDesiredTLSCertificateName(config *v1alpha1.ImpersonationProxySpec) (*certNameInfo, error) { func (c *impersonatorConfigController) findDesiredTLSCertificateName(config *v1alpha1.ImpersonationProxySpec) (*certNameInfo, error) {
// possible valid options:
// - you have a loadbalancer and are autoconfiguring the endpoint -> get cert info based on load balancer ip/hostnome
// - 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?
// - clusterip and no external endpoint
if config.ExternalEndpoint != "" { if config.ExternalEndpoint != "" {
return c.findTLSCertificateNameFromEndpointConfig(config), nil return c.findTLSCertificateNameFromEndpointConfig(config), nil
} else if config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeClusterIP { } else if config.Service.Type == v1alpha1.ImpersonationProxyServiceTypeClusterIP {
@ -765,7 +764,7 @@ func (c *impersonatorConfigController) findTLSCertificateNameFromEndpointConfig(
endpointWithoutPort := strings.Split(endpointMaybeWithPort, ":")[0] endpointWithoutPort := strings.Split(endpointMaybeWithPort, ":")[0]
parsedAsIP := net.ParseIP(endpointWithoutPort) parsedAsIP := net.ParseIP(endpointWithoutPort)
if parsedAsIP != nil { if parsedAsIP != nil {
return &certNameInfo{ready: true, selectedIP: parsedAsIP, clientEndpoint: endpointMaybeWithPort} return &certNameInfo{ready: true, selectedIPs: []net.IP{parsedAsIP}, clientEndpoint: endpointMaybeWithPort}
} }
return &certNameInfo{ready: true, selectedHostname: endpointWithoutPort, clientEndpoint: endpointMaybeWithPort} return &certNameInfo{ready: true, selectedHostname: endpointWithoutPort, clientEndpoint: endpointMaybeWithPort}
} }
@ -797,7 +796,7 @@ func (c *impersonatorConfigController) findTLSCertificateNameFromLoadBalancer()
ip := ingress.IP ip := ingress.IP
parsedIP := net.ParseIP(ip) parsedIP := net.ParseIP(ip)
if parsedIP != nil { if parsedIP != nil {
return &certNameInfo{ready: true, selectedIP: parsedIP, clientEndpoint: ip}, nil return &certNameInfo{ready: true, selectedIPs: []net.IP{parsedIP}, clientEndpoint: ip}, nil
} }
} }
@ -815,22 +814,27 @@ func (c *impersonatorConfigController) findTLSCertificateNameFromClusterIPServic
return nil, err return nil, err
} }
ip := clusterIP.Spec.ClusterIP ip := clusterIP.Spec.ClusterIP
ips := clusterIP.Spec.ClusterIPs
if ip != "" { if ip != "" {
parsedIP := net.ParseIP(ip) // clusterIP will always exist when clusterIPs does, but not vice versa
return &certNameInfo{ready: true, selectedIP: parsedIP, clientEndpoint: ip}, nil var parsedIPs []net.IP
if len(ips) > 0 {
for _, ipFromIPs := range ips {
parsedIPs = append(parsedIPs, net.ParseIP(ipFromIPs))
}
} else {
parsedIPs = []net.IP{net.ParseIP(ip)}
}
return &certNameInfo{ready: true, selectedIPs: parsedIPs, clientEndpoint: ip}, nil
} }
return &certNameInfo{ready: false}, nil return &certNameInfo{ready: false}, nil
} }
func (c *impersonatorConfigController) createNewTLSSecret(ctx context.Context, ca *certauthority.CA, ip net.IP, hostname string) (*v1.Secret, error) { func (c *impersonatorConfigController) createNewTLSSecret(ctx context.Context, ca *certauthority.CA, ips []net.IP, hostname string) (*v1.Secret, error) {
var hostnames []string var hostnames []string
var ips []net.IP
if hostname != "" { if hostname != "" {
hostnames = []string{hostname} hostnames = []string{hostname}
} }
if ip != nil {
ips = []net.IP{ip}
}
impersonationCert, err := ca.IssueServerCert(hostnames, ips, approximatelyOneHundredYears) impersonationCert, err := ca.IssueServerCert(hostnames, ips, approximatelyOneHundredYears)
if err != nil { if err != nil {

View File

@ -427,7 +427,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
t.Logf("DialContext replacing addr %s with %s", addr, replacementAddr) t.Logf("DialContext replacing addr %s with %s", addr, replacementAddr)
addr = replacementAddr addr = replacementAddr
} else if dnsOverrides != nil { } else if dnsOverrides != nil {
t.Fatal("dnsOverrides was provided but not used, which was probably a mistake") t.Fatalf("dnsOverrides was provided but not used, which was probably a mistake. addr %s", addr)
} }
return realDialer.DialContext(ctx, network, addr) return realDialer.DialContext(ctx, network, addr)
} }
@ -747,6 +747,15 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.NoError(client.Tracker().Add(clusterIPService)) r.NoError(client.Tracker().Add(clusterIPService))
} }
var addDualStackClusterIPServiceToTracker = func(resourceName string, clusterIP0 string, clusterIP1 string, client *kubernetesfake.Clientset) {
clusterIPService := newClusterIPService(resourceName, corev1.ServiceStatus{}, corev1.ServiceSpec{
Type: corev1.ServiceTypeClusterIP,
ClusterIP: clusterIP0,
ClusterIPs: []string{clusterIP0, clusterIP1},
})
r.NoError(client.Tracker().Add(clusterIPService))
}
var addSecretToTrackers = func(secret *corev1.Secret, clients ...*kubernetesfake.Clientset) { var addSecretToTrackers = func(secret *corev1.Secret, clients ...*kubernetesfake.Clientset) {
for _, client := range clients { for _, client := range clients {
secretCopy := secret.DeepCopy() secretCopy := secret.DeepCopy()
@ -1541,6 +1550,39 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
}) })
}) })
when("a clusterip service exists with dual stack ips", func() {
const fakeIP1 = "127.0.0.123"
const fakeIP2 = "127.0.0.234" // TODO test that this works for an IPv6 address, which is the actual use case for multiple clusterips
it.Before(func() {
addCredentialIssuerToTrackers(v1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerResourceName},
Spec: v1alpha1.CredentialIssuerSpec{
ImpersonationProxy: &v1alpha1.ImpersonationProxySpec{
Mode: v1alpha1.ImpersonationProxyModeEnabled,
Service: v1alpha1.ImpersonationProxyServiceSpec{
Type: v1alpha1.ImpersonationProxyServiceTypeClusterIP,
},
},
},
}, pinnipedInformerClient, pinnipedAPIClient)
addNodeWithRoleToTracker("worker", kubeAPIClient)
addDualStackClusterIPServiceToTracker(clusterIPServiceName, fakeIP1, fakeIP2, kubeInformerClient)
addDualStackClusterIPServiceToTracker(clusterIPServiceName, fakeIP1, fakeIP2, kubeAPIClient)
})
it("certs are valid for both ip addresses", func() {
startInformersAndController()
r.NoError(runControllerSync())
r.Len(kubeAPIClient.Actions(), 3)
requireNodesListed(kubeAPIClient.Actions()[0])
ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1])
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca)
requireTLSServerIsRunning(ca, fakeIP2, map[string]string{fakeIP2 + ":443": testServerAddr()})
requireTLSServerIsRunning(ca, fakeIP1, map[string]string{fakeIP1 + ":443": testServerAddr()})
requireCredentialIssuer(newSuccessStrategy(fakeIP1, ca))
})
})
when("a load balancer and a secret already exists", func() { when("a load balancer and a secret already exists", func() {
var caCrt []byte var caCrt []byte
it.Before(func() { it.Before(func() {