From e7c65fd483e35220c8bbb8f1bd841be5f00aa055 Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Fri, 25 May 2018 12:03:06 +0200 Subject: [PATCH] create a bootstrap token only if doesn't exist (#213) * create a bootstrap token only if doesn't exists --- Gopkg.lock | 2 +- cmd/controller/main.go | 12 ++-- examples/machine-controller.yaml | 10 ++- pkg/controller/kubeconfig.go | 104 ++++++++++++++++++++++++++----- pkg/controller/machine.go | 18 +++--- 5 files changed, 113 insertions(+), 33 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index fa3c94cc3..da8a89ba9 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -825,6 +825,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "9142fb754533aecc18583f74fea2499ef90df3659ffb9bdac58da1a176755cfb" + inputs-digest = "e8a59ced8a7166c0dcf91a2ce4de99aea56851aeff555710772fc2b0d903ed0b" solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/controller/main.go b/cmd/controller/main.go index d0ebac7bd..063c9dd4d 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -100,8 +100,8 @@ type controllerRunOptions struct { // nodeLister holds a lister that knows how to list Nodes from a cache nodeLister listerscorev1.NodeLister - // configMapLister holds a lister that knows how to list ConfigMaps from a cache - configMapLister listerscorev1.ConfigMapLister + // secreSystemNstLister knows hot to list Secrects that are inside kube-system namespace from a cache + secretSystemNsLister listerscorev1.SecretLister // machineInformer holds a shared informer for Machines machineInformer cache.SharedIndexInformer @@ -176,6 +176,7 @@ func main() { machineInformerFactory := machineinformers.NewSharedInformerFactory(machineClient, time.Second*30) kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, time.Second*30) kubePublicKubeInformerFactory := kubeinformers.NewFilteredSharedInformerFactory(kubeClient, time.Second*30, metav1.NamespacePublic, nil) + kubeSystemInformerFactory := kubeinformers.NewFilteredSharedInformerFactory(kubeClient, time.Second*30, metav1.NamespaceSystem, nil) defaultKubeInformerFactory := kubeinformers.NewFilteredSharedInformerFactory(kubeClient, time.Second*30, metav1.NamespaceDefault, nil) kubeconfigProvider := clusterinfo.New(cfg, kubePublicKubeInformerFactory.Core().V1().ConfigMaps().Lister(), defaultKubeInformerFactory.Core().V1().Endpoints().Lister()) @@ -188,7 +189,7 @@ func main() { leaderElectionClient: leaderElectionClient, nodeInformer: kubeInformerFactory.Core().V1().Nodes().Informer(), nodeLister: kubeInformerFactory.Core().V1().Nodes().Lister(), - configMapLister: kubePublicKubeInformerFactory.Core().V1().ConfigMaps().Lister(), + secretSystemNsLister: kubeSystemInformerFactory.Core().V1().Secrets().Lister(), machineInformer: machineInformerFactory.Machine().V1alpha1().Machines().Informer(), machineLister: machineInformerFactory.Machine().V1alpha1().Machines().Lister(), kubeconfigProvider: kubeconfigProvider, @@ -199,8 +200,9 @@ func main() { kubePublicKubeInformerFactory.Start(stopCh) defaultKubeInformerFactory.Start(stopCh) machineInformerFactory.Start(stopCh) + kubeSystemInformerFactory.Start(stopCh) - for _, syncsMap := range []map[reflect.Type]bool{kubeInformerFactory.WaitForCacheSync(stopCh), kubePublicKubeInformerFactory.WaitForCacheSync(stopCh), machineInformerFactory.WaitForCacheSync(stopCh), defaultKubeInformerFactory.WaitForCacheSync(stopCh)} { + for _, syncsMap := range []map[reflect.Type]bool{kubeInformerFactory.WaitForCacheSync(stopCh), kubePublicKubeInformerFactory.WaitForCacheSync(stopCh), machineInformerFactory.WaitForCacheSync(stopCh), defaultKubeInformerFactory.WaitForCacheSync(stopCh), kubeSystemInformerFactory.WaitForCacheSync(stopCh)} { for key, synced := range syncsMap { if !synced { glog.Fatalf("unable to sync %s", key) @@ -279,9 +281,9 @@ func startControllerViaLeaderElection(runOptions controllerRunOptions) error { runOptions.machineClient, runOptions.nodeInformer, runOptions.nodeLister, - runOptions.configMapLister, runOptions.machineInformer, runOptions.machineLister, + runOptions.secretSystemNsLister, runOptions.clusterDNSIPs, controller.MetricsCollection{ Machines: runOptions.metrics.Machines, diff --git a/examples/machine-controller.yaml b/examples/machine-controller.yaml index ae22ef002..84751e7aa 100644 --- a/examples/machine-controller.yaml +++ b/examples/machine-controller.yaml @@ -83,10 +83,18 @@ rules: - apiGroups: - "" resources: - - secrets - events verbs: - create +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - update + - list + - watch - apiGroups: - "" resources: diff --git a/pkg/controller/kubeconfig.go b/pkg/controller/kubeconfig.go index 427e028e8..c8c3f7ee8 100644 --- a/pkg/controller/kubeconfig.go +++ b/pkg/controller/kubeconfig.go @@ -6,60 +6,130 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/util/rand" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) const ( secretTypeBootstrapToken v1.SecretType = "bootstrap.kubernetes.io/token" + machineNameLabelKey = "machine.k8s.io/machine.name" + tokenIDKey = "token-id" + tokenSecretKey = "token-secret" + expirationKey = "expiration" + tokenFormatter = "%s.%s" ) +func (c *Controller) createBootstrapKubeconfig(name string) (*clientcmdapi.Config, error) { + token, err := c.createBootstrapToken(name) + if err != nil { + return nil, err + } + + infoKubeconfig, err := c.kubeconfigProvider.GetKubeconfig() + if err != nil { + return nil, err + } + + outConfig := infoKubeconfig.DeepCopy() + + outConfig.AuthInfos = map[string]*clientcmdapi.AuthInfo{ + "": { + Token: token, + }, + } + + return outConfig, nil +} + func (c *Controller) createBootstrapToken(name string) (string, error) { + existingSecret, err := c.getSecretIfExists(name) + if err != nil { + return "", err + } + if existingSecret != nil { + return c.updateSecretExpirationAndGetToken(existingSecret) + } + tokenID := rand.String(6) tokenSecret := rand.String(16) secret := v1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("bootstrap-token-%s", tokenID), + Name: fmt.Sprintf("bootstrap-token-%s", tokenID), + Labels: map[string]string{machineNameLabelKey: name}, }, Type: secretTypeBootstrapToken, StringData: map[string]string{ "description": "bootstrap token for " + name, - "token-id": tokenID, - "token-secret": tokenSecret, - "expiration": metav1.Now().Add(24 * time.Hour).Format(time.RFC3339), + tokenIDKey: tokenID, + tokenSecretKey: tokenSecret, + expirationKey: metav1.Now().Add(24 * time.Hour).Format(time.RFC3339), "usage-bootstrap-authentication": "true", "usage-bootstrap-signing": "true", "auth-extra-groups": "system:bootstrappers:machine-controller:default-node-token", }, } - _, err := c.kubeClient.CoreV1().Secrets(metav1.NamespaceSystem).Create(&secret) + _, err = c.kubeClient.CoreV1().Secrets(metav1.NamespaceSystem).Create(&secret) if err != nil { return "", err } - return fmt.Sprintf("%s.%s", tokenID, tokenSecret), nil + return fmt.Sprintf(tokenFormatter, tokenID, tokenSecret), nil } -func (c *Controller) createBootstrapKubeconfig(name string) (*clientcmdapi.Config, error) { - token, err := c.createBootstrapToken(name) +func (c *Controller) updateSecretExpirationAndGetToken(secret *v1.Secret) (string, error) { + if secret.StringData == nil { + secret.StringData = map[string]string{} + } + secret.StringData[expirationKey] = metav1.Now().Add(1 * time.Hour).Format(time.RFC3339) + tokenID := secret.StringData[tokenIDKey] + tokenSecret := secret.StringData[tokenSecretKey] + token := fmt.Sprintf(tokenFormatter, tokenID, tokenSecret) + + expBytes, ok := secret.Data["expiration"] + if !ok { + return "", fmt.Errorf("haven't found %s key in the secret's Data field", expirationKey) + } + expString := string(expBytes) + expVal := metav1.Now() + err := expVal.UnmarshalQueryParameter(expString) if err != nil { - return nil, err + return "", err + } + now := metav1.Now() + now.Add(15 * time.Minute) + // expVal has to point to a time in the future otherwise we need to update expiration time + if now.Before(&expVal) { + return token, nil } - infoKubeconfig, err := c.kubeconfigProvider.GetKubeconfig() + _, err = c.kubeClient.CoreV1().Secrets(metav1.NamespaceSystem).Update(secret) if err != nil { - return nil, err + return "", err } + return token, nil +} - outConfig := infoKubeconfig.DeepCopy() +func (c *Controller) getSecretIfExists(name string) (*v1.Secret, error) { + req, err := labels.NewRequirement(machineNameLabelKey, selection.Equals, []string{name}) + if err != nil { + return nil, err + } - outConfig.AuthInfos = map[string]*clientcmdapi.AuthInfo{ - "": { - Token: token, - }, + selector := labels.NewSelector().Add(*req) + secrets, err := c.secretSystemNsLister.List(selector) + if err != nil { + return nil, err } - return outConfig, nil + if len(secrets) == 0 { + return nil, nil + } + if len(secrets) > 1 { + return nil, fmt.Errorf("expected to find exactly one secret for the given machine name =%s but found %d", name, len(secrets)) + } + return secrets[0], nil } diff --git a/pkg/controller/machine.go b/pkg/controller/machine.go index 2917f75bf..a8eab927e 100644 --- a/pkg/controller/machine.go +++ b/pkg/controller/machine.go @@ -78,9 +78,9 @@ type Controller struct { kubeClient kubernetes.Interface machineClient machineclientset.Interface - nodesLister listerscorev1.NodeLister - configMapLister listerscorev1.ConfigMapLister - machinesLister machinelistersv1alpha1.MachineLister + nodesLister listerscorev1.NodeLister + machinesLister machinelistersv1alpha1.MachineLister + secretSystemNsLister listerscorev1.SecretLister workqueue workqueue.RateLimitingInterface recorder record.EventRecorder @@ -115,9 +115,9 @@ func NewMachineController( machineClient machineclientset.Interface, nodeInformer cache.SharedIndexInformer, nodeLister listerscorev1.NodeLister, - configMapLister listerscorev1.ConfigMapLister, machineInformer cache.SharedIndexInformer, machineLister machinelistersv1alpha1.MachineLister, + secretSystemNsLister listerscorev1.SecretLister, clusterDNSIPs []net.IP, metrics MetricsCollection, kubeconfigProvider KubeconfigProvider, @@ -129,12 +129,12 @@ func NewMachineController( eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")}) controller := &Controller{ - kubeClient: kubeClient, - nodesLister: nodeLister, - configMapLister: configMapLister, + kubeClient: kubeClient, + nodesLister: nodeLister, - machineClient: machineClient, - machinesLister: machineLister, + machineClient: machineClient, + machinesLister: machineLister, + secretSystemNsLister: secretSystemNsLister, workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(2*time.Second, 10*time.Second, 5), "Machines"), recorder: eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "machine-controller"}),