From dcd698c60dd7023941730d841657488e4acfade7 Mon Sep 17 00:00:00 2001 From: Marcus Noble Date: Wed, 1 Jun 2022 06:48:42 +0100 Subject: [PATCH] Updated to use informers Signed-off-by: Marcus Noble --- Makefile | 2 +- go.sum | 6 ++- main.go | 122 ++++++++++++++++++++++++++++---------------- manifests/rbac.yaml | 2 +- 4 files changed, 85 insertions(+), 47 deletions(-) diff --git a/Makefile b/Makefile index df30aef..5d802b5 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .DEFAULT_GOAL := default -IMAGE ?= docker.cluster.fun/averagemarcus/kube-1password-secrets:latest +IMAGE ?= rg.fr-par.scw.cloud/averagemarcus/kube-1password-secrets:latest .PHONY: test # Run all tests, linting and format checks test: lint check-format run-tests diff --git a/go.sum b/go.sum index dbb4f6b..dcf7053 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,7 @@ github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= @@ -31,6 +32,7 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -55,6 +57,7 @@ github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= @@ -63,6 +66,7 @@ github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -160,7 +164,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IV golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -198,6 +201,7 @@ k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUc k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= diff --git a/main.go b/main.go index 6cd8aaa..4f6a7d6 100644 --- a/main.go +++ b/main.go @@ -7,16 +7,21 @@ import ( "os" "os/user" "strings" - "time" "git.cloud.cluster.fun/AverageMarcus/kube-1password-secrets/internal/onepassword" apiv1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" ) const ( + fieldManagerName = "kube-1password-secrets" idAnnotation = "kube-1password" vaultAnnotation = "kube-1password/vault" usernameAnnotation = "kube-1password/username-key" @@ -25,8 +30,14 @@ const ( secretTextParseAnnotation = "kube-1password/secret-text-parse" ) +var ( + opClient *onepassword.Client + clientset *kubernetes.Clientset +) + func main() { - opClient, err := buildOpClient() + var err error + opClient, err = buildOpClient() if err != nil { panic(err.Error()) } @@ -35,63 +46,86 @@ func main() { if err != nil { panic(err.Error()) } - clientset, err := kubernetes.NewForConfig(config) + clientset, err = kubernetes.NewForConfig(config) if err != nil { panic(err) } - for { - log.Println("[DEBUG] Syncing secrets") - list, err := clientset.CoreV1().Secrets(apiv1.NamespaceAll).List(context.Background(), metav1.ListOptions{}) + stopper := make(chan struct{}) + defer close(stopper) + factory := informers.NewSharedInformerFactory(clientset, 0) + secretInformer := factory.Core().V1().Secrets() + informer := secretInformer.Informer() + defer runtime.HandleCrash() + go factory.Start(stopper) + if !cache.WaitForCacheSync(stopper, informer.HasSynced) { + runtime.HandleError(fmt.Errorf("Timed out waiting for caches to sync")) + return + } + + informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { processSecret(obj.(*apiv1.Secret)) }, + UpdateFunc: func(old interface{}, new interface{}) { + managedFields := new.(*apiv1.Secret).GetManagedFields() + if len(managedFields) == 0 || managedFields[len(managedFields)-1].Manager != fieldManagerName { + processSecret(new.(*apiv1.Secret)) + } + }, + DeleteFunc: func(interface{}) {}, + }) + + lister := secretInformer.Lister().Secrets((v1.NamespaceAll)) + secrets, err := lister.List(labels.Everything()) + + for _, s := range secrets { + processSecret(s) + } + + <-stopper +} + +func processSecret(s *apiv1.Secret) { + if passwordID, exists := s.ObjectMeta.Annotations[idAnnotation]; exists { + log.Printf("[INFO] Syncing secret %s with 1Password secret %s\n", s.GetName(), passwordID) + keys := parseAnnotations(s.ObjectMeta.Annotations) + + vault := keys["vault"] + + item, err := opClient.GetSecret(vault, passwordID) if err != nil { - panic(err) + log.Println("[ERROR] Could not get secret", err) + return } - for _, s := range list.Items { - if passwordID, exists := s.ObjectMeta.Annotations[idAnnotation]; exists { - log.Printf("[INFO] Syncing secret %s with 1Password secret %s\n", s.GetName(), passwordID) - keys := parseAnnotations(s.ObjectMeta.Annotations) - vault := keys["vault"] + s.Data = make(map[string][]byte) - item, err := opClient.GetSecret(vault, passwordID) - if err != nil { - log.Println("[ERROR] Could not get secret", err) - continue - } + if item.Username != "" { + s.Data[keys["username"]] = []byte(parseNewlines(item.Username)) + } - s.Data = make(map[string][]byte) + if item.Password != "" { + s.Data[keys["password"]] = []byte(parseNewlines(item.Password)) + } - if item.Username != "" { - s.Data[keys["username"]] = []byte(parseNewlines(item.Username)) - } - - if item.Password != "" { - s.Data[keys["password"]] = []byte(parseNewlines(item.Password)) - } - - if item.SecretText != "" { - if s.ObjectMeta.Annotations[secretTextParseAnnotation] != "" { - // Parse secret text as individual secrets - lines := strings.Split(item.SecretText, "\n") - for _, line := range lines { - parts := strings.SplitN(line, "=", 2) - if len(parts) == 2 { - s.Data[parts[0]] = []byte(parseNewlines(parts[1])) - } - } - } else { - s.Data[keys["secretText"]] = []byte(parseNewlines(item.SecretText)) + if item.SecretText != "" { + if s.ObjectMeta.Annotations[secretTextParseAnnotation] != "" { + // Parse secret text as individual secrets + lines := strings.Split(item.SecretText, "\n") + for _, line := range lines { + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 { + s.Data[parts[0]] = []byte(parseNewlines(parts[1])) } } - - if _, err := clientset.CoreV1().Secrets(s.GetNamespace()).Update(context.Background(), &s, metav1.UpdateOptions{}); err != nil { - log.Println("[ERROR] Could not update secret value", err) - continue - } + } else { + s.Data[keys["secretText"]] = []byte(parseNewlines(item.SecretText)) } } - time.Sleep(5 * time.Minute) + _, err = clientset.CoreV1().Secrets(s.GetNamespace()).Update(context.Background(), s, metav1.UpdateOptions{FieldManager: fieldManagerName}) + if err != nil { + log.Println("[ERROR] Could not update secret value", err) + } } } diff --git a/manifests/rbac.yaml b/manifests/rbac.yaml index 26eced9..ff2494e 100644 --- a/manifests/rbac.yaml +++ b/manifests/rbac.yaml @@ -12,7 +12,7 @@ metadata: rules: - apiGroups: [""] resources: ["secrets"] - verbs: ["get", "list", "update"] + verbs: ["get", "list", "update", "watch"] ---