From b8059876208686f7030e15374ccdfbf37911d1b8 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Sat, 1 Mar 2025 02:17:40 +0000 Subject: [PATCH] Add caching for GitHub App tokens Signed-off-by: Matheus Pimenta --- cmd/operator/main.go | 22 ++++++++++ go.mod | 7 ++-- go.sum | 18 +++++---- .../resourcesetinputprovider_controller.go | 13 +++++- ...esourcesetinputprovider_controller_test.go | 40 +++++++++++++++++++ .../controller/testdata/rsa-private-key.pem | 27 +++++++++++++ internal/gitprovider/github.go | 2 +- internal/reporter/metrics.go | 5 +++ 8 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 internal/controller/testdata/rsa-private-key.pem diff --git a/cmd/operator/main.go b/cmd/operator/main.go index afc18ef..730f456 100644 --- a/cmd/operator/main.go +++ b/cmd/operator/main.go @@ -7,10 +7,12 @@ import ( "crypto/fips140" "errors" "os" + "time" "github.com/fluxcd/cli-utils/pkg/kstatus/polling" "github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader" "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/pkg/cache" runtimeCtrl "github.com/fluxcd/pkg/runtime/controller" "github.com/fluxcd/pkg/runtime/logger" "github.com/fluxcd/pkg/runtime/pprof" @@ -55,6 +57,8 @@ func init() { func main() { var ( concurrent int + tokenCacheMaxSize int + tokenCacheMaxDuration time.Duration metricsAddr string healthAddr string enableLeaderElection bool @@ -65,6 +69,10 @@ func main() { ) flag.IntVar(&concurrent, "concurrent", 10, "The number of concurrent resource reconciles.") + flag.IntVar(&tokenCacheMaxSize, "token-cache-max-size", 100, + "The maximum size of the cache in number of tokens.") + flag.DurationVar(&tokenCacheMaxDuration, "token-cache-max-duration", cache.TokenMaxDuration, + "The maximum duration a token is cached.") flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&healthAddr, "health-addr", ":8081", "The address the health endpoint binds to.") flag.StringVar(&storagePath, "storage-path", "/data", "The local storage path.") @@ -158,6 +166,19 @@ func main() { os.Exit(1) } + var tokenCache *cache.TokenCache + if tokenCacheMaxSize > 0 { + tokenCache, err = cache.NewTokenCache(tokenCacheMaxSize, + cache.WithMaxDuration(tokenCacheMaxDuration), + cache.WithMetricsRegisterer(reporter.Registerer()), + cache.WithMetricsPrefix("flux_token_"), + cache.WithEventNamespaceLabel("exported_namespace")) + if err != nil { + setupLog.Error(err, "unable to create token cache") + os.Exit(1) + } + } + if err = (&controller.EntitlementReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), @@ -235,6 +256,7 @@ func main() { Client: mgr.GetClient(), StatusManager: controllerName, EventRecorder: mgr.GetEventRecorderFor(controllerName), + TokenCache: tokenCache, }).SetupWithManager(mgr, controller.ResourceSetInputProviderReconcilerOptions{ RateLimiter: runtimeCtrl.GetRateLimiter(rateLimiterOptions), diff --git a/go.mod b/go.mod index 91a62c4..3d4e866 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,8 @@ require ( github.com/fluxcd/cli-utils v0.36.0-flux.12 github.com/fluxcd/pkg/apis/kustomize v1.9.0 github.com/fluxcd/pkg/apis/meta v1.10.0 - github.com/fluxcd/pkg/auth v0.3.0 + github.com/fluxcd/pkg/auth v0.6.0 + github.com/fluxcd/pkg/cache v0.6.0 github.com/fluxcd/pkg/kustomize v1.16.0 github.com/fluxcd/pkg/runtime v0.54.0 github.com/fluxcd/pkg/ssa v0.45.1 @@ -20,7 +21,7 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.1 github.com/google/go-containerregistry v0.20.3 github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20250115185438-c4dd792fa06c - github.com/google/go-github/v68 v68.0.0 + github.com/google/go-github/v69 v69.2.0 github.com/gosimple/slug v1.15.0 github.com/hashicorp/go-retryablehttp v0.7.7 github.com/olekukonko/tablewriter v0.0.5 @@ -75,7 +76,7 @@ require ( github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20250115170608-608f37feb051 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/bradleyfalzon/ghinstallation/v2 v2.13.0 // indirect + github.com/bradleyfalzon/ghinstallation/v2 v2.14.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect diff --git a/go.sum b/go.sum index 792c5c9..d06d524 100644 --- a/go.sum +++ b/go.sum @@ -76,8 +76,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bradleyfalzon/ghinstallation/v2 v2.13.0 h1:5FhjW93/YLQJDmPdeyMPw7IjAPzqsr+0jHPfrPz0sZI= -github.com/bradleyfalzon/ghinstallation/v2 v2.13.0/go.mod h1:EJ6fgedVEHa2kUyBTTvslJCXJafS/mhJNNKEOCspZXQ= +github.com/bradleyfalzon/ghinstallation/v2 v2.14.0 h1:0D4vKCHOvYrDU8u61TnE2JfNT4VRrBLphmxtqazTO+M= +github.com/bradleyfalzon/ghinstallation/v2 v2.14.0/go.mod h1:LOVmdZYVZ8jqdr4n9wWm1ocDiMz9IfMGfRkaYC1a52A= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= @@ -121,8 +121,10 @@ github.com/fluxcd/pkg/apis/kustomize v1.9.0 h1:SJpT1CK58AnTvCpDKeGfMNA0Xud/4VReZ github.com/fluxcd/pkg/apis/kustomize v1.9.0/go.mod h1:AZl2GU03oPVue6SUivdiIYd/3mvF94j7t1G2JO26d4s= github.com/fluxcd/pkg/apis/meta v1.10.0 h1:rqbAuyl5ug7A5jjRf/rNwBXmNl6tJ9wG2iIsriwnQUk= github.com/fluxcd/pkg/apis/meta v1.10.0/go.mod h1:n7NstXHDaleAUMajcXTVkhz0MYkvEXy1C/eLI/t1xoI= -github.com/fluxcd/pkg/auth v0.3.0 h1:I1A3e81O+bpAgEcJ3e+rXqObKPjzBu6FLYXQTSxXLOs= -github.com/fluxcd/pkg/auth v0.3.0/go.mod h1:g9KJ4iNcCd6Sb7al4yN1+olgOfgwmU4lgCWbwvMsFRE= +github.com/fluxcd/pkg/auth v0.6.0 h1:plHUxZ/+IJn7lm1jEnxwr1hvIgMKIepRmheOHZz3ltA= +github.com/fluxcd/pkg/auth v0.6.0/go.mod h1:mdlybbiiRJ5AoRYx1jM7jFoEUYxzB2Ma406fYYt01qA= +github.com/fluxcd/pkg/cache v0.6.0 h1:l6tly7YGYkCaMj0Sfn9m+jiLir9CDksI+QDOE/BwK0c= +github.com/fluxcd/pkg/cache v0.6.0/go.mod h1:VNMLzJa62iDHfojoywykJ2pdUlgSjVzTLrOgkNlPxEo= github.com/fluxcd/pkg/envsubst v1.3.0 h1:84Ain+8EBvyzu6y0FsKRwNsvaSiKuqhTqeh/4yoGFFU= github.com/fluxcd/pkg/envsubst v1.3.0/go.mod h1:lz6HvqDnxbX0sIqjr1fxw0oTGYACLVFcOE/srKS0VQQ= github.com/fluxcd/pkg/kustomize v1.16.0 h1:UBOeIvkrC6y4owYs7vZwG5PUVFeqnRoDFN9eaNhuNPI= @@ -133,8 +135,8 @@ github.com/fluxcd/pkg/sourceignore v0.11.0 h1:xzpYmc5/t/Ck+/DkJSX3r+VbahDRIAn5kb github.com/fluxcd/pkg/sourceignore v0.11.0/go.mod h1:ri2FvlzX8ep2iszOK5gF/riYq2TNgpVvsfJ2QY0dLWI= github.com/fluxcd/pkg/ssa v0.45.1 h1:ISl84TJwRP/GuZXrKiR9Tf8JOnG5XFgtjcYoR4XQYf4= github.com/fluxcd/pkg/ssa v0.45.1/go.mod h1:8Anf7XVZ0zxOve7HXbDaW1s0gfmP95ksJBlKfDYinhQ= -github.com/fluxcd/pkg/ssh v0.16.0 h1:dhSWNp30p05EJ86bhICezad9pG3fJi4CAVKnZ3EmUV8= -github.com/fluxcd/pkg/ssh v0.16.0/go.mod h1:MyDegNZHnKNDAwM5/A2t/1FjpvpS8BsRZQ4WqEwCHc0= +github.com/fluxcd/pkg/ssh v0.17.0 h1:o+MgdM/OB8R/+KEc3W3ml/inEKZqCwT8V71dkbTAbm4= +github.com/fluxcd/pkg/ssh v0.17.0/go.mod h1:4yU099LjFWOJXZiu73rvqA70mOoSXG2yqxfPBxhnGgQ= github.com/fluxcd/pkg/tar v0.11.0 h1:pjf/rzr6HNAPiuxT59mtba9tfBtdNiSQ/UqduG8vZ2I= github.com/fluxcd/pkg/tar v0.11.0/go.mod h1:+kiP25NqibWMpFWgizyPEMqnMJIux7bCgEy+4pfxyI4= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= @@ -186,8 +188,8 @@ github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20250115185438- github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20250115185438-c4dd792fa06c/go.mod h1:8mk2eu7HGqCp+JSWQVFCnKQwk/K6cIY6ID9aX72iTRo= github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20241111191718-6bce25ecf029 h1:tmtax9EjrCFrrw72NeGso7qZUnJXTIP368kcjE4lZwE= github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20241111191718-6bce25ecf029/go.mod h1:zD6WJVa49IK5fhrZOUaq7UcSgxZFlnS80EJBrcVFkFI= -github.com/google/go-github/v68 v68.0.0 h1:ZW57zeNZiXTdQ16qrDiZ0k6XucrxZ2CGmoTvcCyQG6s= -github.com/google/go-github/v68 v68.0.0/go.mod h1:K9HAUBovM2sLwM408A18h+wd9vqdLOEqTUCbnRIcx68= +github.com/google/go-github/v69 v69.2.0 h1:wR+Wi/fN2zdUx9YxSmYE0ktiX9IAR/BeePzeaUUbEHE= +github.com/google/go-github/v69 v69.2.0/go.mod h1:xne4jymxLR6Uj9b7J7PyTpkMYstEMMwGZa0Aehh1azM= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= diff --git a/internal/controller/resourcesetinputprovider_controller.go b/internal/controller/resourcesetinputprovider_controller.go index 01cdd27..03d5195 100644 --- a/internal/controller/resourcesetinputprovider_controller.go +++ b/internal/controller/resourcesetinputprovider_controller.go @@ -14,6 +14,7 @@ import ( "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/auth/github" + "github.com/fluxcd/pkg/cache" "github.com/fluxcd/pkg/runtime/conditions" "github.com/fluxcd/pkg/runtime/patch" "github.com/opencontainers/go-digest" @@ -40,6 +41,7 @@ type ResourceSetInputProviderReconciler struct { kuberecorder.EventRecorder StatusManager string + TokenCache *cache.TokenCache } // +kubebuilder:rbac:groups=fluxcd.controlplane.io,resources=resourcesetinputproviders,verbs=get;list;watch;create;update;patch;delete @@ -357,7 +359,16 @@ func (r *ResourceSetInputProviderReconciler) getGitHubToken( return password, err } - ghc, err := github.New(github.WithAppData(authData)) + opts := []github.OptFunc{github.WithAppData(authData)} + + if r.TokenCache != nil { + opts = append(opts, github.WithCache(r.TokenCache, + fluxcdv1.ResourceSetInputProviderKind, + obj.GetName(), + obj.GetNamespace())) + } + + ghc, err := github.New(opts...) if err != nil { return "", err } diff --git a/internal/controller/resourcesetinputprovider_controller_test.go b/internal/controller/resourcesetinputprovider_controller_test.go index 47564d3..84255e8 100644 --- a/internal/controller/resourcesetinputprovider_controller_test.go +++ b/internal/controller/resourcesetinputprovider_controller_test.go @@ -6,9 +6,13 @@ package controller import ( "context" "fmt" + "os" "testing" + "time" "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/auth/github" + "github.com/fluxcd/pkg/cache" "github.com/fluxcd/pkg/runtime/conditions" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" @@ -495,6 +499,42 @@ spec: g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) } +func TestResourceSetInputProviderReconciler_getGitHubToken_cached(t *testing.T) { + const key = "githubAppID=123,githubAppInstallationID=123456,githubAppBaseURL=https://github.com,githubAppPrivateKeyDigest=9d5b1bf1d595f2da0c8e9941bc84a82dabbad433fc95fea56aa596eda99e550b" + + g := NewWithT(t) + + ctx := context.Background() + + tokenCache, err := cache.NewTokenCache(1) + g.Expect(err).NotTo(HaveOccurred()) + + r := &ResourceSetInputProviderReconciler{ + TokenCache: tokenCache, + } + + _, _, err = r.TokenCache.GetOrSet(ctx, key, func(context.Context) (cache.Token, error) { + return &github.AppToken{ + Token: "my-gh-app-token", + ExpiresAt: time.Now().Add(time.Hour), + }, nil + }) + g.Expect(err).NotTo(HaveOccurred()) + + privateKeyPEM, err := os.ReadFile("testdata/rsa-private-key.pem") + g.Expect(err).NotTo(HaveOccurred()) + + token, err := r.getGitHubToken(ctx, &fluxcdv1.ResourceSetInputProvider{}, map[string][]byte{ + "githubAppID": []byte("123"), + "githubAppInstallationID": []byte("123456"), + "githubAppBaseURL": []byte("https://github.com"), + "githubAppPrivateKey": privateKeyPEM, + }) + + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(token).To(Equal("my-gh-app-token")) +} + func getResourceSetInputProviderReconciler() *ResourceSetInputProviderReconciler { return &ResourceSetInputProviderReconciler{ Client: testClient, diff --git a/internal/controller/testdata/rsa-private-key.pem b/internal/controller/testdata/rsa-private-key.pem new file mode 100644 index 0000000..2d28f33 --- /dev/null +++ b/internal/controller/testdata/rsa-private-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAq3CARSLLsDLQ53YVVo+MFGPLFdlr6bNOVxHb4dwV4+AXiYR5 +00Bd7VFR6THUYucI1LjLAMt5pWuG9wvCo4r4E2HyVsjZcEwUI3XDLbX+73J8xt/w +e1rD4vKEz3zehlgJsm5x1GzrTjz6Vwa8WOO5A7r0M3aUluFsD769oykracs+wsXY +MUfnEghUgATb6/1givxkdWpL+n6Q5jkE68JNvCYrFT82oAj5hF253hjUWdcjR2Hl ++c6ocBLqTPfSk1WqIkWAk1oMGPOBUDZ2nKfVyWkXc6LwMYMHoyAh6yvcXd6UBpmP +psVWPn4Up2iH9qkuEvmJhYac1d3WfM7v3uCMtQIDAQABAoIBAAbGkf3pg6UJmnG4 +xtwecpoFswDrOxVyoD3XDK/ZsRsfVV7P17luhFJ8/HXxIbns8/o6+XmlERWQG+FH +W4siKBmAAqzFEGRHUCPHTjj6xezJw2kKxb2NAfaba34AgJJNCB5qDgv9aAlgu0DV +mSZm4v46P9eJhWg3LZGrvLamSbsgcX/tFrCqQsPdYfKQk+Eenwm5n42TbADaC27I +5ChHBA/B0yY/HhSn2HKTVvMHsnmnHpDQ/RSihAztvtzhVK+jCjnS5EX1YYY9v7pv +t4koI39Qt6IP0s5gt4gtXFhJlS2Nza6JtaHZUmjVeQxKi5pe7Iqe9hwgaS3VqCWx +JSv/JzsCgYEAwgHR8HQkLa/KV8awtDDKSpeyYvv5xlCyWHTw8H1zU49pAVN/Xh46 +DIwmkZH3OKBVpOr6wVKdniueeb3DO8dvIJ8hpw/f+mKBGJLsGKHfZ7/x1kT/q13h +C74OqFRLew4dFH0bqPkqvE568X6/C/CWnmbiTggqcn0tVjr0peeR6MsCgYEA4jiZ +xbsaD8lQE36E62Ol2dc4aySCb7pMaixtXOjyL9C8BO8BjiwgF5u+Otu9s6QhryBF +0BqOShZq8GxlcKpsJKXWCkmC2crx2Aayl3s3+7ScZ8Qp2KGrUPxrpL/RZL2lZ9lH +dX1LYC+TM5a9KcSf1qodH4slP6d2Aj2bWCKIMH8CgYEArKbYAWgqZioiJXlh+gnN +jRJxI1vgzdc00DnJzgumnX9r0E1RdR3rRQ1YqYXAADnX3ftsCq2OLZvd3bO90i5K +vDpBxZ4AEqClCIx/5e/wlDEidDBVY1kZlMyf2LejsLA/uuMXwYl0ub4R9WZ5eJO0 +RuWCkjT8KYUy2qF+5UIu/H8CgYAQWCSMC7ObVmEpt2dlFmMCNTGHVDD0X5JrzV/t +aYst9zfOZ3JGUlvTONZqrDutgftJCtzgZzrGkY4SZtKBbF652x12ys7ga3BDumAm +36kwz2DJgnu/gha9mC8yzQUU8TrFIQavr2jFv0o0XPy3ytP9j3bhM41yZuf4y3iw +ynXqgwKBgQCQR7RK06eFAydcAFY1q/1Ozl/fruMox5ojBVAmlu/5C0FQK5UPLvoK +UKbj5fA/oWwE3X3yD+9iF4YjgZ7Tr3pBirA7rqsYrH9e2zaiiyAYQwLlbnAOrdjC +eOLmLSnyvUFp4E7RRYVoHmPzxAY+nfkODwtO7FGlfihB+5kPbKPQDA== +-----END RSA PRIVATE KEY----- diff --git a/internal/gitprovider/github.go b/internal/gitprovider/github.go index 01325f6..48b83c4 100644 --- a/internal/gitprovider/github.go +++ b/internal/gitprovider/github.go @@ -11,7 +11,7 @@ import ( "net/url" "strings" - "github.com/google/go-github/v68/github" + "github.com/google/go-github/v69/github" "golang.org/x/oauth2" ) diff --git a/internal/reporter/metrics.go b/internal/reporter/metrics.go index 8a8d001..477d74c 100644 --- a/internal/reporter/metrics.go +++ b/internal/reporter/metrics.go @@ -13,6 +13,11 @@ import ( fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1" ) +// Registerer returns the metrics registerer. +func Registerer() prometheus.Registerer { + return crtlmetrics.Registry +} + // MustRegisterMetrics attempts to register the metrics collectors // in the controller-runtime metrics registry. func MustRegisterMetrics() {