Impersonation controller updates CredentialIssuer on every call to Sync

- This commit does not include the updates that we plan to make to
  the `status.strategies[].frontend` field of the CredentialIssuer.
  That will come in a future commit.
This commit is contained in:
Ryan Richard 2021-03-02 14:48:58 -08:00
parent 84cc42b2ca
commit 1ad2c38509
3 changed files with 354 additions and 79 deletions

View File

@ -20,14 +20,18 @@ import (
v1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/apimachinery/pkg/util/intstr"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
"go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
pinnipedclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
"go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/clusterhost"
"go.pinniped.dev/internal/concierge/impersonator"
pinnipedcontroller "go.pinniped.dev/internal/controller"
"go.pinniped.dev/internal/controller/issuerconfig"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/plog"
)
@ -40,21 +44,31 @@ const (
caCrtKey = "ca.crt"
caKeyKey = "ca.key"
appLabelKey = "app"
// TODO move these to the api package after resolving an upcoming merge.
PendingStrategyReason = v1alpha1.StrategyReason("Pending")
ErrorDuringSetupStrategyReason = v1alpha1.StrategyReason("ErrorDuringSetup")
)
type impersonatorConfigController struct {
namespace string
configMapResourceName string
k8sClient kubernetes.Interface
configMapsInformer corev1informers.ConfigMapInformer
servicesInformer corev1informers.ServiceInformer
secretsInformer corev1informers.SecretInformer
credentialIssuerResourceName string
generatedLoadBalancerServiceName string
tlsSecretName string
caSecretName string
labels map[string]string
startTLSListenerFunc StartTLSListenerFunc
httpHandlerFactory func() (http.Handler, error)
k8sClient kubernetes.Interface
pinnipedAPIClient pinnipedclientset.Interface
configMapsInformer corev1informers.ConfigMapInformer
servicesInformer corev1informers.ServiceInformer
secretsInformer corev1informers.SecretInformer
labels map[string]string
clock clock.Clock
startTLSListenerFunc StartTLSListenerFunc
httpHandlerFactory func() (http.Handler, error)
server *http.Server
hasControlPlaneNodes *bool
@ -67,7 +81,9 @@ type StartTLSListenerFunc func(network, listenAddress string, config *tls.Config
func NewImpersonatorConfigController(
namespace string,
configMapResourceName string,
credentialIssuerResourceName string,
k8sClient kubernetes.Interface,
pinnipedAPIClient pinnipedclientset.Interface,
configMapsInformer corev1informers.ConfigMapInformer,
servicesInformer corev1informers.ServiceInformer,
secretsInformer corev1informers.SecretInformer,
@ -77,6 +93,7 @@ func NewImpersonatorConfigController(
tlsSecretName string,
caSecretName string,
labels map[string]string,
clock clock.Clock,
startTLSListenerFunc StartTLSListenerFunc,
httpHandlerFactory func() (http.Handler, error),
) controllerlib.Controller {
@ -86,14 +103,17 @@ func NewImpersonatorConfigController(
Syncer: &impersonatorConfigController{
namespace: namespace,
configMapResourceName: configMapResourceName,
k8sClient: k8sClient,
configMapsInformer: configMapsInformer,
servicesInformer: servicesInformer,
secretsInformer: secretsInformer,
credentialIssuerResourceName: credentialIssuerResourceName,
generatedLoadBalancerServiceName: generatedLoadBalancerServiceName,
tlsSecretName: tlsSecretName,
caSecretName: caSecretName,
k8sClient: k8sClient,
pinnipedAPIClient: pinnipedAPIClient,
configMapsInformer: configMapsInformer,
servicesInformer: servicesInformer,
secretsInformer: secretsInformer,
labels: labels,
clock: clock,
startTLSListenerFunc: startTLSListenerFunc,
httpHandlerFactory: httpHandlerFactory,
},
@ -126,11 +146,37 @@ func NewImpersonatorConfigController(
func (c *impersonatorConfigController) Sync(syncCtx controllerlib.Context) error {
plog.Debug("Starting impersonatorConfigController Sync")
ctx := syncCtx.Context
strategy, err := c.doSync(syncCtx.Context)
if err != nil {
strategy = &v1alpha1.CredentialIssuerStrategy{
Type: v1alpha1.ImpersonationProxyStrategyType,
Status: v1alpha1.ErrorStrategyStatus,
Reason: ErrorDuringSetupStrategyReason,
Message: err.Error(),
LastUpdateTime: metav1.NewTime(c.clock.Now()),
}
}
updateStrategyErr := c.updateStrategy(syncCtx.Context, strategy)
if updateStrategyErr != nil {
plog.Error("error while updating the CredentialIssuer status", err)
if err == nil {
err = updateStrategyErr
}
}
if err == nil {
plog.Debug("Successfully finished impersonatorConfigController Sync")
}
return err
}
func (c *impersonatorConfigController) doSync(ctx context.Context) (*v1alpha1.CredentialIssuerStrategy, error) {
config, err := c.loadImpersonationProxyConfiguration()
if err != nil {
return err
return nil, err
}
// Make a live API call to avoid the cost of having an informer watch all node changes on the cluster,
@ -140,7 +186,7 @@ func (c *impersonatorConfigController) Sync(syncCtx controllerlib.Context) error
if c.hasControlPlaneNodes == nil {
hasControlPlaneNodes, err := clusterhost.New(c.k8sClient).HasControlPlaneNodes(ctx)
if err != nil {
return err
return nil, err
}
c.hasControlPlaneNodes = &hasControlPlaneNodes
plog.Debug("Queried for control plane nodes", "foundControlPlaneNodes", hasControlPlaneNodes)
@ -148,39 +194,38 @@ func (c *impersonatorConfigController) Sync(syncCtx controllerlib.Context) error
if c.shouldHaveImpersonator(config) {
if err = c.ensureImpersonatorIsStarted(); err != nil {
return err
return nil, err
}
} else {
if err = c.ensureImpersonatorIsStopped(); err != nil {
return err
return nil, err
}
}
if c.shouldHaveLoadBalancer(config) {
if err = c.ensureLoadBalancerIsStarted(ctx); err != nil {
return err
return nil, err
}
} else {
if err = c.ensureLoadBalancerIsStopped(ctx); err != nil {
return err
return nil, err
}
}
waitingForLoadBalancer := false
if c.shouldHaveTLSSecret(config) {
var impersonationCA *certauthority.CA
if impersonationCA, err = c.ensureCASecretIsCreated(ctx); err != nil {
return err
return nil, err
}
if err = c.ensureTLSSecret(ctx, config, impersonationCA); err != nil {
return err
if waitingForLoadBalancer, err = c.ensureTLSSecret(ctx, config, impersonationCA); err != nil {
return nil, err
}
} else if err = c.ensureTLSSecretIsRemoved(ctx); err != nil {
return err
return nil, err
}
plog.Debug("Successfully finished impersonatorConfigController Sync")
return nil
return c.doSyncResult(waitingForLoadBalancer, config), nil
}
func (c *impersonatorConfigController) loadImpersonationProxyConfiguration() (*impersonator.Config, error) {
@ -212,7 +257,19 @@ func (c *impersonatorConfigController) loadImpersonationProxyConfiguration() (*i
}
func (c *impersonatorConfigController) shouldHaveImpersonator(config *impersonator.Config) bool {
return (config.Mode == impersonator.ModeAuto && !*c.hasControlPlaneNodes) || config.Mode == impersonator.ModeEnabled
return c.enabledByAutoMode(config) || config.Mode == impersonator.ModeEnabled
}
func (c *impersonatorConfigController) enabledByAutoMode(config *impersonator.Config) bool {
return config.Mode == impersonator.ModeAuto && !*c.hasControlPlaneNodes
}
func (c *impersonatorConfigController) disabledByAutoMode(config *impersonator.Config) bool {
return config.Mode == impersonator.ModeAuto && *c.hasControlPlaneNodes
}
func (c *impersonatorConfigController) disabledExplicitly(config *impersonator.Config) bool {
return config.Mode == impersonator.ModeDisabled
}
func (c *impersonatorConfigController) shouldHaveLoadBalancer(config *impersonator.Config) bool {
@ -223,6 +280,10 @@ func (c *impersonatorConfigController) shouldHaveTLSSecret(config *impersonator.
return c.shouldHaveImpersonator(config)
}
func (c *impersonatorConfigController) updateStrategy(ctx context.Context, strategy *v1alpha1.CredentialIssuerStrategy) error {
return issuerconfig.UpdateStrategy(ctx, c.credentialIssuerResourceName, c.labels, c.pinnipedAPIClient, *strategy)
}
func (c *impersonatorConfigController) loadBalancerExists() (bool, error) {
_, err := c.servicesInformer.Lister().Services(c.namespace).Get(c.generatedLoadBalancerServiceName)
notFound := k8serrors.IsNotFound(err)
@ -342,17 +403,17 @@ func (c *impersonatorConfigController) ensureLoadBalancerIsStopped(ctx context.C
return c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedLoadBalancerServiceName, metav1.DeleteOptions{})
}
func (c *impersonatorConfigController) ensureTLSSecret(ctx context.Context, config *impersonator.Config, ca *certauthority.CA) error {
func (c *impersonatorConfigController) ensureTLSSecret(ctx context.Context, config *impersonator.Config, ca *certauthority.CA) (bool, error) {
secretFromInformer, err := c.secretsInformer.Lister().Secrets(c.namespace).Get(c.tlsSecretName)
notFound := k8serrors.IsNotFound(err)
if !notFound && err != nil {
return err
return false, err
}
if !notFound {
secretWasDeleted, err := c.deleteTLSSecretWhenCertificateDoesNotMatchDesiredState(ctx, config, ca, secretFromInformer)
if err != nil {
return err
return false, err
}
// If it was deleted by the above call, then set it to nil. This allows us to avoid waiting
// for the informer cache to update before deciding to proceed to create the new Secret below.
@ -457,35 +518,36 @@ func certHostnameAndIPMatchDesiredState(desiredIP net.IP, actualIPs []net.IP, de
return false
}
func (c *impersonatorConfigController) ensureTLSSecretIsCreatedAndLoaded(ctx context.Context, config *impersonator.Config, secret *v1.Secret, ca *certauthority.CA) error {
func (c *impersonatorConfigController) ensureTLSSecretIsCreatedAndLoaded(ctx context.Context, config *impersonator.Config, secret *v1.Secret, ca *certauthority.CA) (bool, error) {
if secret != nil {
err := c.loadTLSCertFromSecret(secret)
if err != nil {
return err
return false, err
}
return nil
return false, nil
}
ip, hostname, nameIsReady, err := c.findDesiredTLSCertificateName(config)
if err != nil {
return err
return false, err
}
if !nameIsReady {
// Sync will get called again when the load balancer is updated with its ingress info, so this is not an error.
return nil
// Return "true" meaning that we are waiting for the load balancer.
return true, nil
}
newTLSSecret, err := c.createNewTLSSecret(ctx, ca, ip, hostname)
if err != nil {
return err
return false, err
}
err = c.loadTLSCertFromSecret(newTLSSecret)
if err != nil {
return err
return false, err
}
return nil
return false, nil
}
func (c *impersonatorConfigController) ensureCASecretIsCreated(ctx context.Context) (*certauthority.CA, error) {
@ -672,6 +734,43 @@ func (c *impersonatorConfigController) ensureTLSSecretIsRemoved(ctx context.Cont
return nil
}
func (c *impersonatorConfigController) doSyncResult(waitingForLoadBalancer bool, config *impersonator.Config) *v1alpha1.CredentialIssuerStrategy {
switch {
case waitingForLoadBalancer:
return &v1alpha1.CredentialIssuerStrategy{
Type: v1alpha1.ImpersonationProxyStrategyType,
Status: v1alpha1.ErrorStrategyStatus,
Reason: PendingStrategyReason,
Message: "waiting for load balancer Service to be assigned IP or hostname",
LastUpdateTime: metav1.NewTime(c.clock.Now()),
}
case c.disabledExplicitly(config):
return &v1alpha1.CredentialIssuerStrategy{
Type: v1alpha1.ImpersonationProxyStrategyType,
Status: v1alpha1.ErrorStrategyStatus,
Reason: v1alpha1.DisabledStrategyReason,
Message: "impersonation proxy was explicitly disabled by configuration",
LastUpdateTime: metav1.NewTime(c.clock.Now()),
}
case c.disabledByAutoMode(config):
return &v1alpha1.CredentialIssuerStrategy{
Type: v1alpha1.ImpersonationProxyStrategyType,
Status: v1alpha1.ErrorStrategyStatus,
Reason: v1alpha1.DisabledStrategyReason,
Message: "automatically determined that impersonation proxy should be disabled",
LastUpdateTime: metav1.NewTime(c.clock.Now()),
}
default:
return &v1alpha1.CredentialIssuerStrategy{
Type: v1alpha1.ImpersonationProxyStrategyType,
Status: v1alpha1.SuccessStrategyStatus,
Reason: v1alpha1.ListeningStrategyReason,
Message: "impersonation proxy is ready to accept client connections",
LastUpdateTime: metav1.NewTime(c.clock.Now()),
}
}
}
func (c *impersonatorConfigController) setTLSCert(cert *tls.Certificate) {
c.tlsCertMutex.Lock()
defer c.tlsCertMutex.Unlock()

View File

@ -26,12 +26,15 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/clock"
kubeinformers "k8s.io/client-go/informers"
corev1informers "k8s.io/client-go/informers/core/v1"
kubernetesfake "k8s.io/client-go/kubernetes/fake"
coretesting "k8s.io/client-go/testing"
"k8s.io/client-go/tools/cache"
"go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
pinnipedfake "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned/fake"
"go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/testutil"
@ -86,6 +89,8 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) {
_ = NewImpersonatorConfigController(
installedInNamespace,
configMapResourceName,
"",
nil,
nil,
configMapsInformer,
servicesInformer,
@ -98,6 +103,7 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) {
nil,
nil,
nil,
nil,
)
configMapsInformerFilter = observableWithInformerOption.GetFilterForInformer(configMapsInformer)
servicesInformerFilter = observableWithInformerOption.GetFilterForInformer(servicesInformer)
@ -273,6 +279,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
spec.Run(t, "Sync", func(t *testing.T, when spec.G, it spec.S) {
const installedInNamespace = "some-namespace"
const configMapResourceName = "some-configmap-resource-name"
const credentialIssuerResourceName = "some-credential-issuer-resource-name"
const loadBalancerServiceName = "some-service-resource-name"
const tlsSecretName = "some-tls-secret-name" //nolint:gosec // this is not a credential
const caSecretName = "some-ca-secret-name"
@ -284,6 +291,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
var subject controllerlib.Controller
var kubeAPIClient *kubernetesfake.Clientset
var pinnipedAPIClient *pinnipedfake.Clientset
var kubeInformerClient *kubernetesfake.Clientset
var kubeInformers kubeinformers.SharedInformerFactory
var timeoutContext context.Context
@ -294,6 +302,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
var startTLSListenerUponCloseError error
var httpHandlerFactoryFuncError error
var startedTLSListener net.Listener
var frozenNow time.Time
var startTLSListenerFunc = func(network, listenAddress string, config *tls.Config) (net.Listener, error) {
startTLSListenerFuncWasCalled++
@ -423,7 +432,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
subject = NewImpersonatorConfigController(
installedInNamespace,
configMapResourceName,
credentialIssuerResourceName,
kubeAPIClient,
pinnipedAPIClient,
kubeInformers.Core().V1().ConfigMaps(),
kubeInformers.Core().V1().Services(),
kubeInformers.Core().V1().Secrets(),
@ -433,6 +444,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
tlsSecretName,
caSecretName,
labels,
clock.NewFakeClock(frozenNow),
startTLSListenerFunc,
func() (http.Handler, error) {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
@ -652,6 +664,74 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
)
}
var newSuccessStrategy = func() v1alpha1.CredentialIssuerStrategy {
return v1alpha1.CredentialIssuerStrategy{
Type: v1alpha1.ImpersonationProxyStrategyType,
Status: v1alpha1.SuccessStrategyStatus,
Reason: v1alpha1.ListeningStrategyReason,
Message: "impersonation proxy is ready to accept client connections",
LastUpdateTime: metav1.NewTime(frozenNow),
}
}
var newAutoDisabledStrategy = func() v1alpha1.CredentialIssuerStrategy {
return v1alpha1.CredentialIssuerStrategy{
Type: v1alpha1.ImpersonationProxyStrategyType,
Status: v1alpha1.ErrorStrategyStatus,
Reason: v1alpha1.DisabledStrategyReason,
Message: "automatically determined that impersonation proxy should be disabled",
LastUpdateTime: metav1.NewTime(frozenNow),
}
}
var newManuallyDisabledStrategy = func() v1alpha1.CredentialIssuerStrategy {
s := newAutoDisabledStrategy()
s.Message = "impersonation proxy was explicitly disabled by configuration"
return s
}
var newPendingStrategy = func() v1alpha1.CredentialIssuerStrategy {
return v1alpha1.CredentialIssuerStrategy{
Type: v1alpha1.ImpersonationProxyStrategyType,
Status: v1alpha1.ErrorStrategyStatus,
Reason: PendingStrategyReason,
Message: "waiting for load balancer Service to be assigned IP or hostname",
LastUpdateTime: metav1.NewTime(frozenNow),
}
}
var newErrorStrategy = func(msg string) v1alpha1.CredentialIssuerStrategy {
return v1alpha1.CredentialIssuerStrategy{
Type: v1alpha1.ImpersonationProxyStrategyType,
Status: v1alpha1.ErrorStrategyStatus,
Reason: ErrorDuringSetupStrategyReason,
Message: msg,
LastUpdateTime: metav1.NewTime(frozenNow),
}
}
var requireCredentialIssuer = func(expectedStrategy v1alpha1.CredentialIssuerStrategy) {
// Rather than looking at the specific API actions on pinnipedAPIClient, we just look
// at the final result here.
// This is because the implementation is using a helper from another package to create
// and update the CredentialIssuer, and the specific API actions performed by that
// implementation are pretty complex and are already tested by its own unit tests.
// As long as we get the final result that we wanted then we are happy for the purposes
// of this test.
credentialIssuerObj, err := pinnipedAPIClient.Tracker().Get(
schema.GroupVersionResource{
Group: v1alpha1.SchemeGroupVersion.Group,
Version: v1alpha1.SchemeGroupVersion.Version,
Resource: "credentialissuers",
}, "", credentialIssuerResourceName,
)
r.NoError(err)
credentialIssuer, ok := credentialIssuerObj.(*v1alpha1.CredentialIssuer)
r.True(ok, "should have been able to cast this obj to CredentialIssuer: %v", credentialIssuerObj)
r.Equal(labels, credentialIssuer.Labels)
r.Equal([]v1alpha1.CredentialIssuerStrategy{expectedStrategy}, credentialIssuer.Status.Strategies)
}
var requireLoadBalancerWasCreated = func(action coretesting.Action) {
createAction, ok := action.(coretesting.CreateAction)
r.True(ok, "should have been able to cast this action to CreateAction: %v", action)
@ -738,6 +818,8 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
kubeinformers.WithNamespace(installedInNamespace),
)
kubeAPIClient = kubernetesfake.NewSimpleClientset()
pinnipedAPIClient = pinnipedfake.NewSimpleClientset()
frozenNow = time.Date(2021, time.March, 2, 7, 42, 0, 0, time.Local)
})
it.After(func() {
@ -747,7 +829,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
when("the ConfigMap does not yet exist in the installation namespace or it was deleted (defaults to auto mode)", func() {
it.Before(func() {
addImpersonatorConfigMapToTracker("some-other-configmap", "foo: bar", kubeInformerClient)
addImpersonatorConfigMapToTracker("some-other-unrelated-configmap", "foo: bar", kubeInformerClient)
})
when("there are visible control plane nodes", func() {
@ -761,6 +843,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSServerWasNeverStarted()
r.Len(kubeAPIClient.Actions(), 1)
requireNodesListed(kubeAPIClient.Actions()[0])
requireCredentialIssuer(newAutoDisabledStrategy())
})
})
@ -780,6 +863,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireNodesListed(kubeAPIClient.Actions()[0])
requireLoadBalancerWasDeleted(kubeAPIClient.Actions()[1])
requireTLSSecretWasDeleted(kubeAPIClient.Actions()[2])
requireCredentialIssuer(newAutoDisabledStrategy())
})
})
@ -796,10 +880,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireNodesListed(kubeAPIClient.Actions()[0])
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1])
requireCASecretWasCreated(kubeAPIClient.Actions()[2])
requireCredentialIssuer(newPendingStrategy())
})
})
when("there are not visible control plane nodes and a load balancer already exists without an IP", func() {
when("there are not visible control plane nodes and a load balancer already exists without an IP/hostname", func() {
it.Before(func() {
addNodeWithRoleToTracker("worker", kubeAPIClient)
addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeInformerClient)
@ -813,6 +898,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.Len(kubeAPIClient.Actions(), 2)
requireNodesListed(kubeAPIClient.Actions()[0])
requireCASecretWasCreated(kubeAPIClient.Actions()[1])
requireCredentialIssuer(newPendingStrategy())
})
})
@ -830,6 +916,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.Len(kubeAPIClient.Actions(), 2)
requireNodesListed(kubeAPIClient.Actions()[0])
requireCASecretWasCreated(kubeAPIClient.Actions()[1])
requireCredentialIssuer(newPendingStrategy())
})
})
@ -847,6 +934,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.Len(kubeAPIClient.Actions(), 2)
requireNodesListed(kubeAPIClient.Actions()[0])
requireCASecretWasCreated(kubeAPIClient.Actions()[1])
requireCredentialIssuer(newErrorStrategy("could not find valid IP addresses or hostnames from load balancer some-namespace/some-service-resource-name"))
})
})
@ -865,6 +953,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1])
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca)
requireTLSServerIsRunning(ca, "127.0.0.123", map[string]string{"127.0.0.123:443": testServerAddr()})
requireCredentialIssuer(newSuccessStrategy())
})
it("keeps the secret around after resync", func() {
@ -876,6 +965,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.NoError(runControllerSync())
r.Len(kubeAPIClient.Actions(), 3) // nothing changed
requireCredentialIssuer(newSuccessStrategy())
})
})
@ -895,6 +985,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1])
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca)
requireTLSServerIsRunning(ca, firstHostname, map[string]string{firstHostname + httpsPort: testServerAddr()})
requireCredentialIssuer(newSuccessStrategy())
})
it("keeps the secret around after resync", func() {
@ -906,6 +997,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.NoError(runControllerSync())
r.Len(kubeAPIClient.Actions(), 3) // nothing changed
requireCredentialIssuer(newSuccessStrategy())
})
})
@ -925,6 +1017,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1])
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca)
requireTLSServerIsRunning(ca, firstHostname, map[string]string{firstHostname + httpsPort: testServerAddr()})
requireCredentialIssuer(newSuccessStrategy())
})
it("keeps the secret around after resync", func() {
@ -936,6 +1029,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.NoError(runControllerSync())
r.Len(kubeAPIClient.Actions(), 3) // nothing changed
requireCredentialIssuer(newSuccessStrategy())
})
})
@ -960,6 +1054,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasDeleted(kubeAPIClient.Actions()[1])
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], caCrt)
requireTLSServerIsRunning(caCrt, testServerAddr(), nil)
requireCredentialIssuer(newSuccessStrategy())
})
})
@ -983,6 +1078,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireNodesListed(kubeAPIClient.Actions()[0])
requireTLSSecretWasDeleted(kubeAPIClient.Actions()[1])
requireTLSServerIsRunningWithoutCerts()
requireCredentialIssuer(newErrorStrategy("error on delete"))
})
})
@ -1010,10 +1106,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
updateLoadBalancerServiceInTracker(loadBalancerServiceName, []corev1.LoadBalancerIngress{{IP: "not-an-ip"}}, kubeInformerClient, "1")
waitForInformerCacheToSeeResourceVersion(kubeInformers.Core().V1().Services().Informer(), "1")
r.EqualError(runControllerSync(),
"could not find valid IP addresses or hostnames from load balancer some-namespace/some-service-resource-name")
errString := "could not find valid IP addresses or hostnames from load balancer some-namespace/some-service-resource-name"
r.EqualError(runControllerSync(), errString)
r.Len(kubeAPIClient.Actions(), 1) // no new actions
requireTLSServerIsRunning(caCrt, testServerAddr(), nil)
requireCredentialIssuer(newErrorStrategy(errString))
})
})
})
@ -1036,6 +1133,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSServerWasNeverStarted()
requireNodesListed(kubeAPIClient.Actions()[0])
r.Len(kubeAPIClient.Actions(), 1)
requireCredentialIssuer(newAutoDisabledStrategy())
})
})
@ -1052,6 +1150,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1])
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca)
requireTLSServerIsRunning(ca, testServerAddr(), nil)
requireCredentialIssuer(newSuccessStrategy())
})
})
})
@ -1068,6 +1167,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSServerWasNeverStarted()
requireNodesListed(kubeAPIClient.Actions()[0])
r.Len(kubeAPIClient.Actions(), 1)
requireCredentialIssuer(newManuallyDisabledStrategy())
})
})
@ -1078,25 +1178,22 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
addNodeWithRoleToTracker("control-plane", kubeAPIClient)
})
it("starts the impersonator", func() {
startInformersAndController()
r.NoError(runControllerSync())
requireTLSServerIsRunningWithoutCerts()
})
it("returns an error when the tls listener fails to start", func() {
startTLSListenerFuncError = errors.New("tls error")
startInformersAndController()
r.EqualError(runControllerSync(), "tls error")
})
it("starts the load balancer", func() {
it("starts the impersonator and creates a load balancer", func() {
startInformersAndController()
r.NoError(runControllerSync())
r.Len(kubeAPIClient.Actions(), 3)
requireNodesListed(kubeAPIClient.Actions()[0])
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1])
requireCASecretWasCreated(kubeAPIClient.Actions()[2])
requireTLSServerIsRunningWithoutCerts()
requireCredentialIssuer(newPendingStrategy())
})
it("returns an error when the tls listener fails to start", func() {
startTLSListenerFuncError = errors.New("tls error")
startInformersAndController()
r.EqualError(runControllerSync(), "tls error")
requireCredentialIssuer(newErrorStrategy("tls error"))
})
})
@ -1108,24 +1205,21 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
addLoadBalancerServiceToTracker(loadBalancerServiceName, kubeAPIClient)
})
it("starts the impersonator", func() {
it("starts the impersonator without creating a load balancer", func() {
startInformersAndController()
r.NoError(runControllerSync())
r.Len(kubeAPIClient.Actions(), 2)
requireNodesListed(kubeAPIClient.Actions()[0])
requireCASecretWasCreated(kubeAPIClient.Actions()[1])
requireTLSServerIsRunningWithoutCerts()
requireCredentialIssuer(newPendingStrategy())
})
it("returns an error when the tls listener fails to start", func() {
startTLSListenerFuncError = errors.New("tls error")
startInformersAndController()
r.EqualError(runControllerSync(), "tls error")
})
it("does not start the load balancer", func() {
startInformersAndController()
r.NoError(runControllerSync())
r.Len(kubeAPIClient.Actions(), 2)
requireNodesListed(kubeAPIClient.Actions()[0])
requireCASecretWasCreated(kubeAPIClient.Actions()[1])
requireCredentialIssuer(newErrorStrategy("tls error"))
})
})
@ -1150,10 +1244,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.Len(kubeAPIClient.Actions(), 1)
requireNodesListed(kubeAPIClient.Actions()[0])
requireTLSServerIsRunning(caCrt, testServerAddr(), nil)
requireCredentialIssuer(newSuccessStrategy())
})
})
when("we have a hostname specified for the endpoint", func() {
when("the configmap has a hostname specified for the endpoint", func() {
const fakeHostname = "fake.example.com"
it.Before(func() {
configMapYAML := fmt.Sprintf("{mode: enabled, endpoint: %s}", fakeHostname)
@ -1161,7 +1256,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
addNodeWithRoleToTracker("worker", kubeAPIClient)
})
it("starts the impersonator, generates a valid cert for the hostname", func() {
it("starts the impersonator, generates a valid cert for the specified hostname", func() {
startInformersAndController()
r.NoError(runControllerSync())
r.Len(kubeAPIClient.Actions(), 3)
@ -1170,10 +1265,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], 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())
})
})
when("endpoint is IP address with a port", func() {
when("the configmap has a endpoint which is an IP address with a port", func() {
const fakeIPWithPort = "127.0.0.1:3000"
it.Before(func() {
configMapYAML := fmt.Sprintf("{mode: enabled, endpoint: %s}", fakeIPWithPort)
@ -1181,7 +1277,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
addNodeWithRoleToTracker("worker", kubeAPIClient)
})
it("starts the impersonator, generates a valid cert for the hostname", func() {
it("starts the impersonator, generates a valid cert for the specified IP address", func() {
startInformersAndController()
r.NoError(runControllerSync())
r.Len(kubeAPIClient.Actions(), 3)
@ -1190,10 +1286,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca)
// Check that the server is running and that TLS certs that are being served are are for fakeIPWithPort.
requireTLSServerIsRunning(ca, fakeIPWithPort, map[string]string{fakeIPWithPort: testServerAddr()})
requireCredentialIssuer(newSuccessStrategy())
})
})
when("endpoint is hostname with a port", func() {
when("the configmap has a endpoint which is a hostname with a port", func() {
const fakeHostnameWithPort = "fake.example.com:3000"
it.Before(func() {
configMapYAML := fmt.Sprintf("{mode: enabled, endpoint: %s}", fakeHostnameWithPort)
@ -1201,7 +1298,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
addNodeWithRoleToTracker("worker", kubeAPIClient)
})
it("starts the impersonator, generates a valid cert for the hostname", func() {
it("starts the impersonator, generates a valid cert for the specified hostname", func() {
startInformersAndController()
r.NoError(runControllerSync())
r.Len(kubeAPIClient.Actions(), 3)
@ -1210,10 +1307,11 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], 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())
})
})
when("switching from ip address endpoint to hostname endpoint and back to ip address", func() {
when("switching the configmap from ip address endpoint to hostname endpoint and back to ip address", func() {
const fakeHostname = "fake.example.com"
const fakeIP = "127.0.0.42"
var hostnameYAML = fmt.Sprintf("{mode: enabled, endpoint: %s}", fakeHostname)
@ -1232,6 +1330,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca)
// Check that the server is running and that TLS certs that are being served are are for fakeIP.
requireTLSServerIsRunning(ca, fakeIP, map[string]string{fakeIP + httpsPort: testServerAddr()})
requireCredentialIssuer(newSuccessStrategy())
// Simulate the informer cache's background update from its watch.
addSecretFromCreateActionToTracker(kubeAPIClient.Actions()[1], kubeInformerClient, "1")
@ -1249,6 +1348,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasCreated(kubeAPIClient.Actions()[4], ca) // reuses the old 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())
// Simulate the informer cache's background update from its watch.
deleteSecretFromTracker(tlsSecretName, kubeInformerClient)
@ -1265,6 +1365,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasCreated(kubeAPIClient.Actions()[6], ca) // reuses the old CA again
// Check that the server is running and that TLS certs that are being served are are for fakeIP.
requireTLSServerIsRunning(ca, fakeIP, map[string]string{fakeIP + httpsPort: testServerAddr()})
requireCredentialIssuer(newSuccessStrategy())
})
})
@ -1285,6 +1386,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], 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())
// Simulate the informer cache's background update from its watch for the CA Secret.
addSecretFromCreateActionToTracker(kubeAPIClient.Actions()[1], kubeInformerClient, "1")
@ -1300,6 +1402,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
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())
})
})
@ -1320,6 +1423,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], 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())
// Simulate the informer cache's background update from its watch for the CA Secret.
addSecretFromCreateActionToTracker(kubeAPIClient.Actions()[2], kubeInformerClient, "1")
@ -1337,6 +1441,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasCreated(kubeAPIClient.Actions()[5], ca) // created using the new 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())
})
})
@ -1355,6 +1460,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], 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())
// Simulate the informer cache's background update from its watch for the CA Secret.
addSecretFromCreateActionToTracker(kubeAPIClient.Actions()[2], kubeInformerClient, "1")
@ -1381,6 +1487,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasCreated(kubeAPIClient.Actions()[4], caCrt) // created using the updated CA
// Check that the server is running and that TLS certs that are being served are are for fakeHostname.
requireTLSServerIsRunning(caCrt, fakeHostname, map[string]string{fakeHostname + httpsPort: testServerAddr()})
requireCredentialIssuer(newSuccessStrategy())
})
when("deleting the TLS cert due to mismatched CA results in an error", func() {
@ -1397,6 +1504,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.Error(runControllerSync(), "error on tls secret delete")
r.Len(kubeAPIClient.Actions(), 4)
requireTLSSecretWasDeleted(kubeAPIClient.Actions()[3]) // tried to delete cert but failed
requireCredentialIssuer(newErrorStrategy("error on tls secret delete"))
})
})
})
@ -1417,6 +1525,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireNodesListed(kubeAPIClient.Actions()[0])
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1])
requireCASecretWasCreated(kubeAPIClient.Actions()[2])
requireCredentialIssuer(newPendingStrategy())
// Simulate the informer cache's background update from its watch.
addServiceFromCreateActionToTracker(kubeAPIClient.Actions()[1], kubeInformerClient, "1")
@ -1431,6 +1540,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSServerIsNoLongerRunning()
r.Len(kubeAPIClient.Actions(), 4)
requireLoadBalancerWasDeleted(kubeAPIClient.Actions()[3])
requireCredentialIssuer(newManuallyDisabledStrategy())
deleteServiceFromTracker(loadBalancerServiceName, kubeInformerClient)
waitForServiceToBeDeleted(kubeInformers.Core().V1().Services(), loadBalancerServiceName)
@ -1442,6 +1552,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSServerIsRunningWithoutCerts()
r.Len(kubeAPIClient.Actions(), 5)
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[4])
requireCredentialIssuer(newPendingStrategy())
})
when("there is an error while shutting down the server", func() {
@ -1459,6 +1570,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.EqualError(runControllerSync(), "fake server close error")
requireTLSServerIsNoLongerRunning()
requireCredentialIssuer(newErrorStrategy("fake server close error"))
})
})
})
@ -1480,6 +1592,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
ca := requireCASecretWasCreated(kubeAPIClient.Actions()[1]) // created immediately because "endpoint" was specified
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], ca)
requireTLSServerIsRunning(ca, testServerAddr(), nil)
requireCredentialIssuer(newSuccessStrategy())
// Simulate the informer cache's background update from its watch.
addSecretFromCreateActionToTracker(kubeAPIClient.Actions()[1], kubeInformerClient, "1")
@ -1496,6 +1609,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[3])
requireTLSSecretWasDeleted(kubeAPIClient.Actions()[4]) // the Secret was deleted because it contained a cert with the wrong IP
requireTLSServerIsRunningWithoutCerts()
requireCredentialIssuer(newPendingStrategy())
// Simulate the informer cache's background update from its watch.
addServiceFromCreateActionToTracker(kubeAPIClient.Actions()[3], kubeInformerClient, "1")
@ -1507,6 +1621,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.NoError(runControllerSync())
r.Len(kubeAPIClient.Actions(), 5) // no new actions while it is waiting for the load balancer's ingress
requireTLSServerIsRunningWithoutCerts()
requireCredentialIssuer(newPendingStrategy())
// Update the ingress of the LB in the informer's client and run Sync again.
fakeIP := "127.0.0.123"
@ -1517,6 +1632,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasCreated(kubeAPIClient.Actions()[5], ca) // reuses the existing CA
// Check that the server is running and that TLS certs that are being served are are for fakeIP.
requireTLSServerIsRunning(ca, fakeIP, map[string]string{fakeIP + httpsPort: testServerAddr()})
requireCredentialIssuer(newSuccessStrategy())
// Simulate the informer cache's background update from its watch.
addSecretFromCreateActionToTracker(kubeAPIClient.Actions()[5], kubeInformerClient, "3")
@ -1532,6 +1648,8 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireLoadBalancerWasDeleted(kubeAPIClient.Actions()[6])
requireTLSSecretWasDeleted(kubeAPIClient.Actions()[7])
requireTLSSecretWasCreated(kubeAPIClient.Actions()[8], ca) // recreated because the endpoint was updated, reused the old CA
requireTLSServerIsRunning(ca, testServerAddr(), nil)
requireCredentialIssuer(newSuccessStrategy())
})
})
})
@ -1549,6 +1667,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1])
requireCASecretWasCreated(kubeAPIClient.Actions()[2])
requireTLSServerIsRunningWithoutCerts()
requireCredentialIssuer(newPendingStrategy())
// Simulate the informer cache's background update from its watch.
addServiceFromCreateActionToTracker(kubeAPIClient.Actions()[1], kubeInformerClient, "1")
@ -1559,7 +1678,8 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.NoError(runControllerSync())
r.Equal(1, startTLSListenerFuncWasCalled) // wasn't started a second time
requireTLSServerIsRunningWithoutCerts() // still running
r.Len(kubeAPIClient.Actions(), 3) // no new API calls
requireCredentialIssuer(newPendingStrategy())
r.Len(kubeAPIClient.Actions(), 3) // no new API calls
})
it("creates certs from the ip address listed on the load balancer", func() {
@ -1570,6 +1690,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1])
ca := requireCASecretWasCreated(kubeAPIClient.Actions()[2])
requireTLSServerIsRunningWithoutCerts()
requireCredentialIssuer(newPendingStrategy())
// Simulate the informer cache's background update from its watch.
addServiceFromCreateActionToTracker(kubeAPIClient.Actions()[1], kubeInformerClient, "0")
@ -1585,6 +1706,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.Len(kubeAPIClient.Actions(), 4)
requireTLSSecretWasCreated(kubeAPIClient.Actions()[3], ca) // uses the ca from last time
requireTLSServerIsRunning(ca, testServerAddr(), nil) // running with certs now
requireCredentialIssuer(newSuccessStrategy())
// Simulate the informer cache's background update from its watch.
addSecretFromCreateActionToTracker(kubeAPIClient.Actions()[3], kubeInformerClient, "2")
@ -1594,6 +1716,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.Equal(1, startTLSListenerFuncWasCalled) // wasn't started again
r.Len(kubeAPIClient.Actions(), 4) // no more actions
requireTLSServerIsRunning(ca, testServerAddr(), nil) // still running
requireCredentialIssuer(newSuccessStrategy())
})
it("creates certs from the hostname listed on the load balancer", func() {
@ -1605,6 +1728,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireLoadBalancerWasCreated(kubeAPIClient.Actions()[1])
ca := requireCASecretWasCreated(kubeAPIClient.Actions()[2])
requireTLSServerIsRunningWithoutCerts()
requireCredentialIssuer(newPendingStrategy())
// Simulate the informer cache's background update from its watch.
addServiceFromCreateActionToTracker(kubeAPIClient.Actions()[1], kubeInformerClient, "0")
@ -1620,6 +1744,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.Len(kubeAPIClient.Actions(), 4)
requireTLSSecretWasCreated(kubeAPIClient.Actions()[3], ca) // uses the ca from last time
requireTLSServerIsRunning(ca, hostname, map[string]string{hostname + httpsPort: testServerAddr()}) // running with certs now
requireCredentialIssuer(newSuccessStrategy())
// Simulate the informer cache's background update from its watch.
addSecretFromCreateActionToTracker(kubeAPIClient.Actions()[3], kubeInformerClient, "1")
@ -1629,6 +1754,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.Equal(1, startTLSListenerFuncWasCalled) // wasn't started a third time
r.Len(kubeAPIClient.Actions(), 4) // no more actions
requireTLSServerIsRunning(ca, hostname, map[string]string{hostname + httpsPort: testServerAddr()}) // still running
requireCredentialIssuer(newSuccessStrategy())
})
})
@ -1636,6 +1762,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
it("returns an error", func() {
startInformersAndController()
r.EqualError(runControllerSync(), "no nodes found")
requireCredentialIssuer(newErrorStrategy("no nodes found"))
requireTLSServerWasNeverStarted()
})
})
@ -1649,6 +1776,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
it("returns an error", func() {
startInformersAndController()
r.EqualError(runControllerSync(), "some factory error")
requireCredentialIssuer(newErrorStrategy("some factory error"))
requireTLSServerWasNeverStarted()
})
})
@ -1660,7 +1788,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
it("returns an error", func() {
startInformersAndController()
r.EqualError(runControllerSync(), "invalid impersonator configuration: decode yaml: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type impersonator.Config")
errString := "invalid impersonator configuration: decode yaml: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type impersonator.Config"
r.EqualError(runControllerSync(), errString)
requireCredentialIssuer(newErrorStrategy(errString))
requireTLSServerWasNeverStarted()
})
})
@ -1668,14 +1798,16 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
when("there is an error creating the load balancer", func() {
it.Before(func() {
addNodeWithRoleToTracker("worker", kubeAPIClient)
startInformersAndController()
kubeAPIClient.PrependReactor("create", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("error on create")
})
})
it("exits with an error", func() {
it("returns an error", func() {
startInformersAndController()
r.EqualError(runControllerSync(), "error on create")
requireCredentialIssuer(newErrorStrategy("error on create"))
requireTLSServerIsRunningWithoutCerts()
})
})
@ -1695,6 +1827,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
it("starts the impersonator without certs and returns an error", func() {
startInformersAndController()
r.EqualError(runControllerSync(), "error on tls secret create")
requireCredentialIssuer(newErrorStrategy("error on tls secret create"))
requireTLSServerIsRunningWithoutCerts()
r.Len(kubeAPIClient.Actions(), 3)
requireNodesListed(kubeAPIClient.Actions()[0])
@ -1719,6 +1852,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
it("starts the impersonator without certs and returns an error", func() {
startInformersAndController()
r.EqualError(runControllerSync(), "error on ca secret create")
requireCredentialIssuer(newErrorStrategy("error on ca secret create"))
requireTLSServerIsRunningWithoutCerts()
r.Len(kubeAPIClient.Actions(), 2)
requireNodesListed(kubeAPIClient.Actions()[0])
@ -1735,7 +1869,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
it("starts the impersonator without certs and returns an error", func() {
startInformersAndController()
r.EqualError(runControllerSync(), "could not load CA: tls: failed to find any PEM data in certificate input")
errString := "could not load CA: tls: failed to find any PEM data in certificate input"
r.EqualError(runControllerSync(), errString)
requireCredentialIssuer(newErrorStrategy(errString))
requireTLSServerIsRunningWithoutCerts()
r.Len(kubeAPIClient.Actions(), 1)
requireNodesListed(kubeAPIClient.Actions()[0])
@ -1756,6 +1892,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
it("does not start the impersonator, deletes the loadbalancer, returns an error", func() {
r.EqualError(runControllerSync(), "error on delete")
requireCredentialIssuer(newErrorStrategy("error on delete"))
requireTLSServerWasNeverStarted()
r.Len(kubeAPIClient.Actions(), 3)
requireNodesListed(kubeAPIClient.Actions()[0])
@ -1791,6 +1928,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasDeleted(kubeAPIClient.Actions()[2]) // deleted the bad cert
requireTLSSecretWasCreated(kubeAPIClient.Actions()[3], ca)
requireTLSServerIsRunning(ca, testServerAddr(), nil)
requireCredentialIssuer(newSuccessStrategy())
})
when("there is an error while the invalid cert is being deleted", func() {
@ -1802,7 +1940,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
it("tries to delete the invalid cert, starts the impersonator without certs, and returns an error", func() {
startInformersAndController()
r.EqualError(runControllerSync(), "PEM data represented an invalid cert, but got error while deleting it: error on delete")
errString := "PEM data represented an invalid cert, but got error while deleting it: error on delete"
r.EqualError(runControllerSync(), errString)
requireCredentialIssuer(newErrorStrategy(errString))
requireTLSServerIsRunningWithoutCerts()
r.Len(kubeAPIClient.Actions(), 3)
requireNodesListed(kubeAPIClient.Actions()[0])
@ -1835,6 +1975,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasDeleted(kubeAPIClient.Actions()[1]) // deleted the bad cert
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], caCrt)
requireTLSServerIsRunning(caCrt, testServerAddr(), nil)
requireCredentialIssuer(newSuccessStrategy())
})
when("there is an error while the invalid cert is being deleted", func() {
@ -1846,7 +1987,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
it("tries to delete the invalid cert, starts the impersonator without certs, and returns an error", func() {
startInformersAndController()
r.EqualError(runControllerSync(), "found missing or not PEM-encoded data in TLS Secret, but got error while deleting it: error on delete")
errString := "found missing or not PEM-encoded data in TLS Secret, but got error while deleting it: error on delete"
r.EqualError(runControllerSync(), errString)
requireCredentialIssuer(newErrorStrategy(errString))
requireTLSServerIsRunningWithoutCerts()
r.Len(kubeAPIClient.Actions(), 2)
requireNodesListed(kubeAPIClient.Actions()[0])
@ -1880,6 +2023,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
requireTLSSecretWasDeleted(kubeAPIClient.Actions()[1]) // deleted the bad cert
requireTLSSecretWasCreated(kubeAPIClient.Actions()[2], caCrt)
requireTLSServerIsRunning(caCrt, testServerAddr(), nil)
requireCredentialIssuer(newSuccessStrategy())
})
when("there is an error while the invalid cert is being deleted", func() {
@ -1891,7 +2035,9 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
it("tries to delete the invalid cert, starts the impersonator without certs, and returns an error", func() {
startInformersAndController()
r.EqualError(runControllerSync(), "cert had an invalid private key, but got error while deleting it: error on delete")
errString := "cert had an invalid private key, but got error while deleting it: error on delete"
r.EqualError(runControllerSync(), errString)
requireCredentialIssuer(newErrorStrategy(errString))
requireTLSServerIsRunningWithoutCerts()
r.Len(kubeAPIClient.Actions(), 2)
requireNodesListed(kubeAPIClient.Actions()[0])
@ -1900,5 +2046,32 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
})
})
})
when("there is an error while creating or updating the CredentialIssuer status", func() {
it.Before(func() {
addNodeWithRoleToTracker("worker", kubeAPIClient)
pinnipedAPIClient.PrependReactor("create", "credentialissuers", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("error on create")
})
})
it("returns the error", func() {
startInformersAndController()
r.EqualError(runControllerSync(), "could not create or update credentialissuer: create failed: error on create")
})
when("there is also a more fundamental error while starting the impersonator", func() {
it.Before(func() {
kubeAPIClient.PrependReactor("create", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("error on service creation")
})
})
it("returns the more fundamental error instead of the CredentialIssuer error", func() {
startInformersAndController()
r.EqualError(runControllerSync(), "error on service creation")
})
})
})
}, spec.Parallel(), spec.Report(report.Terminal{}))
}

View File

@ -277,12 +277,14 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
singletonWorker,
).
// The impersonation proxy configuration controllers dynamically configure the impersonation proxy feature.
// The impersonator configuration controller dynamically configures the impersonation proxy feature.
WithController(
impersonatorconfig.NewImpersonatorConfigController(
c.ServerInstallationInfo.Namespace,
c.NamesConfig.ImpersonationConfigMap,
c.NamesConfig.CredentialIssuer,
client.Kubernetes,
client.PinnipedConcierge,
informers.installationNamespaceK8s.Core().V1().ConfigMaps(),
informers.installationNamespaceK8s.Core().V1().Services(),
informers.installationNamespaceK8s.Core().V1().Secrets(),
@ -292,6 +294,7 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
c.NamesConfig.ImpersonationTLSCertificateSecret,
c.NamesConfig.ImpersonationCACertificateSecret,
c.Labels,
clock.RealClock{},
tls.Listen,
func() (http.Handler, error) {
impersonationProxyHandler, err := impersonator.New(