// Copyright 2021 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package impersonatorconfig import ( "context" "crypto/tls" "crypto/x509" "encoding/base64" "encoding/pem" "fmt" "net" "strings" "time" 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/errors" "k8s.io/apimachinery/pkg/util/intstr" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" 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" "go.pinniped.dev/internal/constable" pinnipedcontroller "go.pinniped.dev/internal/controller" "go.pinniped.dev/internal/controller/apicerts" "go.pinniped.dev/internal/controller/issuerconfig" "go.pinniped.dev/internal/controllerlib" "go.pinniped.dev/internal/dynamiccert" "go.pinniped.dev/internal/plog" ) const ( impersonationProxyPort = 8444 defaultHTTPSPort = 443 approximatelyOneHundredYears = 100 * 365 * 24 * time.Hour caCommonName = "Pinniped Impersonation Proxy CA" caCrtKey = "ca.crt" caKeyKey = "ca.key" appLabelKey = "app" ) type impersonatorConfigController struct { namespace string configMapResourceName string credentialIssuerResourceName string generatedLoadBalancerServiceName string tlsSecretName string caSecretName string impersonationSignerSecretName string k8sClient kubernetes.Interface pinnipedAPIClient pinnipedclientset.Interface configMapsInformer corev1informers.ConfigMapInformer servicesInformer corev1informers.ServiceInformer secretsInformer corev1informers.SecretInformer labels map[string]string clock clock.Clock impersonationSigningCertProvider dynamiccert.Provider impersonatorFunc impersonator.FactoryFunc hasControlPlaneNodes *bool serverStopCh chan struct{} errorCh chan error tlsServingCertDynamicCertProvider dynamiccert.Private } func NewImpersonatorConfigController( namespace string, configMapResourceName string, credentialIssuerResourceName string, k8sClient kubernetes.Interface, pinnipedAPIClient pinnipedclientset.Interface, configMapsInformer corev1informers.ConfigMapInformer, servicesInformer corev1informers.ServiceInformer, secretsInformer corev1informers.SecretInformer, withInformer pinnipedcontroller.WithInformerOptionFunc, withInitialEvent pinnipedcontroller.WithInitialEventOptionFunc, generatedLoadBalancerServiceName string, tlsSecretName string, caSecretName string, labels map[string]string, clock clock.Clock, impersonatorFunc impersonator.FactoryFunc, impersonationSignerSecretName string, impersonationSigningCertProvider dynamiccert.Provider, ) controllerlib.Controller { secretNames := sets.NewString(tlsSecretName, caSecretName, impersonationSignerSecretName) return controllerlib.New( controllerlib.Config{ Name: "impersonator-config-controller", Syncer: &impersonatorConfigController{ namespace: namespace, configMapResourceName: configMapResourceName, credentialIssuerResourceName: credentialIssuerResourceName, generatedLoadBalancerServiceName: generatedLoadBalancerServiceName, tlsSecretName: tlsSecretName, caSecretName: caSecretName, impersonationSignerSecretName: impersonationSignerSecretName, k8sClient: k8sClient, pinnipedAPIClient: pinnipedAPIClient, configMapsInformer: configMapsInformer, servicesInformer: servicesInformer, secretsInformer: secretsInformer, labels: labels, clock: clock, impersonationSigningCertProvider: impersonationSigningCertProvider, impersonatorFunc: impersonatorFunc, tlsServingCertDynamicCertProvider: dynamiccert.NewServingCert("impersonation-proxy-serving-cert"), }, }, withInformer( configMapsInformer, pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(configMapResourceName, namespace), controllerlib.InformerOption{}, ), withInformer( servicesInformer, pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(generatedLoadBalancerServiceName, namespace), controllerlib.InformerOption{}, ), withInformer( secretsInformer, pinnipedcontroller.SimpleFilter(func(obj metav1.Object) bool { return obj.GetNamespace() == namespace && secretNames.Has(obj.GetName()) }, nil), controllerlib.InformerOption{}, ), // Be sure to run once even if the ConfigMap that the informer is watching doesn't exist so we can implement // the default configuration behavior. withInitialEvent(controllerlib.Key{ Namespace: namespace, Name: configMapResourceName, }), // TODO fix these controller options to make this a singleton queue ) } func (c *impersonatorConfigController) Sync(syncCtx controllerlib.Context) error { plog.Debug("Starting impersonatorConfigController Sync") strategy, err := c.doSync(syncCtx) if err != nil { strategy = &v1alpha1.CredentialIssuerStrategy{ Type: v1alpha1.ImpersonationProxyStrategyType, Status: v1alpha1.ErrorStrategyStatus, Reason: v1alpha1.ErrorDuringSetupStrategyReason, Message: err.Error(), LastUpdateTime: metav1.NewTime(c.clock.Now()), } // The impersonator is not ready, so clear the signer CA from the dynamic provider. c.clearSignerCA() } 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 } type certNameInfo struct { // ready will be true when the certificate name information is known. // ready will be false when it is pending because we are waiting for a load balancer to get assigned an ip/hostname. // When false, the other fields in this struct should not be considered meaningful and may be zero values. ready bool // The IP address or hostname which was selected to be used as the name in the cert. // Either selectedIP or selectedHostname will be set, but not both. selectedIP net.IP selectedHostname string // The name of the endpoint to which a client should connect to talk to the impersonator. // This may be a hostname or an IP, and may include a port number. clientEndpoint string } func (c *impersonatorConfigController) doSync(syncCtx controllerlib.Context) (*v1alpha1.CredentialIssuerStrategy, error) { ctx := syncCtx.Context config, err := c.loadImpersonationProxyConfiguration() if err != nil { return nil, err } // Make a live API call to avoid the cost of having an informer watch all node changes on the cluster, // since there could be lots and we don't especially care about node changes. // Once we have concluded that there is or is not a visible control plane, then cache that decision // to avoid listing nodes very often. if c.hasControlPlaneNodes == nil { hasControlPlaneNodes, err := clusterhost.New(c.k8sClient).HasControlPlaneNodes(ctx) if err != nil { return nil, err } c.hasControlPlaneNodes = &hasControlPlaneNodes plog.Debug("Queried for control plane nodes", "foundControlPlaneNodes", hasControlPlaneNodes) } if c.shouldHaveImpersonator(config) { if err = c.ensureImpersonatorIsStarted(syncCtx); err != nil { return nil, err } } else { if err = c.ensureImpersonatorIsStopped(true); err != nil { return nil, err // TODO write unit test that errors during stopping the server are returned by sync } } if c.shouldHaveLoadBalancer(config) { if err = c.ensureLoadBalancerIsStarted(ctx); err != nil { return nil, err } } else { if err = c.ensureLoadBalancerIsStopped(ctx); err != nil { return nil, err } } nameInfo, err := c.findDesiredTLSCertificateName(config) if err != nil { // Unexpected error while determining the name that should go into the certs, so clear any existing certs. c.tlsServingCertDynamicCertProvider.UnsetCertKeyContent() return nil, err } var impersonationCA *certauthority.CA if c.shouldHaveTLSSecret(config) { if impersonationCA, err = c.ensureCASecretIsCreated(ctx); err != nil { return nil, err } if err = c.ensureTLSSecret(ctx, nameInfo, impersonationCA); err != nil { return nil, err } } else if err = c.ensureTLSSecretIsRemoved(ctx); err != nil { return nil, err } credentialIssuerStrategyResult := c.doSyncResult(nameInfo, config, impersonationCA) if err = c.loadSignerCA(credentialIssuerStrategyResult.Status); err != nil { return nil, err } return credentialIssuerStrategyResult, nil } func (c *impersonatorConfigController) loadImpersonationProxyConfiguration() (*impersonator.Config, error) { configMap, err := c.configMapsInformer.Lister().ConfigMaps(c.namespace).Get(c.configMapResourceName) notFound := k8serrors.IsNotFound(err) if err != nil && !notFound { return nil, fmt.Errorf("failed to get %s/%s configmap: %w", c.namespace, c.configMapResourceName, err) } var config *impersonator.Config if notFound { plog.Info("Did not find impersonation proxy config: using default config values", "configmap", c.configMapResourceName, "namespace", c.namespace, ) config = impersonator.NewConfig() // use default configuration options } else { config, err = impersonator.ConfigFromConfigMap(configMap) if err != nil { return nil, fmt.Errorf("invalid impersonator configuration: %v", err) } plog.Info("Read impersonation proxy config", "configmap", c.configMapResourceName, "namespace", c.namespace, ) } return config, nil } func (c *impersonatorConfigController) shouldHaveImpersonator(config *impersonator.Config) bool { 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 { return c.shouldHaveImpersonator(config) && !config.HasEndpoint() } func (c *impersonatorConfigController) shouldHaveTLSSecret(config *impersonator.Config) bool { 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) if notFound { return false, nil } if err != nil { return false, err } 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) ensureImpersonatorIsStarted(syncCtx controllerlib.Context) error { if c.serverStopCh != nil { // The server was already started, but it could have died in the background, so make a non-blocking // check to see if it has sent any errors on the errorCh. select { case runningErr := <-c.errorCh: if runningErr == nil { // The server sent a nil error, meaning that it shutdown without reporting any particular // error for some reason. We would still like to report this as an error for logging purposes. runningErr = constable.Error("unexpected shutdown of proxy server") } // The server has stopped, so finish shutting it down. // If that fails too, return both errors for logging purposes. // By returning an error, the sync function will be called again // and we'll have a chance to restart the server. close(c.errorCh) // We don't want ensureImpersonatorIsStopped to block on reading this channel. stoppingErr := c.ensureImpersonatorIsStopped(false) return errors.NewAggregate([]error{runningErr, stoppingErr}) default: // Seems like it is still running, so nothing to do. return nil } } plog.Info("Starting impersonation proxy", "port", impersonationProxyPort) startImpersonatorFunc, err := c.impersonatorFunc( impersonationProxyPort, c.tlsServingCertDynamicCertProvider, c.impersonationSigningCertProvider, ) if err != nil { return err } c.serverStopCh = make(chan struct{}) // use a buffered channel so that startImpersonatorFunc can send // on it without coordinating with the main controller go routine c.errorCh = make(chan error, 1) // startImpersonatorFunc will block until the server shuts down (or fails to start), so run it in the background. go func() { defer utilruntime.HandleCrash() // The server has stopped, so enqueue ourselves for another sync, // so we can try to start the server again as quickly as possible. defer syncCtx.Queue.AddRateLimited(syncCtx.Key) // Forward any errors returned by startImpersonatorFunc on the errorCh. c.errorCh <- startImpersonatorFunc(c.serverStopCh) }() return nil } func (c *impersonatorConfigController) ensureImpersonatorIsStopped(shouldCloseErrChan bool) error { if c.serverStopCh == nil { return nil } plog.Info("Stopping impersonation proxy", "port", impersonationProxyPort) close(c.serverStopCh) stopErr := <-c.errorCh if shouldCloseErrChan { close(c.errorCh) } c.serverStopCh = nil c.errorCh = nil return stopErr } func (c *impersonatorConfigController) ensureLoadBalancerIsStarted(ctx context.Context) error { running, err := c.loadBalancerExists() if err != nil { return err } if running { return nil } appNameLabel := c.labels[appLabelKey] loadBalancer := v1.Service{ Spec: v1.ServiceSpec{ Type: v1.ServiceTypeLoadBalancer, Ports: []v1.ServicePort{ { TargetPort: intstr.FromInt(impersonationProxyPort), Port: defaultHTTPSPort, Protocol: v1.ProtocolTCP, }, }, Selector: map[string]string{appLabelKey: appNameLabel}, }, ObjectMeta: metav1.ObjectMeta{ Name: c.generatedLoadBalancerServiceName, Namespace: c.namespace, Labels: c.labels, }, } plog.Info("creating load balancer for impersonation proxy", "service", c.generatedLoadBalancerServiceName, "namespace", c.namespace) _, err = c.k8sClient.CoreV1().Services(c.namespace).Create(ctx, &loadBalancer, metav1.CreateOptions{}) return err } func (c *impersonatorConfigController) ensureLoadBalancerIsStopped(ctx context.Context) error { running, err := c.loadBalancerExists() if err != nil { return err } if !running { return nil } plog.Info("Deleting load balancer for impersonation proxy", "service", c.generatedLoadBalancerServiceName, "namespace", c.namespace) return c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedLoadBalancerServiceName, metav1.DeleteOptions{}) } func (c *impersonatorConfigController) ensureTLSSecret(ctx context.Context, nameInfo *certNameInfo, ca *certauthority.CA) error { secretFromInformer, err := c.secretsInformer.Lister().Secrets(c.namespace).Get(c.tlsSecretName) notFound := k8serrors.IsNotFound(err) if !notFound && err != nil { return err } if !notFound { secretWasDeleted, err := c.deleteTLSSecretWhenCertificateDoesNotMatchDesiredState(ctx, nameInfo, ca, secretFromInformer) if err != nil { return 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. if secretWasDeleted { secretFromInformer = nil } } return c.ensureTLSSecretIsCreatedAndLoaded(ctx, nameInfo, secretFromInformer, ca) } func (c *impersonatorConfigController) deleteTLSSecretWhenCertificateDoesNotMatchDesiredState(ctx context.Context, nameInfo *certNameInfo, ca *certauthority.CA, secret *v1.Secret) (bool, error) { certPEM := secret.Data[v1.TLSCertKey] block, _ := pem.Decode(certPEM) if block == nil { plog.Warning("Found missing or not PEM-encoded data in TLS Secret", "invalidCertPEM", string(certPEM), "secret", c.tlsSecretName, "namespace", c.namespace) deleteErr := c.ensureTLSSecretIsRemoved(ctx) if deleteErr != nil { return false, fmt.Errorf("found missing or not PEM-encoded data in TLS Secret, but got error while deleting it: %w", deleteErr) } return true, nil } actualCertFromSecret, err := x509.ParseCertificate(block.Bytes) if err != nil { plog.Error("Found invalid PEM data in TLS Secret", err, "invalidCertPEM", string(certPEM), "secret", c.tlsSecretName, "namespace", c.namespace) if err = c.ensureTLSSecretIsRemoved(ctx); err != nil { return false, fmt.Errorf("PEM data represented an invalid cert, but got error while deleting it: %w", err) } return true, nil } keyPEM := secret.Data[v1.TLSPrivateKeyKey] _, err = tls.X509KeyPair(certPEM, keyPEM) if err != nil { plog.Error("Found invalid private key PEM data in TLS Secret", err, "secret", c.tlsSecretName, "namespace", c.namespace) if err = c.ensureTLSSecretIsRemoved(ctx); err != nil { return false, fmt.Errorf("cert had an invalid private key, but got error while deleting it: %w", err) } return true, nil } opts := x509.VerifyOptions{Roots: ca.Pool()} if _, err = actualCertFromSecret.Verify(opts); err != nil { // The TLS cert was not signed by the current CA. Since they are mismatched, delete the TLS cert // so we can recreate it using the current CA. if err = c.ensureTLSSecretIsRemoved(ctx); err != nil { return false, err } return true, nil } if !nameInfo.ready { // We currently have a secret but we are waiting for a load balancer to be assigned an ingress, so // our current secret must be old/unwanted. if err = c.ensureTLSSecretIsRemoved(ctx); err != nil { return false, err } return true, nil } actualIPs := actualCertFromSecret.IPAddresses actualHostnames := actualCertFromSecret.DNSNames plog.Info("Checking TLS certificate names", "desiredIP", nameInfo.selectedIP, "desiredHostname", nameInfo.selectedHostname, "actualIPs", actualIPs, "actualHostnames", actualHostnames, "secret", c.tlsSecretName, "namespace", c.namespace) if certHostnameAndIPMatchDesiredState(nameInfo.selectedIP, actualIPs, nameInfo.selectedHostname, actualHostnames) { // The cert already matches the desired state, so there is no need to delete/recreate it. return false, nil } if err = c.ensureTLSSecretIsRemoved(ctx); err != nil { return false, err } return true, nil } func certHostnameAndIPMatchDesiredState(desiredIP net.IP, actualIPs []net.IP, desiredHostname string, actualHostnames []string) bool { if desiredIP != nil && len(actualIPs) == 1 && desiredIP.Equal(actualIPs[0]) && len(actualHostnames) == 0 { return true } if desiredHostname != "" && len(actualHostnames) == 1 && desiredHostname == actualHostnames[0] && len(actualIPs) == 0 { return true } return false } func (c *impersonatorConfigController) ensureTLSSecretIsCreatedAndLoaded(ctx context.Context, nameInfo *certNameInfo, secret *v1.Secret, ca *certauthority.CA) error { if secret != nil { err := c.loadTLSCertFromSecret(secret) if err != nil { return err } return nil } if !nameInfo.ready { return nil } newTLSSecret, err := c.createNewTLSSecret(ctx, ca, nameInfo.selectedIP, nameInfo.selectedHostname) if err != nil { return err } err = c.loadTLSCertFromSecret(newTLSSecret) if err != nil { return err } return nil } func (c *impersonatorConfigController) ensureCASecretIsCreated(ctx context.Context) (*certauthority.CA, error) { caSecret, err := c.secretsInformer.Lister().Secrets(c.namespace).Get(c.caSecretName) if err != nil && !k8serrors.IsNotFound(err) { return nil, err } var impersonationCA *certauthority.CA if k8serrors.IsNotFound(err) { impersonationCA, err = c.createCASecret(ctx) } else { crtBytes := caSecret.Data[caCrtKey] keyBytes := caSecret.Data[caKeyKey] impersonationCA, err = certauthority.Load(string(crtBytes), string(keyBytes)) } if err != nil { return nil, err } return impersonationCA, nil } func (c *impersonatorConfigController) createCASecret(ctx context.Context) (*certauthority.CA, error) { impersonationCA, err := certauthority.New(caCommonName, approximatelyOneHundredYears) if err != nil { return nil, fmt.Errorf("could not create impersonation CA: %w", err) } caPrivateKeyPEM, err := impersonationCA.PrivateKeyToPEM() if err != nil { return nil, err } secret := v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: c.caSecretName, Namespace: c.namespace, Labels: c.labels, }, Data: map[string][]byte{ caCrtKey: impersonationCA.Bundle(), caKeyKey: caPrivateKeyPEM, }, Type: v1.SecretTypeOpaque, } plog.Info("Creating CA certificates for impersonation proxy", "secret", c.caSecretName, "namespace", c.namespace) if _, err = c.k8sClient.CoreV1().Secrets(c.namespace).Create(ctx, &secret, metav1.CreateOptions{}); err != nil { return nil, err } return impersonationCA, nil } func (c *impersonatorConfigController) findDesiredTLSCertificateName(config *impersonator.Config) (*certNameInfo, error) { if config.HasEndpoint() { return c.findTLSCertificateNameFromEndpointConfig(config), nil } return c.findTLSCertificateNameFromLoadBalancer() } func (c *impersonatorConfigController) findTLSCertificateNameFromEndpointConfig(config *impersonator.Config) *certNameInfo { endpointMaybeWithPort := config.Endpoint endpointWithoutPort := strings.Split(endpointMaybeWithPort, ":")[0] parsedAsIP := net.ParseIP(endpointWithoutPort) if parsedAsIP != nil { return &certNameInfo{ready: true, selectedIP: parsedAsIP, clientEndpoint: endpointMaybeWithPort} } return &certNameInfo{ready: true, selectedHostname: endpointWithoutPort, clientEndpoint: endpointMaybeWithPort} } func (c *impersonatorConfigController) findTLSCertificateNameFromLoadBalancer() (*certNameInfo, error) { lb, err := c.servicesInformer.Lister().Services(c.namespace).Get(c.generatedLoadBalancerServiceName) notFound := k8serrors.IsNotFound(err) if notFound { // We aren't ready and will try again later in this case. return &certNameInfo{ready: false}, nil } if err != nil { return nil, err } ingresses := lb.Status.LoadBalancer.Ingress if len(ingresses) == 0 || (ingresses[0].Hostname == "" && ingresses[0].IP == "") { plog.Info("load balancer for impersonation proxy does not have an ingress yet, so skipping tls cert generation while we wait", "service", c.generatedLoadBalancerServiceName, "namespace", c.namespace) return &certNameInfo{ready: false}, nil } for _, ingress := range ingresses { hostname := ingress.Hostname if hostname != "" { return &certNameInfo{ready: true, selectedHostname: hostname, clientEndpoint: hostname}, nil } } for _, ingress := range ingresses { ip := ingress.IP parsedIP := net.ParseIP(ip) if parsedIP != nil { return &certNameInfo{ready: true, selectedIP: parsedIP, clientEndpoint: ip}, nil } } return nil, fmt.Errorf("could not find valid IP addresses or hostnames from load balancer %s/%s", c.namespace, lb.Name) } func (c *impersonatorConfigController) createNewTLSSecret(ctx context.Context, ca *certauthority.CA, ip net.IP, hostname string) (*v1.Secret, error) { var hostnames []string var ips []net.IP if hostname != "" { hostnames = []string{hostname} } if ip != nil { ips = []net.IP{ip} } impersonationCert, err := ca.IssueServerCert(hostnames, ips, approximatelyOneHundredYears) if err != nil { return nil, fmt.Errorf("could not create impersonation cert: %w", err) } certPEM, keyPEM, err := certauthority.ToPEM(impersonationCert) if err != nil { return nil, err } newTLSSecret := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: c.tlsSecretName, Namespace: c.namespace, Labels: c.labels, }, Data: map[string][]byte{ v1.TLSPrivateKeyKey: keyPEM, v1.TLSCertKey: certPEM, }, Type: v1.SecretTypeTLS, } plog.Info("Creating TLS certificates for impersonation proxy", "ips", ips, "hostnames", hostnames, "secret", c.tlsSecretName, "namespace", c.namespace) return c.k8sClient.CoreV1().Secrets(c.namespace).Create(ctx, newTLSSecret, metav1.CreateOptions{}) } func (c *impersonatorConfigController) loadTLSCertFromSecret(tlsSecret *v1.Secret) error { certPEM := tlsSecret.Data[v1.TLSCertKey] keyPEM := tlsSecret.Data[v1.TLSPrivateKeyKey] if err := c.tlsServingCertDynamicCertProvider.SetCertKeyContent(certPEM, keyPEM); err != nil { c.tlsServingCertDynamicCertProvider.UnsetCertKeyContent() return fmt.Errorf("could not parse TLS cert PEM data from Secret: %w", err) } plog.Info("Loading TLS certificates for impersonation proxy", "certPEM", string(certPEM), "secret", c.tlsSecretName, "namespace", c.namespace) 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", "secret", c.tlsSecretName, "namespace", c.namespace) err = c.k8sClient.CoreV1().Secrets(c.namespace).Delete(ctx, c.tlsSecretName, metav1.DeleteOptions{}) if err != nil { return err } c.tlsServingCertDynamicCertProvider.UnsetCertKeyContent() return nil } func (c *impersonatorConfigController) loadSignerCA(status v1alpha1.StrategyStatus) error { // Clear it when the impersonator is not completely ready. if status != v1alpha1.SuccessStrategyStatus { c.clearSignerCA() return nil } signingCertSecret, err := c.secretsInformer.Lister().Secrets(c.namespace).Get(c.impersonationSignerSecretName) if err != nil { return fmt.Errorf("could not load the impersonator's credential signing secret: %w", err) } certPEM := signingCertSecret.Data[apicerts.CACertificateSecretKey] keyPEM := signingCertSecret.Data[apicerts.CACertificatePrivateKeySecretKey] if err := c.impersonationSigningCertProvider.SetCertKeyContent(certPEM, keyPEM); err != nil { return fmt.Errorf("could not load the impersonator's credential signing secret: %w", err) } plog.Info("Loading credential signing certificate for impersonation proxy", "certPEM", string(certPEM), "fromSecret", c.impersonationSignerSecretName, "namespace", c.namespace) return nil } func (c *impersonatorConfigController) clearSignerCA() { plog.Info("Clearing credential signing certificate for impersonation proxy") c.impersonationSigningCertProvider.UnsetCertKeyContent() } func (c *impersonatorConfigController) doSyncResult(nameInfo *certNameInfo, config *impersonator.Config, ca *certauthority.CA) *v1alpha1.CredentialIssuerStrategy { switch { 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()), } case !nameInfo.ready: return &v1alpha1.CredentialIssuerStrategy{ Type: v1alpha1.ImpersonationProxyStrategyType, Status: v1alpha1.ErrorStrategyStatus, Reason: v1alpha1.PendingStrategyReason, Message: "waiting for load balancer Service to be assigned IP or hostname", 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()), Frontend: &v1alpha1.CredentialIssuerFrontend{ Type: v1alpha1.ImpersonationProxyFrontendType, ImpersonationProxyInfo: &v1alpha1.ImpersonationProxyInfo{ Endpoint: "https://" + nameInfo.clientEndpoint, CertificateAuthorityData: base64.StdEncoding.EncodeToString(ca.Bundle()), }, }, } } }