2021-02-12 01:22:47 +00:00
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package impersonatorconfig
import (
2021-02-16 23:57:02 +00:00
"context"
2021-02-12 01:22:47 +00:00
"crypto/tls"
2021-02-25 00:03:17 +00:00
"crypto/x509"
2021-02-12 01:22:47 +00:00
"crypto/x509/pkix"
2021-03-03 00:51:35 +00:00
"encoding/base64"
2021-02-25 00:03:17 +00:00
"encoding/pem"
2021-02-12 01:22:47 +00:00
"fmt"
"net"
2021-02-26 23:01:38 +00:00
"strings"
2021-02-12 01:22:47 +00:00
"time"
2021-02-16 23:57:02 +00:00
v1 "k8s.io/api/core/v1"
2021-02-12 01:22:47 +00:00
k8serrors "k8s.io/apimachinery/pkg/api/errors"
2021-02-16 23:57:02 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2021-03-02 22:48:58 +00:00
"k8s.io/apimachinery/pkg/util/clock"
2021-03-10 18:30:06 +00:00
"k8s.io/apimachinery/pkg/util/errors"
2021-02-16 23:57:02 +00:00
"k8s.io/apimachinery/pkg/util/intstr"
2021-03-10 18:30:06 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2021-02-12 01:22:47 +00:00
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
2021-03-02 22:48:58 +00:00
"go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
pinnipedclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
2021-02-12 01:22:47 +00:00
"go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/clusterhost"
"go.pinniped.dev/internal/concierge/impersonator"
2021-03-10 18:30:06 +00:00
"go.pinniped.dev/internal/constable"
2021-02-12 01:22:47 +00:00
pinnipedcontroller "go.pinniped.dev/internal/controller"
2021-03-10 18:30:06 +00:00
"go.pinniped.dev/internal/controller/apicerts"
2021-03-02 22:48:58 +00:00
"go.pinniped.dev/internal/controller/issuerconfig"
2021-02-12 01:22:47 +00:00
"go.pinniped.dev/internal/controllerlib"
2021-03-10 18:30:06 +00:00
"go.pinniped.dev/internal/dynamiccert"
2021-02-12 01:22:47 +00:00
"go.pinniped.dev/internal/plog"
)
const (
2021-03-10 18:30:06 +00:00
impersonationProxyPort = 8444
2021-03-02 01:02:08 +00:00
defaultHTTPSPort = 443
2021-03-08 19:31:16 +00:00
oneHundredYears = 100 * 365 * 24 * time . Hour
2021-03-02 01:02:08 +00:00
caCommonName = "Pinniped Impersonation Proxy CA"
caCrtKey = "ca.crt"
caKeyKey = "ca.key"
appLabelKey = "app"
2021-02-12 01:22:47 +00:00
)
type impersonatorConfigController struct {
namespace string
configMapResourceName string
2021-03-02 22:48:58 +00:00
credentialIssuerResourceName string
2021-02-12 01:22:47 +00:00
generatedLoadBalancerServiceName string
2021-02-24 18:56:24 +00:00
tlsSecretName string
2021-03-02 01:02:08 +00:00
caSecretName string
2021-03-10 18:30:06 +00:00
impersonationSignerSecretName string
2021-03-02 22:48:58 +00:00
k8sClient kubernetes . Interface
pinnipedAPIClient pinnipedclientset . Interface
configMapsInformer corev1informers . ConfigMapInformer
servicesInformer corev1informers . ServiceInformer
secretsInformer corev1informers . SecretInformer
2021-03-10 18:30:06 +00:00
labels map [ string ] string
clock clock . Clock
impersonationSigningCertProvider dynamiccert . Provider
impersonatorFunc impersonator . FactoryFunc
2021-02-12 01:22:47 +00:00
2021-03-10 18:30:06 +00:00
hasControlPlaneNodes * bool
serverStopCh chan struct { }
errorCh chan error
tlsServingCertDynamicCertProvider dynamiccert . Provider
2021-02-12 01:22:47 +00:00
}
func NewImpersonatorConfigController (
namespace string ,
configMapResourceName string ,
2021-03-02 22:48:58 +00:00
credentialIssuerResourceName string ,
2021-02-12 01:22:47 +00:00
k8sClient kubernetes . Interface ,
2021-03-02 22:48:58 +00:00
pinnipedAPIClient pinnipedclientset . Interface ,
2021-02-12 01:22:47 +00:00
configMapsInformer corev1informers . ConfigMapInformer ,
2021-02-18 01:22:13 +00:00
servicesInformer corev1informers . ServiceInformer ,
2021-02-24 18:56:24 +00:00
secretsInformer corev1informers . SecretInformer ,
2021-02-12 01:22:47 +00:00
withInformer pinnipedcontroller . WithInformerOptionFunc ,
withInitialEvent pinnipedcontroller . WithInitialEventOptionFunc ,
generatedLoadBalancerServiceName string ,
2021-02-24 18:56:24 +00:00
tlsSecretName string ,
2021-03-02 01:02:08 +00:00
caSecretName string ,
2021-02-16 23:57:02 +00:00
labels map [ string ] string ,
2021-03-02 22:48:58 +00:00
clock clock . Clock ,
2021-03-10 18:30:06 +00:00
impersonatorFunc impersonator . FactoryFunc ,
impersonationSignerSecretName string ,
impersonationSigningCertProvider dynamiccert . Provider ,
2021-02-12 01:22:47 +00:00
) controllerlib . Controller {
2021-03-10 18:30:06 +00:00
secretNames := sets . NewString ( tlsSecretName , caSecretName , impersonationSignerSecretName )
2021-02-12 01:22:47 +00:00
return controllerlib . New (
controllerlib . Config {
Name : "impersonator-config-controller" ,
Syncer : & impersonatorConfigController {
2021-03-10 18:30:06 +00:00
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 . New ( ) ,
2021-02-12 01:22:47 +00:00
} ,
} ,
withInformer (
configMapsInformer ,
pinnipedcontroller . NameAndNamespaceExactMatchFilterFactory ( configMapResourceName , namespace ) ,
controllerlib . InformerOption { } ,
) ,
2021-02-18 01:22:13 +00:00
withInformer (
servicesInformer ,
pinnipedcontroller . NameAndNamespaceExactMatchFilterFactory ( generatedLoadBalancerServiceName , namespace ) ,
controllerlib . InformerOption { } ,
) ,
2021-02-24 18:56:24 +00:00
withInformer (
secretsInformer ,
2021-03-02 01:02:08 +00:00
pinnipedcontroller . SimpleFilter ( func ( obj metav1 . Object ) bool {
2021-03-10 18:30:06 +00:00
return obj . GetNamespace ( ) == namespace && secretNames . Has ( obj . GetName ( ) )
2021-03-02 01:02:08 +00:00
} , nil ) ,
2021-02-24 18:56:24 +00:00
controllerlib . InformerOption { } ,
) ,
2021-03-02 01:02:08 +00:00
// 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.
2021-02-12 01:22:47 +00:00
withInitialEvent ( controllerlib . Key {
Namespace : namespace ,
Name : configMapResourceName ,
} ) ,
2021-03-10 18:30:06 +00:00
// TODO fix these controller options to make this a singleton queue
2021-02-12 01:22:47 +00:00
)
}
2021-03-02 01:02:08 +00:00
func ( c * impersonatorConfigController ) Sync ( syncCtx controllerlib . Context ) error {
2021-02-18 23:58:27 +00:00
plog . Debug ( "Starting impersonatorConfigController Sync" )
2021-02-12 01:22:47 +00:00
2021-03-10 18:30:06 +00:00
strategy , err := c . doSync ( syncCtx )
2021-03-02 22:48:58 +00:00
if err != nil {
strategy = & v1alpha1 . CredentialIssuerStrategy {
Type : v1alpha1 . ImpersonationProxyStrategyType ,
Status : v1alpha1 . ErrorStrategyStatus ,
2021-03-02 23:27:54 +00:00
Reason : v1alpha1 . ErrorDuringSetupStrategyReason ,
2021-03-02 22:48:58 +00:00
Message : err . Error ( ) ,
LastUpdateTime : metav1 . NewTime ( c . clock . Now ( ) ) ,
}
2021-03-10 18:30:06 +00:00
// The impersonator is not ready, so clear the signer CA from the dynamic provider.
c . clearSignerCA ( )
2021-03-02 22:48:58 +00:00
}
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
}
2021-03-03 17:22:35 +00:00
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
}
2021-03-10 18:30:06 +00:00
func ( c * impersonatorConfigController ) doSync ( syncCtx controllerlib . Context ) ( * v1alpha1 . CredentialIssuerStrategy , error ) {
ctx := syncCtx . Context
2021-03-02 01:02:08 +00:00
config , err := c . loadImpersonationProxyConfiguration ( )
if err != nil {
2021-03-02 22:48:58 +00:00
return nil , err
2021-02-12 01:22:47 +00:00
}
// 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 {
2021-03-02 01:02:08 +00:00
hasControlPlaneNodes , err := clusterhost . New ( c . k8sClient ) . HasControlPlaneNodes ( ctx )
2021-02-12 01:22:47 +00:00
if err != nil {
2021-03-02 22:48:58 +00:00
return nil , err
2021-02-12 01:22:47 +00:00
}
c . hasControlPlaneNodes = & hasControlPlaneNodes
plog . Debug ( "Queried for control plane nodes" , "foundControlPlaneNodes" , hasControlPlaneNodes )
}
2021-02-18 01:22:13 +00:00
if c . shouldHaveImpersonator ( config ) {
2021-03-10 18:30:06 +00:00
if err = c . ensureImpersonatorIsStarted ( syncCtx ) ; err != nil {
2021-03-02 22:48:58 +00:00
return nil , err
2021-02-12 01:22:47 +00:00
}
} else {
2021-03-10 18:30:06 +00:00
if err = c . ensureImpersonatorIsStopped ( true ) ; err != nil {
return nil , err // TODO write unit test that errors during stopping the server are returned by sync
2021-02-12 01:22:47 +00:00
}
}
2021-02-18 01:22:13 +00:00
if c . shouldHaveLoadBalancer ( config ) {
2021-03-02 01:02:08 +00:00
if err = c . ensureLoadBalancerIsStarted ( ctx ) ; err != nil {
2021-03-02 22:48:58 +00:00
return nil , err
2021-02-16 23:57:02 +00:00
}
} else {
2021-03-02 01:02:08 +00:00
if err = c . ensureLoadBalancerIsStopped ( ctx ) ; err != nil {
2021-03-02 22:48:58 +00:00
return nil , err
2021-02-16 23:57:02 +00:00
}
}
2021-02-12 01:22:47 +00:00
2021-03-03 17:22:35 +00:00
nameInfo , err := c . findDesiredTLSCertificateName ( config )
if err != nil {
2021-03-10 18:30:06 +00:00
// Unexpected error while determining the name that should go into the certs, so clear any existing certs.
c . tlsServingCertDynamicCertProvider . Set ( nil , nil )
2021-03-03 17:22:35 +00:00
return nil , err
}
2021-03-03 00:51:35 +00:00
var impersonationCA * certauthority . CA
2021-02-24 18:56:24 +00:00
if c . shouldHaveTLSSecret ( config ) {
2021-03-02 01:02:08 +00:00
if impersonationCA , err = c . ensureCASecretIsCreated ( ctx ) ; err != nil {
2021-03-02 22:48:58 +00:00
return nil , err
2021-02-24 18:56:24 +00:00
}
2021-03-03 17:22:35 +00:00
if err = c . ensureTLSSecret ( ctx , nameInfo , impersonationCA ) ; err != nil {
2021-03-02 22:48:58 +00:00
return nil , err
2021-02-24 18:56:24 +00:00
}
2021-03-02 01:02:08 +00:00
} else if err = c . ensureTLSSecretIsRemoved ( ctx ) ; err != nil {
2021-03-02 22:48:58 +00:00
return nil , err
2021-02-24 18:56:24 +00:00
}
2021-03-10 18:30:06 +00:00
credentialIssuerStrategyResult := c . doSyncResult ( nameInfo , config , impersonationCA )
if err = c . loadSignerCA ( credentialIssuerStrategyResult . Status ) ; err != nil {
return nil , err
}
return credentialIssuerStrategyResult , nil
2021-02-12 01:22:47 +00:00
}
2021-03-02 01:02:08 +00:00
func ( c * impersonatorConfigController ) loadImpersonationProxyConfiguration ( ) ( * impersonator . Config , error ) {
configMap , err := c . configMapsInformer . Lister ( ) . ConfigMaps ( c . namespace ) . Get ( c . configMapResourceName )
2021-02-25 00:03:17 +00:00
notFound := k8serrors . IsNotFound ( err )
2021-03-02 01:02:08 +00:00
if err != nil && ! notFound {
return nil , fmt . Errorf ( "failed to get %s/%s configmap: %w" , c . namespace , c . configMapResourceName , err )
2021-02-25 00:03:17 +00:00
}
2021-03-02 01:02:08 +00:00
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 ,
)
2021-02-25 00:03:17 +00:00
}
2021-03-02 01:02:08 +00:00
return config , nil
2021-02-24 18:56:24 +00:00
}
2021-02-18 01:22:13 +00:00
func ( c * impersonatorConfigController ) shouldHaveImpersonator ( config * impersonator . Config ) bool {
2021-03-02 22:48:58 +00:00
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
2021-02-18 01:22:13 +00:00
}
func ( c * impersonatorConfigController ) shouldHaveLoadBalancer ( config * impersonator . Config ) bool {
2021-03-03 17:22:35 +00:00
return c . shouldHaveImpersonator ( config ) && ! config . HasEndpoint ( )
2021-02-18 01:22:13 +00:00
}
2021-02-25 00:03:17 +00:00
func ( c * impersonatorConfigController ) shouldHaveTLSSecret ( config * impersonator . Config ) bool {
2021-02-26 01:03:34 +00:00
return c . shouldHaveImpersonator ( config )
}
2021-03-02 22:48:58 +00:00
func ( c * impersonatorConfigController ) updateStrategy ( ctx context . Context , strategy * v1alpha1 . CredentialIssuerStrategy ) error {
return issuerconfig . UpdateStrategy ( ctx , c . credentialIssuerResourceName , c . labels , c . pinnipedAPIClient , * strategy )
}
2021-03-02 01:02:08 +00:00
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
2021-02-26 01:03:34 +00:00
}
2021-03-02 01:02:08 +00:00
if err != nil {
return false , err
2021-02-26 01:03:34 +00:00
}
2021-03-02 01:02:08 +00:00
return true , nil
2021-02-25 00:03:17 +00:00
}
2021-03-02 01:02:08 +00:00
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
2021-02-12 01:22:47 +00:00
}
2021-03-02 01:02:08 +00:00
if err != nil {
return false , nil , err
}
return true , secret , nil
2021-02-12 01:22:47 +00:00
}
2021-03-10 18:30:06 +00:00
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
2021-03-10 19:24:42 +00:00
// and we'll have a chance to restart the server.
2021-03-10 18:30:06 +00:00
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
}
2021-02-12 01:22:47 +00:00
}
2021-03-10 18:30:06 +00:00
plog . Info ( "Starting impersonation proxy" , "port" , impersonationProxyPort )
startImpersonatorFunc , err := c . impersonatorFunc (
impersonationProxyPort ,
c . tlsServingCertDynamicCertProvider ,
dynamiccert . NewCAProvider ( c . impersonationSigningCertProvider ) ,
)
2021-02-12 01:22:47 +00:00
if err != nil {
return err
}
2021-03-10 18:30:06 +00:00
c . serverStopCh = make ( chan struct { } )
c . errorCh = make ( chan error )
2021-02-12 01:22:47 +00:00
2021-03-10 18:30:06 +00:00
// startImpersonatorFunc will block until the server shuts down (or fails to start), so run it in the background.
2021-02-12 01:22:47 +00:00
go func ( ) {
2021-03-10 18:30:06 +00:00
startOrStopErr := startImpersonatorFunc ( c . serverStopCh )
// The server has stopped, so enqueue ourselves for another sync, so we can
// try to start the server again as quickly as possible.
syncCtx . Queue . AddRateLimited ( syncCtx . Key ) // TODO this a race because the main controller go routine could run and complete before we send on the err chan
// Forward any errors returned by startImpersonatorFunc on the errorCh.
c . errorCh <- startOrStopErr
2021-02-12 01:22:47 +00:00
} ( )
2021-03-10 18:30:06 +00:00
2021-02-12 01:22:47 +00:00
return nil
}
2021-02-16 23:57:02 +00:00
2021-03-10 18:30:06 +00:00
func ( c * impersonatorConfigController ) ensureImpersonatorIsStopped ( shouldCloseErrChan bool ) error {
if c . serverStopCh == nil {
return nil
2021-02-24 18:56:24 +00:00
}
2021-03-10 18:30:06 +00:00
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
2021-02-24 18:56:24 +00:00
}
2021-02-18 01:22:13 +00:00
func ( c * impersonatorConfigController ) ensureLoadBalancerIsStarted ( ctx context . Context ) error {
2021-03-02 01:02:08 +00:00
running , err := c . loadBalancerExists ( )
2021-02-18 01:22:13 +00:00
if err != nil {
return err
}
if running {
return nil
}
2021-03-02 01:02:08 +00:00
appNameLabel := c . labels [ appLabelKey ]
2021-02-16 23:57:02 +00:00
loadBalancer := v1 . Service {
Spec : v1 . ServiceSpec {
2021-03-02 01:02:08 +00:00
Type : v1 . ServiceTypeLoadBalancer ,
2021-02-16 23:57:02 +00:00
Ports : [ ] v1 . ServicePort {
{
2021-03-10 18:30:06 +00:00
TargetPort : intstr . FromInt ( impersonationProxyPort ) ,
2021-03-02 01:02:08 +00:00
Port : defaultHTTPSPort ,
2021-02-16 23:57:02 +00:00
Protocol : v1 . ProtocolTCP ,
} ,
} ,
2021-03-02 01:02:08 +00:00
Selector : map [ string ] string { appLabelKey : appNameLabel } ,
2021-02-16 23:57:02 +00:00
} ,
ObjectMeta : metav1 . ObjectMeta {
Name : c . generatedLoadBalancerServiceName ,
Namespace : c . namespace ,
Labels : c . labels ,
} ,
}
2021-02-18 01:22:13 +00:00
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 { } )
2021-03-02 01:02:08 +00:00
return err
2021-02-16 23:57:02 +00:00
}
2021-02-24 18:56:24 +00:00
2021-02-25 00:03:17 +00:00
func ( c * impersonatorConfigController ) ensureLoadBalancerIsStopped ( ctx context . Context ) error {
2021-03-02 01:02:08 +00:00
running , err := c . loadBalancerExists ( )
2021-02-24 18:56:24 +00:00
if err != nil {
return err
}
2021-02-25 00:03:17 +00:00
if ! running {
return nil
}
plog . Info ( "Deleting load balancer for impersonation proxy" ,
"service" , c . generatedLoadBalancerServiceName ,
"namespace" , c . namespace )
2021-03-02 01:02:08 +00:00
return c . k8sClient . CoreV1 ( ) . Services ( c . namespace ) . Delete ( ctx , c . generatedLoadBalancerServiceName , metav1 . DeleteOptions { } )
}
2021-03-03 17:22:35 +00:00
func ( c * impersonatorConfigController ) ensureTLSSecret ( ctx context . Context , nameInfo * certNameInfo , ca * certauthority . CA ) error {
2021-03-02 01:02:08 +00:00
secretFromInformer , err := c . secretsInformer . Lister ( ) . Secrets ( c . namespace ) . Get ( c . tlsSecretName )
notFound := k8serrors . IsNotFound ( err )
if ! notFound && err != nil {
2021-03-03 17:22:35 +00:00
return err
2021-02-25 00:03:17 +00:00
}
2021-03-02 01:02:08 +00:00
if ! notFound {
2021-03-03 17:22:35 +00:00
secretWasDeleted , err := c . deleteTLSSecretWhenCertificateDoesNotMatchDesiredState ( ctx , nameInfo , ca , secretFromInformer )
2021-03-02 01:02:08 +00:00
if err != nil {
2021-03-03 17:22:35 +00:00
return err
2021-03-02 01:02:08 +00:00
}
// 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
}
2021-02-25 00:03:17 +00:00
}
2021-03-03 17:22:35 +00:00
return c . ensureTLSSecretIsCreatedAndLoaded ( ctx , nameInfo , secretFromInformer , ca )
2021-03-02 01:02:08 +00:00
}
2021-03-03 17:22:35 +00:00
func ( c * impersonatorConfigController ) deleteTLSSecretWhenCertificateDoesNotMatchDesiredState ( ctx context . Context , nameInfo * certNameInfo , ca * certauthority . CA , secret * v1 . Secret ) ( bool , error ) {
2021-02-25 00:03:17 +00:00
certPEM := secret . Data [ v1 . TLSCertKey ]
block , _ := pem . Decode ( certPEM )
if block == nil {
2021-02-26 18:58:56 +00:00
plog . Warning ( "Found missing or not PEM-encoded data in TLS Secret" ,
2021-03-02 01:02:08 +00:00
"invalidCertPEM" , string ( certPEM ) ,
2021-02-26 18:58:56 +00:00
"secret" , c . tlsSecretName ,
"namespace" , c . namespace )
deleteErr := c . ensureTLSSecretIsRemoved ( ctx )
if deleteErr != nil {
2021-03-02 01:02:08 +00:00
return false , fmt . Errorf ( "found missing or not PEM-encoded data in TLS Secret, but got error while deleting it: %w" , deleteErr )
2021-02-26 18:58:56 +00:00
}
2021-03-02 01:02:08 +00:00
return true , nil
2021-02-26 18:58:56 +00:00
}
actualCertFromSecret , err := x509 . ParseCertificate ( block . Bytes )
if err != nil {
plog . Error ( "Found invalid PEM data in TLS Secret" , err ,
2021-03-02 01:02:08 +00:00
"invalidCertPEM" , string ( certPEM ) ,
2021-02-26 18:58:56 +00:00
"secret" , c . tlsSecretName ,
"namespace" , c . namespace )
2021-03-02 01:02:08 +00:00
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 )
2021-02-26 18:58:56 +00:00
}
2021-03-02 01:02:08 +00:00
return true , nil
2021-02-26 18:58:56 +00:00
}
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 )
2021-03-02 01:02:08 +00:00
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 )
2021-02-26 18:58:56 +00:00
}
2021-03-02 01:02:08 +00:00
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
2021-02-25 00:03:17 +00:00
}
2021-03-03 17:22:35 +00:00
if ! nameInfo . ready {
2021-02-25 00:03:17 +00:00
// 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.
2021-03-02 01:02:08 +00:00
if err = c . ensureTLSSecretIsRemoved ( ctx ) ; err != nil {
return false , err
2021-02-25 00:03:17 +00:00
}
2021-03-02 01:02:08 +00:00
return true , nil
2021-02-25 00:03:17 +00:00
}
2021-02-25 18:27:19 +00:00
actualIPs := actualCertFromSecret . IPAddresses
actualHostnames := actualCertFromSecret . DNSNames
plog . Info ( "Checking TLS certificate names" ,
2021-03-03 17:22:35 +00:00
"desiredIP" , nameInfo . selectedIP ,
"desiredHostname" , nameInfo . selectedHostname ,
2021-02-25 18:27:19 +00:00
"actualIPs" , actualIPs ,
"actualHostnames" , actualHostnames ,
"secret" , c . tlsSecretName ,
"namespace" , c . namespace )
2021-03-03 17:22:35 +00:00
if certHostnameAndIPMatchDesiredState ( nameInfo . selectedIP , actualIPs , nameInfo . selectedHostname , actualHostnames ) {
2021-02-26 18:58:56 +00:00
// The cert already matches the desired state, so there is no need to delete/recreate it.
2021-03-02 01:02:08 +00:00
return false , nil
2021-02-25 18:27:19 +00:00
}
2021-02-26 18:58:56 +00:00
2021-03-02 01:02:08 +00:00
if err = c . ensureTLSSecretIsRemoved ( ctx ) ; err != nil {
return false , err
2021-02-25 00:03:17 +00:00
}
2021-03-02 01:02:08 +00:00
return true , nil
2021-02-25 00:03:17 +00:00
}
2021-03-02 01:02:08 +00:00
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
}
2021-03-03 17:22:35 +00:00
func ( c * impersonatorConfigController ) ensureTLSSecretIsCreatedAndLoaded ( ctx context . Context , nameInfo * certNameInfo , secret * v1 . Secret , ca * certauthority . CA ) error {
2021-02-25 00:03:17 +00:00
if secret != nil {
err := c . loadTLSCertFromSecret ( secret )
if err != nil {
2021-03-03 17:22:35 +00:00
return err
2021-02-25 00:03:17 +00:00
}
2021-03-03 17:22:35 +00:00
return nil
2021-02-24 18:56:24 +00:00
}
2021-02-25 00:03:17 +00:00
2021-03-03 17:22:35 +00:00
if ! nameInfo . ready {
return nil
2021-02-25 00:03:17 +00:00
}
2021-03-03 17:22:35 +00:00
newTLSSecret , err := c . createNewTLSSecret ( ctx , ca , nameInfo . selectedIP , nameInfo . selectedHostname )
2021-02-25 00:03:17 +00:00
if err != nil {
2021-03-03 17:22:35 +00:00
return err
2021-02-25 00:03:17 +00:00
}
err = c . loadTLSCertFromSecret ( newTLSSecret )
if err != nil {
2021-03-03 17:22:35 +00:00
return err
2021-02-25 00:03:17 +00:00
}
2021-03-03 17:22:35 +00:00
return nil
2021-02-25 00:03:17 +00:00
}
2021-03-02 01:02:08 +00:00
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 ) {
2021-03-08 19:31:16 +00:00
impersonationCA , err := certauthority . New ( pkix . Name { CommonName : caCommonName } , oneHundredYears )
2021-03-02 01:02:08 +00:00
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
}
2021-03-03 17:22:35 +00:00
func ( c * impersonatorConfigController ) findDesiredTLSCertificateName ( config * impersonator . Config ) ( * certNameInfo , error ) {
if config . HasEndpoint ( ) {
2021-03-10 18:30:06 +00:00
return c . findTLSCertificateNameFromEndpointConfig ( config ) , nil
2021-02-25 18:27:19 +00:00
}
return c . findTLSCertificateNameFromLoadBalancer ( )
}
2021-03-10 18:30:06 +00:00
func ( c * impersonatorConfigController ) findTLSCertificateNameFromEndpointConfig ( config * impersonator . Config ) * certNameInfo {
2021-03-03 17:22:35 +00:00
endpointMaybeWithPort := config . Endpoint
endpointWithoutPort := strings . Split ( endpointMaybeWithPort , ":" ) [ 0 ]
2021-02-26 23:01:38 +00:00
parsedAsIP := net . ParseIP ( endpointWithoutPort )
2021-02-25 18:27:19 +00:00
if parsedAsIP != nil {
2021-03-10 18:30:06 +00:00
return & certNameInfo { ready : true , selectedIP : parsedAsIP , clientEndpoint : endpointMaybeWithPort }
2021-02-24 18:56:24 +00:00
}
2021-03-10 18:30:06 +00:00
return & certNameInfo { ready : true , selectedHostname : endpointWithoutPort , clientEndpoint : endpointMaybeWithPort }
2021-02-25 18:27:19 +00:00
}
2021-03-03 17:22:35 +00:00
func ( c * impersonatorConfigController ) findTLSCertificateNameFromLoadBalancer ( ) ( * certNameInfo , error ) {
2021-02-25 18:27:19 +00:00
lb , err := c . servicesInformer . Lister ( ) . Services ( c . namespace ) . Get ( c . generatedLoadBalancerServiceName )
notFound := k8serrors . IsNotFound ( err )
if notFound {
2021-03-02 01:02:08 +00:00
// We aren't ready and will try again later in this case.
2021-03-03 17:22:35 +00:00
return & certNameInfo { ready : false } , nil
2021-02-25 18:27:19 +00:00
}
if err != nil {
2021-03-03 17:22:35 +00:00
return nil , err
2021-02-25 18:27:19 +00:00
}
ingresses := lb . Status . LoadBalancer . Ingress
2021-02-25 19:40:14 +00:00
if len ( ingresses ) == 0 || ( ingresses [ 0 ] . Hostname == "" && ingresses [ 0 ] . IP == "" ) {
2021-02-25 18:27:19 +00:00
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 )
2021-03-03 17:22:35 +00:00
return & certNameInfo { ready : false } , nil
2021-02-25 18:27:19 +00:00
}
2021-02-26 01:03:34 +00:00
for _ , ingress := range ingresses {
hostname := ingress . Hostname
if hostname != "" {
2021-03-03 17:22:35 +00:00
return & certNameInfo { ready : true , selectedHostname : hostname , clientEndpoint : hostname } , nil
2021-02-26 01:03:34 +00:00
}
2021-02-25 19:40:14 +00:00
}
2021-02-26 01:03:34 +00:00
for _ , ingress := range ingresses {
ip := ingress . IP
parsedIP := net . ParseIP ( ip )
if parsedIP != nil {
2021-03-03 17:22:35 +00:00
return & certNameInfo { ready : true , selectedIP : parsedIP , clientEndpoint : ip } , nil
2021-02-26 01:03:34 +00:00
}
2021-02-25 19:40:14 +00:00
}
2021-02-26 01:03:34 +00:00
2021-03-03 17:22:35 +00:00
return nil , fmt . Errorf ( "could not find valid IP addresses or hostnames from load balancer %s/%s" , c . namespace , lb . Name )
2021-02-25 00:03:17 +00:00
}
2021-03-02 01:02:08 +00:00
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 }
}
2021-03-08 19:31:16 +00:00
impersonationCert , err := ca . Issue ( pkix . Name { } , hostnames , ips , oneHundredYears )
2021-02-24 18:56:24 +00:00
if err != nil {
2021-02-25 00:03:17 +00:00
return nil , fmt . Errorf ( "could not create impersonation cert: %w" , err )
2021-02-24 18:56:24 +00:00
}
2021-02-26 18:58:56 +00:00
certPEM , keyPEM , err := certauthority . ToPEM ( impersonationCert )
if err != nil {
return nil , err
}
2021-02-24 18:56:24 +00:00
2021-02-25 00:03:17 +00:00
newTLSSecret := & v1 . Secret {
2021-02-24 18:56:24 +00:00
ObjectMeta : metav1 . ObjectMeta {
Name : c . tlsSecretName ,
Namespace : c . namespace ,
Labels : c . labels ,
} ,
Data : map [ string ] [ ] byte {
v1 . TLSPrivateKeyKey : keyPEM ,
v1 . TLSCertKey : certPEM ,
} ,
2021-03-02 01:02:08 +00:00
Type : v1 . SecretTypeTLS ,
2021-02-25 00:03:17 +00:00
}
2021-02-26 18:58:56 +00:00
2021-02-25 18:27:19 +00:00
plog . Info ( "Creating TLS certificates for impersonation proxy" ,
"ips" , ips ,
"hostnames" , hostnames ,
"secret" , c . tlsSecretName ,
"namespace" , c . namespace )
2021-03-02 01:02:08 +00:00
return c . k8sClient . CoreV1 ( ) . Secrets ( c . namespace ) . Create ( ctx , newTLSSecret , metav1 . CreateOptions { } )
2021-02-25 00:03:17 +00:00
}
func ( c * impersonatorConfigController ) loadTLSCertFromSecret ( tlsSecret * v1 . Secret ) error {
certPEM := tlsSecret . Data [ v1 . TLSCertKey ]
keyPEM := tlsSecret . Data [ v1 . TLSPrivateKeyKey ]
2021-03-10 18:30:06 +00:00
_ , err := tls . X509KeyPair ( certPEM , keyPEM )
2021-02-25 00:03:17 +00:00
if err != nil {
2021-03-10 18:30:06 +00:00
c . tlsServingCertDynamicCertProvider . Set ( nil , nil )
2021-02-25 00:03:17 +00:00
return fmt . Errorf ( "could not parse TLS cert PEM data from Secret: %w" , err )
}
2021-02-25 18:27:19 +00:00
plog . Info ( "Loading TLS certificates for impersonation proxy" ,
2021-03-02 01:02:08 +00:00
"certPEM" , string ( certPEM ) ,
2021-02-25 18:27:19 +00:00
"secret" , c . tlsSecretName ,
"namespace" , c . namespace )
2021-03-10 18:30:06 +00:00
c . tlsServingCertDynamicCertProvider . Set ( certPEM , keyPEM )
2021-02-24 18:56:24 +00:00
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" ,
2021-02-25 18:27:19 +00:00
"secret" , c . tlsSecretName ,
2021-02-24 18:56:24 +00:00
"namespace" , c . namespace )
err = c . k8sClient . CoreV1 ( ) . Secrets ( c . namespace ) . Delete ( ctx , c . tlsSecretName , metav1 . DeleteOptions { } )
if err != nil {
return err
}
2021-03-10 18:30:06 +00:00
c . tlsServingCertDynamicCertProvider . Set ( nil , nil )
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 ]
_ , err = tls . X509KeyPair ( certPEM , keyPEM )
if err != nil {
return fmt . Errorf ( "could not load the impersonator's credential signing secret: %w" , err )
}
2021-02-25 00:03:17 +00:00
2021-03-10 18:30:06 +00:00
plog . Info ( "Loading credential signing certificate for impersonation proxy" ,
"certPEM" , string ( certPEM ) ,
"fromSecret" , c . impersonationSignerSecretName ,
"namespace" , c . namespace )
c . impersonationSigningCertProvider . Set ( certPEM , keyPEM )
2021-02-24 18:56:24 +00:00
return nil
}
2021-03-10 18:30:06 +00:00
func ( c * impersonatorConfigController ) clearSignerCA ( ) {
plog . Info ( "Clearing credential signing certificate for impersonation proxy" )
c . impersonationSigningCertProvider . Set ( nil , nil )
}
2021-03-03 17:22:35 +00:00
func ( c * impersonatorConfigController ) doSyncResult ( nameInfo * certNameInfo , config * impersonator . Config , ca * certauthority . CA ) * v1alpha1 . CredentialIssuerStrategy {
2021-03-02 22:48:58 +00:00
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 ( ) ) ,
}
2021-03-03 17:22:35 +00:00
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 ( ) ) ,
2021-03-03 00:51:35 +00:00
}
2021-03-03 17:22:35 +00:00
default :
2021-03-02 22:48:58 +00:00
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 ( ) ) ,
2021-03-03 00:51:35 +00:00
Frontend : & v1alpha1 . CredentialIssuerFrontend {
Type : v1alpha1 . ImpersonationProxyFrontendType ,
ImpersonationProxyInfo : & v1alpha1 . ImpersonationProxyInfo {
2021-03-03 17:22:35 +00:00
Endpoint : "https://" + nameInfo . clientEndpoint ,
2021-03-03 00:51:35 +00:00
CertificateAuthorityData : base64 . StdEncoding . EncodeToString ( ca . Bundle ( ) ) ,
} ,
} ,
2021-03-02 22:48:58 +00:00
}
}
}