WIP managing TLS secrets from the impersonation config controller
Signed-off-by: Margo Crawford <margaretc@vmware.com>
This commit is contained in:
parent
4dbde4cf7f
commit
d42c533fbb
@ -37,5 +37,5 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
fmt.Println(base64.RawURLEncoding.EncodeToString(reqJSON))
|
fmt.Println(base64.StdEncoding.EncodeToString(reqJSON))
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
@ -38,13 +39,17 @@ type impersonatorConfigController struct {
|
|||||||
k8sClient kubernetes.Interface
|
k8sClient kubernetes.Interface
|
||||||
configMapsInformer corev1informers.ConfigMapInformer
|
configMapsInformer corev1informers.ConfigMapInformer
|
||||||
servicesInformer corev1informers.ServiceInformer
|
servicesInformer corev1informers.ServiceInformer
|
||||||
|
secretsInformer corev1informers.SecretInformer
|
||||||
generatedLoadBalancerServiceName string
|
generatedLoadBalancerServiceName string
|
||||||
|
tlsSecretName string
|
||||||
labels map[string]string
|
labels map[string]string
|
||||||
startTLSListenerFunc StartTLSListenerFunc
|
startTLSListenerFunc StartTLSListenerFunc
|
||||||
httpHandlerFactory func() (http.Handler, error)
|
httpHandlerFactory func() (http.Handler, error)
|
||||||
|
|
||||||
server *http.Server
|
server *http.Server
|
||||||
hasControlPlaneNodes *bool
|
hasControlPlaneNodes *bool
|
||||||
|
tlsCert *tls.Certificate
|
||||||
|
tlsCertMutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type StartTLSListenerFunc func(network, listenAddress string, config *tls.Config) (net.Listener, error)
|
type StartTLSListenerFunc func(network, listenAddress string, config *tls.Config) (net.Listener, error)
|
||||||
@ -55,9 +60,11 @@ func NewImpersonatorConfigController(
|
|||||||
k8sClient kubernetes.Interface,
|
k8sClient kubernetes.Interface,
|
||||||
configMapsInformer corev1informers.ConfigMapInformer,
|
configMapsInformer corev1informers.ConfigMapInformer,
|
||||||
servicesInformer corev1informers.ServiceInformer,
|
servicesInformer corev1informers.ServiceInformer,
|
||||||
|
secretsInformer corev1informers.SecretInformer,
|
||||||
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
||||||
withInitialEvent pinnipedcontroller.WithInitialEventOptionFunc,
|
withInitialEvent pinnipedcontroller.WithInitialEventOptionFunc,
|
||||||
generatedLoadBalancerServiceName string,
|
generatedLoadBalancerServiceName string,
|
||||||
|
tlsSecretName string,
|
||||||
labels map[string]string,
|
labels map[string]string,
|
||||||
startTLSListenerFunc StartTLSListenerFunc,
|
startTLSListenerFunc StartTLSListenerFunc,
|
||||||
httpHandlerFactory func() (http.Handler, error),
|
httpHandlerFactory func() (http.Handler, error),
|
||||||
@ -71,7 +78,9 @@ func NewImpersonatorConfigController(
|
|||||||
k8sClient: k8sClient,
|
k8sClient: k8sClient,
|
||||||
configMapsInformer: configMapsInformer,
|
configMapsInformer: configMapsInformer,
|
||||||
servicesInformer: servicesInformer,
|
servicesInformer: servicesInformer,
|
||||||
|
secretsInformer: secretsInformer,
|
||||||
generatedLoadBalancerServiceName: generatedLoadBalancerServiceName,
|
generatedLoadBalancerServiceName: generatedLoadBalancerServiceName,
|
||||||
|
tlsSecretName: tlsSecretName,
|
||||||
labels: labels,
|
labels: labels,
|
||||||
startTLSListenerFunc: startTLSListenerFunc,
|
startTLSListenerFunc: startTLSListenerFunc,
|
||||||
httpHandlerFactory: httpHandlerFactory,
|
httpHandlerFactory: httpHandlerFactory,
|
||||||
@ -87,6 +96,11 @@ func NewImpersonatorConfigController(
|
|||||||
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(generatedLoadBalancerServiceName, namespace),
|
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(generatedLoadBalancerServiceName, namespace),
|
||||||
controllerlib.InformerOption{},
|
controllerlib.InformerOption{},
|
||||||
),
|
),
|
||||||
|
withInformer(
|
||||||
|
secretsInformer,
|
||||||
|
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(tlsSecretName, namespace),
|
||||||
|
controllerlib.InformerOption{},
|
||||||
|
),
|
||||||
// Be sure to run once even if the ConfigMap that the informer is watching doesn't exist.
|
// Be sure to run once even if the ConfigMap that the informer is watching doesn't exist.
|
||||||
withInitialEvent(controllerlib.Key{
|
withInitialEvent(controllerlib.Key{
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
@ -155,21 +169,31 @@ func (c *impersonatorConfigController) Sync(ctx controllerlib.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.shouldHaveTLSSecret(config) {
|
||||||
|
if err = c.ensureTLSSecretIsCreated(ctx.Context, config); err != nil {
|
||||||
|
// return err // TODO
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = c.ensureTLSSecretIsRemoved(ctx.Context); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
plog.Debug("Successfully finished impersonatorConfigController Sync")
|
plog.Debug("Successfully finished impersonatorConfigController Sync")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *impersonatorConfigController) shouldHaveTLSSecret(config *impersonator.Config) bool {
|
||||||
|
return c.shouldHaveImpersonator(config)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *impersonatorConfigController) shouldHaveImpersonator(config *impersonator.Config) bool {
|
func (c *impersonatorConfigController) shouldHaveImpersonator(config *impersonator.Config) bool {
|
||||||
return (config.Mode == impersonator.ModeAuto && !*c.hasControlPlaneNodes) || config.Mode == impersonator.ModeEnabled
|
return (config.Mode == impersonator.ModeAuto && !*c.hasControlPlaneNodes) || config.Mode == impersonator.ModeEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *impersonatorConfigController) shouldHaveLoadBalancer(config *impersonator.Config) bool {
|
func (c *impersonatorConfigController) shouldHaveLoadBalancer(config *impersonator.Config) bool {
|
||||||
// start the load balancer only if:
|
return c.shouldHaveImpersonator(config) && config.Endpoint == ""
|
||||||
// - the impersonator is running
|
|
||||||
// - the cluster is cloud hosted
|
|
||||||
// - there is no endpoint specified in the config
|
|
||||||
return c.server != nil && !*c.hasControlPlaneNodes && config.Endpoint == ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *impersonatorConfigController) ensureImpersonatorIsStopped() error {
|
func (c *impersonatorConfigController) ensureImpersonatorIsStopped() error {
|
||||||
@ -189,15 +213,6 @@ func (c *impersonatorConfigController) ensureImpersonatorIsStarted() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
impersonationCA, err := certauthority.New(pkix.Name{CommonName: "test CA"}, 24*time.Hour)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create impersonation CA: %w", err)
|
|
||||||
}
|
|
||||||
impersonationCert, err := impersonationCA.Issue(pkix.Name{}, []string{"impersonation-proxy"}, nil, 24*time.Hour)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create impersonation cert: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
handler, err := c.httpHandlerFactory()
|
handler, err := c.httpHandlerFactory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -206,7 +221,7 @@ func (c *impersonatorConfigController) ensureImpersonatorIsStarted() error {
|
|||||||
listener, err := c.startTLSListenerFunc("tcp", impersonationProxyPort, &tls.Config{
|
listener, err := c.startTLSListenerFunc("tcp", impersonationProxyPort, &tls.Config{
|
||||||
MinVersion: tls.VersionTLS12, // Allow v1.2 because clients like the default `curl` on MacOS don't support 1.3 yet.
|
MinVersion: tls.VersionTLS12, // Allow v1.2 because clients like the default `curl` on MacOS don't support 1.3 yet.
|
||||||
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
return impersonationCert, nil
|
return c.getTLSCert(), nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -239,6 +254,18 @@ func (c *impersonatorConfigController) isLoadBalancerRunning() (bool, error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *impersonatorConfigController) tlsSecretExists() (bool, *v1.Secret, error) {
|
||||||
|
secret, err := c.secretsInformer.Lister().Secrets(c.namespace).Get(c.tlsSecretName)
|
||||||
|
notFound := k8serrors.IsNotFound(err)
|
||||||
|
if notFound {
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
return true, secret, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *impersonatorConfigController) ensureLoadBalancerIsStopped(ctx context.Context) error {
|
func (c *impersonatorConfigController) ensureLoadBalancerIsStopped(ctx context.Context) error {
|
||||||
running, err := c.isLoadBalancerRunning()
|
running, err := c.isLoadBalancerRunning()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -295,3 +322,99 @@ func (c *impersonatorConfigController) ensureLoadBalancerIsStarted(ctx context.C
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *impersonatorConfigController) ensureTLSSecretIsCreated(ctx context.Context, config *impersonator.Config) error {
|
||||||
|
tlsSecretExists, tlsSecret, err := c.tlsSecretExists()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if tlsSecretExists {
|
||||||
|
certPEM := tlsSecret.Data[v1.TLSCertKey]
|
||||||
|
keyPEM := tlsSecret.Data[v1.TLSPrivateKeyKey]
|
||||||
|
tlsCert, _ := tls.X509KeyPair(certPEM, keyPEM)
|
||||||
|
// TODO handle err, like when the Secret did not contain the fields that we expected
|
||||||
|
c.setTLSCert(&tlsCert)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
impersonationCA, err := certauthority.New(pkix.Name{CommonName: "test CA"}, 24*time.Hour) // TODO change the length of this
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create impersonation CA: %w", err)
|
||||||
|
}
|
||||||
|
var ips []net.IP
|
||||||
|
if config.Endpoint == "" { // TODO are there other cases where we need to do this?
|
||||||
|
lb, err := c.servicesInformer.Lister().Services(c.namespace).Get(c.generatedLoadBalancerServiceName)
|
||||||
|
notFound := k8serrors.IsNotFound(err)
|
||||||
|
if notFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ingresses := lb.Status.LoadBalancer.Ingress
|
||||||
|
if len(ingresses) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ip := ingresses[0].IP // TODO multiple ips
|
||||||
|
ips = []net.IP{net.ParseIP(ip)}
|
||||||
|
// check with informer to get the ip address of the load balancer if its available
|
||||||
|
// if not, return
|
||||||
|
} else {
|
||||||
|
ips = []net.IP{net.ParseIP(config.Endpoint)}
|
||||||
|
}
|
||||||
|
impersonationCert, err := impersonationCA.Issue(pkix.Name{}, nil, ips, 24*time.Hour) // TODO change the length of this
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create impersonation cert: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.setTLSCert(impersonationCert)
|
||||||
|
|
||||||
|
certPEM, keyPEM, err := certauthority.ToPEM(impersonationCert)
|
||||||
|
// TODO error handling?
|
||||||
|
|
||||||
|
// TODO handle error on create
|
||||||
|
c.k8sClient.CoreV1().Secrets(c.namespace).Create(ctx, &v1.Secret{
|
||||||
|
Type: v1.SecretTypeTLS,
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: c.tlsSecretName,
|
||||||
|
Namespace: c.namespace,
|
||||||
|
Labels: c.labels,
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"ca.crt": impersonationCA.Bundle(),
|
||||||
|
v1.TLSPrivateKeyKey: keyPEM,
|
||||||
|
v1.TLSCertKey: certPEM,
|
||||||
|
},
|
||||||
|
}, metav1.CreateOptions{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *impersonatorConfigController) ensureTLSSecretIsRemoved(ctx context.Context) error {
|
||||||
|
tlsSecretExists, _, err := c.tlsSecretExists()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !tlsSecretExists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
plog.Info("Deleting TLS certificates for impersonation proxy",
|
||||||
|
"service", c.tlsSecretName,
|
||||||
|
"namespace", c.namespace)
|
||||||
|
err = c.k8sClient.CoreV1().Secrets(c.namespace).Delete(ctx, c.tlsSecretName, metav1.DeleteOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *impersonatorConfigController) setTLSCert(cert *tls.Certificate) {
|
||||||
|
c.tlsCertMutex.Lock()
|
||||||
|
defer c.tlsCertMutex.Unlock()
|
||||||
|
c.tlsCert = cert
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *impersonatorConfigController) getTLSCert() *tls.Certificate {
|
||||||
|
c.tlsCertMutex.RLock()
|
||||||
|
defer c.tlsCertMutex.RUnlock()
|
||||||
|
return c.tlsCert
|
||||||
|
}
|
||||||
|
@ -6,6 +6,8 @@ package impersonatorconfig
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -29,6 +31,7 @@ import (
|
|||||||
coretesting "k8s.io/client-go/testing"
|
coretesting "k8s.io/client-go/testing"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/certauthority"
|
||||||
"go.pinniped.dev/internal/controllerlib"
|
"go.pinniped.dev/internal/controllerlib"
|
||||||
"go.pinniped.dev/internal/here"
|
"go.pinniped.dev/internal/here"
|
||||||
"go.pinniped.dev/internal/testutil"
|
"go.pinniped.dev/internal/testutil"
|
||||||
@ -61,12 +64,14 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) {
|
|||||||
const installedInNamespace = "some-namespace"
|
const installedInNamespace = "some-namespace"
|
||||||
const configMapResourceName = "some-configmap-resource-name"
|
const configMapResourceName = "some-configmap-resource-name"
|
||||||
const generatedLoadBalancerServiceName = "some-service-resource-name"
|
const generatedLoadBalancerServiceName = "some-service-resource-name"
|
||||||
|
const tlsSecretName = "some-secret-name"
|
||||||
|
|
||||||
var r *require.Assertions
|
var r *require.Assertions
|
||||||
var observableWithInformerOption *testutil.ObservableWithInformerOption
|
var observableWithInformerOption *testutil.ObservableWithInformerOption
|
||||||
var observableWithInitialEventOption *testutil.ObservableWithInitialEventOption
|
var observableWithInitialEventOption *testutil.ObservableWithInitialEventOption
|
||||||
var configMapsInformerFilter controllerlib.Filter
|
var configMapsInformerFilter controllerlib.Filter
|
||||||
var servicesInformerFilter controllerlib.Filter
|
var servicesInformerFilter controllerlib.Filter
|
||||||
|
var secretsInformerFilter controllerlib.Filter
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
r = require.New(t)
|
r = require.New(t)
|
||||||
@ -75,6 +80,7 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) {
|
|||||||
sharedInformerFactory := kubeinformers.NewSharedInformerFactory(nil, 0)
|
sharedInformerFactory := kubeinformers.NewSharedInformerFactory(nil, 0)
|
||||||
configMapsInformer := sharedInformerFactory.Core().V1().ConfigMaps()
|
configMapsInformer := sharedInformerFactory.Core().V1().ConfigMaps()
|
||||||
servicesInformer := sharedInformerFactory.Core().V1().Services()
|
servicesInformer := sharedInformerFactory.Core().V1().Services()
|
||||||
|
secretsInformer := sharedInformerFactory.Core().V1().Secrets()
|
||||||
|
|
||||||
_ = NewImpersonatorConfigController(
|
_ = NewImpersonatorConfigController(
|
||||||
installedInNamespace,
|
installedInNamespace,
|
||||||
@ -82,15 +88,18 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
configMapsInformer,
|
configMapsInformer,
|
||||||
servicesInformer,
|
servicesInformer,
|
||||||
|
secretsInformer,
|
||||||
observableWithInformerOption.WithInformer,
|
observableWithInformerOption.WithInformer,
|
||||||
observableWithInitialEventOption.WithInitialEvent,
|
observableWithInitialEventOption.WithInitialEvent,
|
||||||
generatedLoadBalancerServiceName,
|
generatedLoadBalancerServiceName,
|
||||||
|
tlsSecretName,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
configMapsInformerFilter = observableWithInformerOption.GetFilterForInformer(configMapsInformer)
|
configMapsInformerFilter = observableWithInformerOption.GetFilterForInformer(configMapsInformer)
|
||||||
servicesInformerFilter = observableWithInformerOption.GetFilterForInformer(servicesInformer)
|
servicesInformerFilter = observableWithInformerOption.GetFilterForInformer(servicesInformer)
|
||||||
|
secretsInformerFilter = observableWithInformerOption.GetFilterForInformer(secretsInformer)
|
||||||
})
|
})
|
||||||
|
|
||||||
when("watching ConfigMap objects", func() {
|
when("watching ConfigMap objects", func() {
|
||||||
@ -189,6 +198,54 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
when("watching Secret objects", func() {
|
||||||
|
var subject controllerlib.Filter
|
||||||
|
var target, wrongNamespace, wrongName, unrelated *corev1.Secret
|
||||||
|
|
||||||
|
it.Before(func() {
|
||||||
|
subject = secretsInformerFilter
|
||||||
|
target = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: tlsSecretName, Namespace: installedInNamespace}}
|
||||||
|
wrongNamespace = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: tlsSecretName, Namespace: "wrong-namespace"}}
|
||||||
|
wrongName = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: installedInNamespace}}
|
||||||
|
unrelated = &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: "wrong-namespace"}}
|
||||||
|
})
|
||||||
|
|
||||||
|
when("the target Secret changes", func() {
|
||||||
|
it("returns true to trigger the sync method", func() {
|
||||||
|
r.True(subject.Add(target))
|
||||||
|
r.True(subject.Update(target, unrelated))
|
||||||
|
r.True(subject.Update(unrelated, target))
|
||||||
|
r.True(subject.Delete(target))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("a Secret from another namespace changes", func() {
|
||||||
|
it("returns false to avoid triggering the sync method", func() {
|
||||||
|
r.False(subject.Add(wrongNamespace))
|
||||||
|
r.False(subject.Update(wrongNamespace, unrelated))
|
||||||
|
r.False(subject.Update(unrelated, wrongNamespace))
|
||||||
|
r.False(subject.Delete(wrongNamespace))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("a Secret with a different name changes", func() {
|
||||||
|
it("returns false to avoid triggering the sync method", func() {
|
||||||
|
r.False(subject.Add(wrongName))
|
||||||
|
r.False(subject.Update(wrongName, unrelated))
|
||||||
|
r.False(subject.Update(unrelated, wrongName))
|
||||||
|
r.False(subject.Delete(wrongName))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("a Secret with a different name and a different namespace changes", func() {
|
||||||
|
it("returns false to avoid triggering the sync method", func() {
|
||||||
|
r.False(subject.Add(unrelated))
|
||||||
|
r.False(subject.Update(unrelated, unrelated))
|
||||||
|
r.False(subject.Delete(unrelated))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
when("starting up", func() {
|
when("starting up", func() {
|
||||||
it("asks for an initial event because the ConfigMap may not exist yet and it needs to run anyway", func() {
|
it("asks for an initial event because the ConfigMap may not exist yet and it needs to run anyway", func() {
|
||||||
r.Equal(&controllerlib.Key{
|
r.Equal(&controllerlib.Key{
|
||||||
@ -205,6 +262,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
const installedInNamespace = "some-namespace"
|
const installedInNamespace = "some-namespace"
|
||||||
const configMapResourceName = "some-configmap-resource-name"
|
const configMapResourceName = "some-configmap-resource-name"
|
||||||
const generatedLoadBalancerServiceName = "some-service-resource-name"
|
const generatedLoadBalancerServiceName = "some-service-resource-name"
|
||||||
|
const tlsSecretName = "some-secret-name"
|
||||||
var labels = map[string]string{"app": "app-name", "other-key": "other-value"}
|
var labels = map[string]string{"app": "app-name", "other-key": "other-value"}
|
||||||
|
|
||||||
var r *require.Assertions
|
var r *require.Assertions
|
||||||
@ -232,7 +290,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
//nolint: gosec // Intentionally binding to all network interfaces.
|
//nolint: gosec // Intentionally binding to all network interfaces.
|
||||||
startedTLSListener, err = tls.Listen(network, ":0", config) // automatically choose the port for unit tests
|
startedTLSListener, err = tls.Listen(network, "127.0.0.1:0", config) // automatically choose the port for unit tests
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
return &tlsListenerWrapper{listener: startedTLSListener, closeError: startTLSListenerUponCloseError}, nil
|
return &tlsListenerWrapper{listener: startedTLSListener, closeError: startTLSListenerUponCloseError}, nil
|
||||||
}
|
}
|
||||||
@ -248,11 +306,21 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var requireTLSServerIsRunning = func() {
|
var requireTLSServerIsRunning = func(caCrt []byte) {
|
||||||
r.Greater(startTLSListenerFuncWasCalled, 0)
|
r.Greater(startTLSListenerFuncWasCalled, 0)
|
||||||
|
|
||||||
tr := &http.Transport{
|
var tr *http.Transport
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // TODO once we're using certs, do not skip verify
|
if caCrt == nil {
|
||||||
|
tr = &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rootCAs := x509.NewCertPool()
|
||||||
|
rootCAs.AppendCertsFromPEM(caCrt)
|
||||||
|
|
||||||
|
tr = &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{RootCAs: rootCAs},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
client := &http.Client{Transport: tr}
|
client := &http.Client{Transport: tr}
|
||||||
url := "https://" + startedTLSListener.Addr().String()
|
url := "https://" + startedTLSListener.Addr().String()
|
||||||
@ -268,6 +336,22 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
r.Equal("hello world", string(body))
|
r.Equal("hello world", string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var requireTLSServerIsRunningWithoutCerts = func() {
|
||||||
|
r.Greater(startTLSListenerFuncWasCalled, 0)
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
url := "https://" + startedTLSListener.Addr().String()
|
||||||
|
req, err := http.NewRequestWithContext(context.Background(), "GET", url, nil)
|
||||||
|
r.NoError(err)
|
||||||
|
_, err = client.Do(req)
|
||||||
|
r.Error(err)
|
||||||
|
r.Regexp("Get .*: remote error: tls: unrecognized name", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
var requireTLSServerIsNoLongerRunning = func() {
|
var requireTLSServerIsNoLongerRunning = func() {
|
||||||
r.Greater(startTLSListenerFuncWasCalled, 0)
|
r.Greater(startTLSListenerFuncWasCalled, 0)
|
||||||
_, err := tls.Dial(
|
_, err := tls.Dial(
|
||||||
@ -276,7 +360,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
&tls.Config{InsecureSkipVerify: true}, //nolint:gosec // TODO once we're using certs, do not skip verify
|
&tls.Config{InsecureSkipVerify: true}, //nolint:gosec // TODO once we're using certs, do not skip verify
|
||||||
)
|
)
|
||||||
r.Error(err)
|
r.Error(err)
|
||||||
r.Regexp(`dial tcp \[::\]:[0-9]+: connect: connection refused`, err.Error())
|
r.Regexp(`dial tcp .*: connect: connection refused`, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
var requireTLSServerWasNeverStarted = func() {
|
var requireTLSServerWasNeverStarted = func() {
|
||||||
@ -306,9 +390,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
kubeAPIClient,
|
kubeAPIClient,
|
||||||
kubeInformers.Core().V1().ConfigMaps(),
|
kubeInformers.Core().V1().ConfigMaps(),
|
||||||
kubeInformers.Core().V1().Services(),
|
kubeInformers.Core().V1().Services(),
|
||||||
|
kubeInformers.Core().V1().Secrets(),
|
||||||
controllerlib.WithInformer,
|
controllerlib.WithInformer,
|
||||||
controllerlib.WithInitialEvent,
|
controllerlib.WithInitialEvent,
|
||||||
generatedLoadBalancerServiceName,
|
generatedLoadBalancerServiceName,
|
||||||
|
tlsSecretName,
|
||||||
labels,
|
labels,
|
||||||
startTLSListenerFunc,
|
startTLSListenerFunc,
|
||||||
func() (http.Handler, error) {
|
func() (http.Handler, error) {
|
||||||
@ -371,6 +457,44 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var createStubTLSSecret = func(resourceName string) *corev1.Secret {
|
||||||
|
return &corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: resourceName,
|
||||||
|
Namespace: installedInNamespace,
|
||||||
|
// Note that this seems to be ignored by the informer during initial creation, so actually
|
||||||
|
// the informer will see this as resource version "". Leaving it here to express the intent
|
||||||
|
// that the initial version is version 0.
|
||||||
|
ResourceVersion: "0",
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var createActualTLSSecret = func(resourceName string) *corev1.Secret {
|
||||||
|
impersonationCA, err := certauthority.New(pkix.Name{CommonName: "test CA"}, 24*time.Hour)
|
||||||
|
r.NoError(err)
|
||||||
|
impersonationCert, err := impersonationCA.Issue(pkix.Name{}, nil, []net.IP{net.ParseIP("127.0.0.1")}, 24*time.Hour)
|
||||||
|
r.NoError(err)
|
||||||
|
certPEM, keyPEM, err := certauthority.ToPEM(impersonationCert)
|
||||||
|
|
||||||
|
return &corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: resourceName,
|
||||||
|
Namespace: installedInNamespace,
|
||||||
|
// Note that this seems to be ignored by the informer during initial creation, so actually
|
||||||
|
// the informer will see this as resource version "". Leaving it here to express the intent
|
||||||
|
// that the initial version is version 0.
|
||||||
|
ResourceVersion: "0",
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"ca.crt": impersonationCA.Bundle(),
|
||||||
|
corev1.TLSPrivateKeyKey: keyPEM,
|
||||||
|
corev1.TLSCertKey: certPEM,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var addLoadBalancerServiceToTracker = func(resourceName string, client *kubernetesfake.Clientset) {
|
var addLoadBalancerServiceToTracker = func(resourceName string, client *kubernetesfake.Clientset) {
|
||||||
loadBalancerService := &corev1.Service{
|
loadBalancerService := &corev1.Service{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
@ -388,6 +512,30 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
r.NoError(client.Tracker().Add(loadBalancerService))
|
r.NoError(client.Tracker().Add(loadBalancerService))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var addLoadBalancerServiceWithIPToTracker = func(resourceName string, client *kubernetesfake.Clientset) {
|
||||||
|
loadBalancerService := &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: resourceName,
|
||||||
|
Namespace: installedInNamespace,
|
||||||
|
// Note that this seems to be ignored by the informer during initial creation, so actually
|
||||||
|
// the informer will see this as resource version "". Leaving it here to express the intent
|
||||||
|
// that the initial version is version 0.
|
||||||
|
ResourceVersion: "0",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Type: corev1.ServiceTypeLoadBalancer,
|
||||||
|
},
|
||||||
|
Status: corev1.ServiceStatus{
|
||||||
|
LoadBalancer: corev1.LoadBalancerStatus{
|
||||||
|
Ingress: []corev1.LoadBalancerIngress{
|
||||||
|
{IP: "127.0.0.1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r.NoError(client.Tracker().Add(loadBalancerService))
|
||||||
|
}
|
||||||
|
|
||||||
var deleteLoadBalancerServiceFromTracker = func(resourceName string, client *kubernetesfake.Clientset) {
|
var deleteLoadBalancerServiceFromTracker = func(resourceName string, client *kubernetesfake.Clientset) {
|
||||||
r.NoError(client.Tracker().Delete(
|
r.NoError(client.Tracker().Delete(
|
||||||
schema.GroupVersionResource{Version: "v1", Resource: "services"},
|
schema.GroupVersionResource{Version: "v1", Resource: "services"},
|
||||||
@ -419,9 +567,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var requireLoadBalancerWasCreated = func(action coretesting.Action) {
|
var requireLoadBalancerWasCreated = func(action coretesting.Action) {
|
||||||
createLoadBalancerAction := action.(coretesting.CreateAction)
|
createAction := action.(coretesting.CreateAction)
|
||||||
r.Equal("create", createLoadBalancerAction.GetVerb())
|
r.Equal("create", createAction.GetVerb())
|
||||||
createdLoadBalancerService := createLoadBalancerAction.GetObject().(*corev1.Service)
|
createdLoadBalancerService := createAction.GetObject().(*corev1.Service)
|
||||||
r.Equal(generatedLoadBalancerServiceName, createdLoadBalancerService.Name)
|
r.Equal(generatedLoadBalancerServiceName, createdLoadBalancerService.Name)
|
||||||
r.Equal(installedInNamespace, createdLoadBalancerService.Namespace)
|
r.Equal(installedInNamespace, createdLoadBalancerService.Namespace)
|
||||||
r.Equal(corev1.ServiceTypeLoadBalancer, createdLoadBalancerService.Spec.Type)
|
r.Equal(corev1.ServiceTypeLoadBalancer, createdLoadBalancerService.Spec.Type)
|
||||||
@ -430,9 +578,32 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var requireLoadBalancerDeleted = func(action coretesting.Action) {
|
var requireLoadBalancerDeleted = func(action coretesting.Action) {
|
||||||
deleteLoadBalancerAction := action.(coretesting.DeleteAction)
|
deleteAction := action.(coretesting.DeleteAction)
|
||||||
r.Equal("delete", deleteLoadBalancerAction.GetVerb())
|
r.Equal("delete", deleteAction.GetVerb())
|
||||||
r.Equal(generatedLoadBalancerServiceName, deleteLoadBalancerAction.GetName())
|
r.Equal(generatedLoadBalancerServiceName, deleteAction.GetName())
|
||||||
|
r.Equal("services", deleteAction.GetResource().Resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
var requireTLSSecretDeleted = func(action coretesting.Action) {
|
||||||
|
deleteAction := action.(coretesting.DeleteAction)
|
||||||
|
r.Equal("delete", deleteAction.GetVerb())
|
||||||
|
r.Equal(tlsSecretName, deleteAction.GetName())
|
||||||
|
r.Equal("secrets", deleteAction.GetResource().Resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
var requireTLSSecretWasCreated = func(action coretesting.Action) []byte {
|
||||||
|
createAction := action.(coretesting.CreateAction)
|
||||||
|
r.Equal("create", createAction.GetVerb())
|
||||||
|
createdSecret := createAction.GetObject().(*corev1.Secret)
|
||||||
|
r.Equal(tlsSecretName, createdSecret.Name)
|
||||||
|
r.Equal(installedInNamespace, createdSecret.Namespace)
|
||||||
|
r.Equal(corev1.SecretTypeTLS, createdSecret.Type)
|
||||||
|
r.Equal(labels, createdSecret.Labels)
|
||||||
|
r.Len(createdSecret.Data, 3)
|
||||||
|
r.NotNil(createdSecret.Data["ca.crt"])
|
||||||
|
r.NotNil(createdSecret.Data[corev1.TLSPrivateKeyKey])
|
||||||
|
r.NotNil(createdSecret.Data[corev1.TLSCertKey])
|
||||||
|
return createdSecret.Data["ca.crt"]
|
||||||
}
|
}
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
@ -471,20 +642,24 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
when("there are visible control plane nodes and a loadbalancer", func() {
|
when("there are visible control plane nodes and a loadbalancer and a tls Secret", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
addNodeWithRoleToTracker("control-plane")
|
addNodeWithRoleToTracker("control-plane")
|
||||||
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeInformerClient)
|
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeInformerClient)
|
||||||
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeAPIClient)
|
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeAPIClient)
|
||||||
|
tlsSecret := createStubTLSSecret(tlsSecretName)
|
||||||
|
r.NoError(kubeAPIClient.Tracker().Add(tlsSecret))
|
||||||
|
r.NoError(kubeInformerClient.Tracker().Add(tlsSecret))
|
||||||
})
|
})
|
||||||
|
|
||||||
it("does not start the impersonator, deletes the loadbalancer", func() {
|
it("does not start the impersonator, deletes the loadbalancer, deletes the Secret", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
requireTLSServerWasNeverStarted()
|
requireTLSServerWasNeverStarted()
|
||||||
r.Len(kubeAPIClient.Actions(), 2)
|
r.Len(kubeAPIClient.Actions(), 3)
|
||||||
requireNodesListed(kubeAPIClient.Actions()[0])
|
requireNodesListed(kubeAPIClient.Actions()[0])
|
||||||
requireLoadBalancerDeleted(kubeAPIClient.Actions()[1])
|
requireLoadBalancerDeleted(kubeAPIClient.Actions()[1])
|
||||||
|
requireTLSSecretDeleted(kubeAPIClient.Actions()[2])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -495,8 +670,8 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
})
|
})
|
||||||
|
|
||||||
it("automatically starts the impersonator", func() {
|
it("starts the impersonator without tls certs", func() {
|
||||||
requireTLSServerIsRunning()
|
requireTLSServerIsRunningWithoutCerts()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("starts the load balancer automatically", func() {
|
it("starts the load balancer automatically", func() {
|
||||||
@ -506,7 +681,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
when("there are not visible control plane nodes and a load balancer already exists", func() {
|
when("there are not visible control plane nodes and a load balancer already exists without an IP", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
addNodeWithRoleToTracker("worker")
|
addNodeWithRoleToTracker("worker")
|
||||||
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeInformerClient)
|
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeInformerClient)
|
||||||
@ -515,8 +690,8 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
})
|
})
|
||||||
|
|
||||||
it("automatically starts the impersonator", func() {
|
it("starts the impersonator without tls certs", func() {
|
||||||
requireTLSServerIsRunning()
|
requireTLSServerIsRunningWithoutCerts()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("does not start the load balancer automatically", func() {
|
it("does not start the load balancer automatically", func() {
|
||||||
@ -537,6 +712,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
r.Len(kubeAPIClient.Actions(), 2)
|
r.Len(kubeAPIClient.Actions(), 2)
|
||||||
requireNodesListed(kubeAPIClient.Actions()[0])
|
requireNodesListed(kubeAPIClient.Actions()[0])
|
||||||
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1])
|
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1])
|
||||||
|
requireTLSServerIsRunningWithoutCerts()
|
||||||
|
|
||||||
// update manually because the kubeAPIClient isn't connected to the informer in the tests
|
// update manually because the kubeAPIClient isn't connected to the informer in the tests
|
||||||
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeInformerClient)
|
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeInformerClient)
|
||||||
@ -544,9 +720,39 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
|
|
||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
r.Equal(1, startTLSListenerFuncWasCalled) // wasn't started a second time
|
r.Equal(1, startTLSListenerFuncWasCalled) // wasn't started a second time
|
||||||
requireTLSServerIsRunning() // still running
|
requireTLSServerIsRunningWithoutCerts() // still running
|
||||||
r.Len(kubeAPIClient.Actions(), 2) // no new API calls
|
r.Len(kubeAPIClient.Actions(), 2) // no new API calls
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("creates certs from the ip address listed on the load balancer", func() {
|
||||||
|
startInformersAndController()
|
||||||
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
|
r.Len(kubeAPIClient.Actions(), 2)
|
||||||
|
requireNodesListed(kubeAPIClient.Actions()[0])
|
||||||
|
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1])
|
||||||
|
requireTLSServerIsRunningWithoutCerts()
|
||||||
|
|
||||||
|
// update manually because the kubeAPIClient isn't connected to the informer in the tests
|
||||||
|
addLoadBalancerServiceWithIPToTracker(generatedLoadBalancerServiceName, kubeInformerClient)
|
||||||
|
waitForInformerCacheToSeeResourceVersion(kubeInformers.Core().V1().Services().Informer(), "0")
|
||||||
|
|
||||||
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
|
r.Equal(1, startTLSListenerFuncWasCalled) // wasn't started a second time
|
||||||
|
r.Len(kubeAPIClient.Actions(), 3)
|
||||||
|
ca := requireTLSSecretWasCreated(kubeAPIClient.Actions()[2])
|
||||||
|
requireTLSServerIsRunning(ca) // running with certs now
|
||||||
|
|
||||||
|
// update manually because the kubeAPIClient isn't connected to the informer in the tests
|
||||||
|
createdSecret := kubeAPIClient.Actions()[2].(coretesting.CreateAction).GetObject().(*corev1.Secret)
|
||||||
|
createdSecret.ResourceVersion = "1"
|
||||||
|
r.NoError(kubeInformerClient.Tracker().Add(createdSecret))
|
||||||
|
waitForInformerCacheToSeeResourceVersion(kubeInformers.Core().V1().Secrets().Informer(), "1")
|
||||||
|
|
||||||
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
|
r.Equal(1, startTLSListenerFuncWasCalled) // wasn't started a third time
|
||||||
|
r.Len(kubeAPIClient.Actions(), 3) // no more actions
|
||||||
|
requireTLSServerIsRunning(ca) // still running
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
when("getting the control plane nodes returns an error, e.g. when there are no nodes", func() {
|
when("getting the control plane nodes returns an error, e.g. when there are no nodes", func() {
|
||||||
@ -587,8 +793,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
addImpersonatorConfigMapToTracker(configMapResourceName, here.Doc(`
|
addImpersonatorConfigMapToTracker(configMapResourceName, here.Doc(`
|
||||||
mode: auto
|
mode: auto
|
||||||
endpoint: https://proxy.example.com:8443/
|
endpoint: 127.0.0.1
|
||||||
`),
|
`), // TODO what to do about ports
|
||||||
|
// TODO IP address and hostname should work
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -614,9 +821,10 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
it("starts the impersonator according to the settings in the ConfigMap", func() {
|
it("starts the impersonator according to the settings in the ConfigMap", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
requireTLSServerIsRunning()
|
r.Len(kubeAPIClient.Actions(), 2)
|
||||||
requireNodesListed(kubeAPIClient.Actions()[0])
|
requireNodesListed(kubeAPIClient.Actions()[0])
|
||||||
r.Len(kubeAPIClient.Actions(), 1)
|
ca := requireTLSSecretWasCreated(kubeAPIClient.Actions()[1])
|
||||||
|
requireTLSServerIsRunning(ca)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -637,7 +845,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
when("the configuration is enabled mode", func() {
|
when("the configuration is enabled mode", func() {
|
||||||
when("there are control plane nodes", func() {
|
when("no load balancer", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
addImpersonatorConfigMapToTracker(configMapResourceName, "mode: enabled")
|
addImpersonatorConfigMapToTracker(configMapResourceName, "mode: enabled")
|
||||||
addNodeWithRoleToTracker("control-plane")
|
addNodeWithRoleToTracker("control-plane")
|
||||||
@ -646,90 +854,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
it("starts the impersonator", func() {
|
it("starts the impersonator", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
requireTLSServerIsRunning()
|
requireTLSServerIsRunningWithoutCerts()
|
||||||
})
|
|
||||||
|
|
||||||
it("returns an error when the tls listener fails to start", func() {
|
|
||||||
startTLSListenerFuncError = errors.New("tls error")
|
|
||||||
startInformersAndController()
|
|
||||||
r.EqualError(controllerlib.TestSync(t, subject, *syncContext), "tls error")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("does not start the load balancer", func() {
|
|
||||||
startInformersAndController()
|
|
||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
|
||||||
r.Len(kubeAPIClient.Actions(), 1)
|
|
||||||
requireNodesListed(kubeAPIClient.Actions()[0])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
when("there are no control plane nodes but a loadbalancer already exists", func() {
|
|
||||||
it.Before(func() {
|
|
||||||
addImpersonatorConfigMapToTracker(configMapResourceName, "mode: enabled")
|
|
||||||
addNodeWithRoleToTracker("worker")
|
|
||||||
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeInformerClient)
|
|
||||||
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeAPIClient)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("starts the impersonator", func() {
|
|
||||||
startInformersAndController()
|
|
||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
|
||||||
requireTLSServerIsRunning()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("returns an error when the tls listener fails to start", func() {
|
|
||||||
startTLSListenerFuncError = errors.New("tls error")
|
|
||||||
startInformersAndController()
|
|
||||||
r.EqualError(controllerlib.TestSync(t, subject, *syncContext), "tls error")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("does not start the load balancer", func() {
|
|
||||||
startInformersAndController()
|
|
||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
|
||||||
r.Len(kubeAPIClient.Actions(), 1)
|
|
||||||
requireNodesListed(kubeAPIClient.Actions()[0])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
when("there are control plane nodes and a loadbalancer already exists", func() {
|
|
||||||
it.Before(func() {
|
|
||||||
addImpersonatorConfigMapToTracker(configMapResourceName, "mode: enabled")
|
|
||||||
addNodeWithRoleToTracker("control-plane")
|
|
||||||
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeInformerClient)
|
|
||||||
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeAPIClient)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("starts the impersonator", func() {
|
|
||||||
startInformersAndController()
|
|
||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
|
||||||
requireTLSServerIsRunning()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("returns an error when the tls listener fails to start", func() {
|
|
||||||
startTLSListenerFuncError = errors.New("tls error")
|
|
||||||
startInformersAndController()
|
|
||||||
r.EqualError(controllerlib.TestSync(t, subject, *syncContext), "tls error")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("stops the load balancer", func() {
|
|
||||||
startInformersAndController()
|
|
||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
|
||||||
r.Len(kubeAPIClient.Actions(), 2)
|
|
||||||
requireNodesListed(kubeAPIClient.Actions()[0])
|
|
||||||
requireLoadBalancerDeleted(kubeAPIClient.Actions()[1])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
when("there are no control plane nodes and there is no load balancer", func() {
|
|
||||||
it.Before(func() {
|
|
||||||
addImpersonatorConfigMapToTracker(configMapResourceName, "mode: enabled")
|
|
||||||
addNodeWithRoleToTracker("worker")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("starts the impersonator", func() {
|
|
||||||
startInformersAndController()
|
|
||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
|
||||||
requireTLSServerIsRunning()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns an error when the tls listener fails to start", func() {
|
it("returns an error when the tls listener fails to start", func() {
|
||||||
@ -746,6 +871,55 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1])
|
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
when("a loadbalancer already exists", func() {
|
||||||
|
it.Before(func() {
|
||||||
|
addImpersonatorConfigMapToTracker(configMapResourceName, "mode: enabled")
|
||||||
|
addNodeWithRoleToTracker("worker")
|
||||||
|
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeInformerClient)
|
||||||
|
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeAPIClient)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("starts the impersonator", func() {
|
||||||
|
startInformersAndController()
|
||||||
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
|
requireTLSServerIsRunningWithoutCerts()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns an error when the tls listener fails to start", func() {
|
||||||
|
startTLSListenerFuncError = errors.New("tls error")
|
||||||
|
startInformersAndController()
|
||||||
|
r.EqualError(controllerlib.TestSync(t, subject, *syncContext), "tls error")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("does not start the load balancer", func() {
|
||||||
|
startInformersAndController()
|
||||||
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
|
r.Len(kubeAPIClient.Actions(), 1)
|
||||||
|
requireNodesListed(kubeAPIClient.Actions()[0])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("a load balancer and a secret already exist", func() {
|
||||||
|
var tlsSecret *corev1.Secret
|
||||||
|
it.Before(func() {
|
||||||
|
addImpersonatorConfigMapToTracker(configMapResourceName, "mode: enabled")
|
||||||
|
addNodeWithRoleToTracker("worker")
|
||||||
|
tlsSecret = createActualTLSSecret(tlsSecretName)
|
||||||
|
r.NoError(kubeAPIClient.Tracker().Add(tlsSecret))
|
||||||
|
r.NoError(kubeInformerClient.Tracker().Add(tlsSecret))
|
||||||
|
addLoadBalancerServiceWithIPToTracker(generatedLoadBalancerServiceName, kubeInformerClient)
|
||||||
|
addLoadBalancerServiceWithIPToTracker(generatedLoadBalancerServiceName, kubeAPIClient)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("starts the impersonator with the existing tls certs, does not start loadbalancer or make tls secret", func() {
|
||||||
|
startInformersAndController()
|
||||||
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
|
r.Len(kubeAPIClient.Actions(), 1)
|
||||||
|
requireNodesListed(kubeAPIClient.Actions()[0])
|
||||||
|
requireTLSServerIsRunning(tlsSecret.Data["ca.crt"])
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
when("the configuration switches from enabled to disabled mode", func() {
|
when("the configuration switches from enabled to disabled mode", func() {
|
||||||
@ -758,7 +932,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
|
|
||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
requireTLSServerIsRunning()
|
requireTLSServerIsRunningWithoutCerts()
|
||||||
r.Len(kubeAPIClient.Actions(), 2)
|
r.Len(kubeAPIClient.Actions(), 2)
|
||||||
requireNodesListed(kubeAPIClient.Actions()[0])
|
requireNodesListed(kubeAPIClient.Actions()[0])
|
||||||
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1])
|
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1])
|
||||||
@ -782,7 +956,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
waitForInformerCacheToSeeResourceVersion(kubeInformers.Core().V1().ConfigMaps().Informer(), "2")
|
waitForInformerCacheToSeeResourceVersion(kubeInformers.Core().V1().ConfigMaps().Informer(), "2")
|
||||||
|
|
||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
requireTLSServerIsRunning()
|
requireTLSServerIsRunningWithoutCerts()
|
||||||
r.Len(kubeAPIClient.Actions(), 4)
|
r.Len(kubeAPIClient.Actions(), 4)
|
||||||
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[3])
|
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[3])
|
||||||
})
|
})
|
||||||
@ -795,7 +969,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
it("returns the error from the sync function", func() {
|
it("returns the error from the sync function", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
r.NoError(controllerlib.TestSync(t, subject, *syncContext))
|
||||||
requireTLSServerIsRunning()
|
requireTLSServerIsRunningWithoutCerts()
|
||||||
|
|
||||||
updateImpersonatorConfigMapInTracker(configMapResourceName, "mode: disabled", "1")
|
updateImpersonatorConfigMapInTracker(configMapResourceName, "mode: disabled", "1")
|
||||||
waitForInformerCacheToSeeResourceVersion(kubeInformers.Core().V1().ConfigMaps().Informer(), "1")
|
waitForInformerCacheToSeeResourceVersion(kubeInformers.Core().V1().ConfigMaps().Informer(), "1")
|
||||||
@ -860,5 +1034,29 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
|
|||||||
r.EqualError(controllerlib.TestSync(t, subject, *syncContext), "could not create load balancer: error on create")
|
r.EqualError(controllerlib.TestSync(t, subject, *syncContext), "could not create load balancer: error on create")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
when("there is an error deleting the tls secret", func() {
|
||||||
|
it.Before(func() {
|
||||||
|
addNodeWithRoleToTracker("control-plane")
|
||||||
|
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeInformerClient)
|
||||||
|
addLoadBalancerServiceToTracker(generatedLoadBalancerServiceName, kubeAPIClient)
|
||||||
|
tlsSecret := createStubTLSSecret(tlsSecretName)
|
||||||
|
r.NoError(kubeAPIClient.Tracker().Add(tlsSecret))
|
||||||
|
r.NoError(kubeInformerClient.Tracker().Add(tlsSecret))
|
||||||
|
startInformersAndController()
|
||||||
|
kubeAPIClient.PrependReactor("delete", "secrets", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
|
return true, nil, fmt.Errorf("error on delete")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("does not start the impersonator, deletes the loadbalancer, returns an error", func() {
|
||||||
|
r.EqualError(controllerlib.TestSync(t, subject, *syncContext), "error on delete")
|
||||||
|
requireTLSServerWasNeverStarted()
|
||||||
|
r.Len(kubeAPIClient.Actions(), 3)
|
||||||
|
requireNodesListed(kubeAPIClient.Actions()[0])
|
||||||
|
requireLoadBalancerDeleted(kubeAPIClient.Actions()[1])
|
||||||
|
requireTLSSecretDeleted(kubeAPIClient.Actions()[2])
|
||||||
|
})
|
||||||
|
})
|
||||||
}, spec.Parallel(), spec.Report(report.Terminal{}))
|
}, spec.Parallel(), spec.Report(report.Terminal{}))
|
||||||
}
|
}
|
||||||
|
@ -296,9 +296,11 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
|
|||||||
client.Kubernetes,
|
client.Kubernetes,
|
||||||
informers.installationNamespaceK8s.Core().V1().ConfigMaps(),
|
informers.installationNamespaceK8s.Core().V1().ConfigMaps(),
|
||||||
informers.installationNamespaceK8s.Core().V1().Services(),
|
informers.installationNamespaceK8s.Core().V1().Services(),
|
||||||
|
informers.installationNamespaceK8s.Core().V1().Secrets(),
|
||||||
controllerlib.WithInformer,
|
controllerlib.WithInformer,
|
||||||
controllerlib.WithInitialEvent,
|
controllerlib.WithInitialEvent,
|
||||||
"pinniped-concierge-impersonation-proxy-load-balancer", // TODO this string should come from `c.NamesConfig`
|
"pinniped-concierge-impersonation-proxy-load-balancer", // TODO this string should come from `c.NamesConfig`
|
||||||
|
"pinniped-concierge-impersonation-proxy-tls-serving-certificate", // TODO this string should come from `c.NamesConfig`
|
||||||
c.Labels,
|
c.Labels,
|
||||||
tls.Listen,
|
tls.Listen,
|
||||||
func() (http.Handler, error) {
|
func() (http.Handler, error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user