From 3a41c2e987652a7ebad944c725f7a14bd3d02f4f Mon Sep 17 00:00:00 2001 From: Rachel Sheikh Date: Mon, 15 Jul 2024 12:09:49 -0700 Subject: [PATCH 1/2] fix(cli): add optional password setting for headless redis client (#19035) (#19039) * chore: add optional password setting for headless redis client Signed-off-by: Rachel Sheikh * fix: remove import cycle Signed-off-by: Rachel Sheikh * fix: add shared SetOptionalRedisPasswordFromKubeConfig method Signed-off-by: Rachel Sheikh * fix: export redis consts Signed-off-by: Rachel Sheikh * test: add test cases for SetOptionalRedisPasswordFromKubeConfig() Signed-off-by: Rachel Sheikh * chore: go mod tidy Signed-off-by: Rachel Sheikh * fix: use require instead of assert Signed-off-by: Rachel Sheikh * fix: Update common/common.go Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> Signed-off-by: Rachel Sheikh --------- Signed-off-by: Rachel Sheikh Signed-off-by: Rachel Sheikh Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> --- cmd/argocd/commands/admin/cluster.go | 9 +-- .../commands/admin/redis_initial_password.go | 21 ++---- cmd/argocd/commands/headless/headless.go | 42 +++++++----- common/common.go | 34 +++++++++- common/common_test.go | 66 +++++++++++++++++++ go.mod | 2 +- 6 files changed, 133 insertions(+), 41 deletions(-) diff --git a/cmd/argocd/commands/admin/cluster.go b/cmd/argocd/commands/admin/cluster.go index a04885b101522..48ee0254fd1b7 100644 --- a/cmd/argocd/commands/admin/cluster.go +++ b/cmd/argocd/commands/admin/cluster.go @@ -106,14 +106,9 @@ func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClie } redisOptions := &redis.Options{Addr: fmt.Sprintf("localhost:%d", port)} - - secret, err := kubeClient.CoreV1().Secrets(namespace).Get(context.Background(), defaulRedisInitialPasswordSecretName, v1.GetOptions{}) - if err == nil { - if _, ok := secret.Data[defaultResisInitialPasswordKey]; ok { - redisOptions.Password = string(secret.Data[defaultResisInitialPasswordKey]) - } + if err = common.SetOptionalRedisPasswordFromKubeConfig(ctx, kubeClient, namespace, redisOptions); err != nil { + log.Warnf("Failed to fetch & set redis password for namespace %s: %v", namespace, err) } - client := redis.NewClient(redisOptions) compressionType, err := cacheutil.CompressionTypeFromString(redisCompressionStr) if err != nil { diff --git a/cmd/argocd/commands/admin/redis_initial_password.go b/cmd/argocd/commands/admin/redis_initial_password.go index eddd915373b15..3f89b54010659 100644 --- a/cmd/argocd/commands/admin/redis_initial_password.go +++ b/cmd/argocd/commands/admin/redis_initial_password.go @@ -6,25 +6,18 @@ import ( "fmt" "math/big" + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" apierr "k8s.io/apimachinery/pkg/api/errors" - - "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - "github.com/argoproj/argo-cd/v2/util/cli" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" + "github.com/argoproj/argo-cd/v2/common" + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/argoproj/argo-cd/v2/util/cli" "github.com/argoproj/argo-cd/v2/util/errors" - - "github.com/spf13/cobra" - corev1 "k8s.io/api/core/v1" -) - -const ( - defaulRedisInitialPasswordSecretName = "argocd-redis" - defaultResisInitialPasswordKey = "auth" ) func generateRandomPassword() (string, error) { @@ -52,8 +45,8 @@ func NewRedisInitialPasswordCommand() *cobra.Command { namespace, _, err := clientConfig.Namespace() errors.CheckError(err) - redisInitialPasswordSecretName := defaulRedisInitialPasswordSecretName - redisInitialPasswordKey := defaultResisInitialPasswordKey + redisInitialPasswordSecretName := common.DefaultRedisInitialPasswordSecretName + redisInitialPasswordKey := common.DefaultRedisInitialPasswordKey fmt.Printf("Checking for initial Redis password in secret %s/%s at key %s. \n", namespace, redisInitialPasswordSecretName, redisInitialPasswordKey) config, err := clientConfig.ClientConfig() diff --git a/cmd/argocd/commands/headless/headless.go b/cmd/argocd/commands/headless/headless.go index d148e527abab4..b99ab9b7350f5 100644 --- a/cmd/argocd/commands/headless/headless.go +++ b/cmd/argocd/commands/headless/headless.go @@ -8,15 +8,11 @@ import ( "sync" "time" - "github.com/spf13/cobra" - - "github.com/argoproj/argo-cd/v2/cmd/argocd/commands/initialize" - "github.com/argoproj/argo-cd/v2/common" - "github.com/alicebob/miniredis/v2" "github.com/golang/protobuf/ptypes/empty" "github.com/redis/go-redis/v9" log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" "github.com/spf13/pflag" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/runtime" @@ -24,7 +20,10 @@ import ( cache2 "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/clientcmd" "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/argoproj/argo-cd/v2/cmd/argocd/commands/initialize" + "github.com/argoproj/argo-cd/v2/common" "github.com/argoproj/argo-cd/v2/pkg/apiclient" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned" @@ -241,20 +240,27 @@ func MaybeStartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOpti return fmt.Errorf("error running miniredis: %w", err) } appstateCache := appstatecache.NewCache(cache.NewCache(&forwardCacheClient{namespace: namespace, context: ctxStr, compression: compression, redisHaProxyName: clientOpts.RedisHaProxyName, redisName: clientOpts.RedisName}), time.Hour) + + redisOptions := &redis.Options{Addr: mr.Addr()} + if err = common.SetOptionalRedisPasswordFromKubeConfig(ctx, kubeClientset, namespace, redisOptions); err != nil { + log.Warnf("Failed to fetch & set redis password for namespace %s: %v", namespace, err) + } srv := server.NewServer(ctx, server.ArgoCDServerOpts{ - EnableGZip: false, - Namespace: namespace, - ListenPort: *port, - AppClientset: appClientset, - DisableAuth: true, - RedisClient: redis.NewClient(&redis.Options{Addr: mr.Addr()}), - Cache: servercache.NewCache(appstateCache, 0, 0, 0), - KubeClientset: kubeClientset, - Insecure: true, - ListenHost: *address, - RepoClientset: &forwardRepoClientset{namespace: namespace, context: ctxStr, repoServerName: clientOpts.RepoServerName, kubeClientset: kubeClientset}, - EnableProxyExtension: false, - }) + EnableGZip: false, + Namespace: namespace, + ListenPort: *port, + AppClientset: appClientset, + DisableAuth: true, + RedisClient: redis.NewClient(redisOptions), + Cache: servercache.NewCache(appstateCache, 0, 0, 0), + KubeClientset: kubeClientset, + DynamicClientset: dynamicClientset, + KubeControllerClientset: controllerClientset, + Insecure: true, + ListenHost: *address, + RepoClientset: &forwardRepoClientset{namespace: namespace, context: ctxStr, repoServerName: clientOpts.RepoServerName, kubeClientset: kubeClientset}, + EnableProxyExtension: false, + }, server.ApplicationSetOpts{}) srv.Init(ctx) lns, err := srv.Listen() diff --git a/common/common.go b/common/common.go index d984f0caedbb8..f095532a70833 100644 --- a/common/common.go +++ b/common/common.go @@ -1,15 +1,20 @@ package common import ( - "errors" + "context" + "fmt" "os" "path/filepath" "strconv" "time" + "github.com/pkg/errors" + "github.com/redis/go-redis/v9" "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" ) // Component names @@ -414,3 +419,30 @@ const TokenVerificationError = "failed to verify the token" var TokenVerificationErr = errors.New(TokenVerificationError) var PermissionDeniedAPIError = status.Error(codes.PermissionDenied, "permission denied") + +// Redis password consts +const ( + DefaultRedisInitialPasswordSecretName = "argocd-redis" + DefaultRedisInitialPasswordKey = "auth" +) + +/* +SetOptionalRedisPasswordFromKubeConfig sets the optional Redis password if it exists in the k8s namespace's secrets. + +We specify kubeClient as kubernetes.Interface to allow for mocking in tests, but this should be treated as a kubernetes.Clientset param. +*/ +func SetOptionalRedisPasswordFromKubeConfig(ctx context.Context, kubeClient kubernetes.Interface, namespace string, redisOptions *redis.Options) error { + secret, err := kubeClient.CoreV1().Secrets(namespace).Get(ctx, DefaultRedisInitialPasswordSecretName, v1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get secret %s/%s: %w", namespace, DefaultRedisInitialPasswordSecretName, err) + } + if secret == nil { + return fmt.Errorf("failed to get secret %s/%s: secret is nil", namespace, DefaultRedisInitialPasswordSecretName) + } + _, ok := secret.Data[DefaultRedisInitialPasswordKey] + if !ok { + return fmt.Errorf("secret %s/%s does not contain key %s", namespace, DefaultRedisInitialPasswordSecretName, DefaultRedisInitialPasswordKey) + } + redisOptions.Password = string(secret.Data[DefaultRedisInitialPasswordKey]) + return nil +} diff --git a/common/common_test.go b/common/common_test.go index 5632c1e7a78cc..1021a30a14f60 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -1,12 +1,18 @@ package common import ( + "context" "fmt" "os" "testing" "time" + "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubefake "k8s.io/client-go/kubernetes/fake" ) // Test env var not set for EnvGRPCKeepAliveMin @@ -44,3 +50,63 @@ func Test_GRPCKeepAliveMinIncorrectlySet(t *testing.T) { grpcKeepAliveTime := GetGRPCKeepAliveTime() assert.Equal(t, 2*grpcKeepAliveExpectedMin, grpcKeepAliveTime) } + +func TestSetOptionalRedisPasswordFromKubeConfig(t *testing.T) { + t.Parallel() + testCases := []struct { + name, namespace, expectedPassword, expectedErr string + secret *corev1.Secret + }{ + { + name: "Secret exists with correct key", + namespace: "default", + expectedPassword: "password123", + expectedErr: "", + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: DefaultRedisInitialPasswordSecretName}, + Data: map[string][]byte{DefaultRedisInitialPasswordKey: []byte("password123")}, + }, + }, + { + name: "Secret does not exist", + namespace: "default", + expectedPassword: "", + expectedErr: fmt.Sprintf("failed to get secret default/%s", DefaultRedisInitialPasswordSecretName), + secret: nil, + }, + { + name: "Secret exists without correct key", + namespace: "default", + expectedPassword: "", + expectedErr: fmt.Sprintf("secret default/%s does not contain key %s", DefaultRedisInitialPasswordSecretName, DefaultRedisInitialPasswordKey), + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: DefaultRedisInitialPasswordSecretName}, + Data: map[string][]byte{}, + }, + }, + } + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + var ( + ctx = context.TODO() + kubeClient = kubefake.NewSimpleClientset() + redisOptions = &redis.Options{} + ) + if tc.secret != nil { + if _, err := kubeClient.CoreV1().Secrets(tc.namespace).Create(ctx, tc.secret, metav1.CreateOptions{}); err != nil { + t.Fatalf("Failed to create secret: %v", err) + } + } + err := SetOptionalRedisPasswordFromKubeConfig(ctx, kubeClient, tc.namespace, redisOptions) + if tc.expectedErr != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectedErr) + } else { + require.NoError(t, err) + } + require.Equal(t, tc.expectedPassword, redisOptions.Password) + }) + } +} diff --git a/go.mod b/go.mod index 876dd5e33c076..ff8b6b0d4b5f1 100644 --- a/go.mod +++ b/go.mod @@ -250,7 +250,7 @@ require ( github.com/opsgenie/opsgenie-go-sdk-v2 v1.0.5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.5.0 github.com/prometheus/common v0.45.0 // indirect From 91b6cd6dfb21d21aa05cbe4a3c0cb41a0014319e Mon Sep 17 00:00:00 2001 From: Netanel Kadosh Date: Tue, 17 Sep 2024 19:18:40 +0300 Subject: [PATCH 2/2] fix: Add redis password to `forwardCacheClient` struct (#19599) Signed-off-by: Netanel Kadosh --- cmd/argocd/commands/headless/headless.go | 36 +++++++++++------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/cmd/argocd/commands/headless/headless.go b/cmd/argocd/commands/headless/headless.go index b99ab9b7350f5..c2c0596361904 100644 --- a/cmd/argocd/commands/headless/headless.go +++ b/cmd/argocd/commands/headless/headless.go @@ -20,7 +20,6 @@ import ( cache2 "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/clientcmd" "k8s.io/utils/ptr" - "sigs.k8s.io/controller-runtime/pkg/client" "github.com/argoproj/argo-cd/v2/cmd/argocd/commands/initialize" "github.com/argoproj/argo-cd/v2/common" @@ -47,6 +46,7 @@ type forwardCacheClient struct { err error redisHaProxyName string redisName string + redisPassword string } func (c *forwardCacheClient) doLazy(action func(client cache.CacheClient) error) error { @@ -63,7 +63,7 @@ func (c *forwardCacheClient) doLazy(action func(client cache.CacheClient) error) return } - redisClient := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("localhost:%d", redisPort)}) + redisClient := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("localhost:%d", redisPort), Password: c.redisPassword}) c.client = cache.NewRedisCache(redisClient, time.Hour, c.compression) }) if c.err != nil { @@ -239,28 +239,26 @@ func MaybeStartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOpti if err != nil { return fmt.Errorf("error running miniredis: %w", err) } - appstateCache := appstatecache.NewCache(cache.NewCache(&forwardCacheClient{namespace: namespace, context: ctxStr, compression: compression, redisHaProxyName: clientOpts.RedisHaProxyName, redisName: clientOpts.RedisName}), time.Hour) - redisOptions := &redis.Options{Addr: mr.Addr()} if err = common.SetOptionalRedisPasswordFromKubeConfig(ctx, kubeClientset, namespace, redisOptions); err != nil { log.Warnf("Failed to fetch & set redis password for namespace %s: %v", namespace, err) } + + appstateCache := appstatecache.NewCache(cache.NewCache(&forwardCacheClient{namespace: namespace, context: ctxStr, compression: compression, redisHaProxyName: clientOpts.RedisHaProxyName, redisName: clientOpts.RedisName, redisPassword: redisOptions.Password}), time.Hour) srv := server.NewServer(ctx, server.ArgoCDServerOpts{ - EnableGZip: false, - Namespace: namespace, - ListenPort: *port, - AppClientset: appClientset, - DisableAuth: true, - RedisClient: redis.NewClient(redisOptions), - Cache: servercache.NewCache(appstateCache, 0, 0, 0), - KubeClientset: kubeClientset, - DynamicClientset: dynamicClientset, - KubeControllerClientset: controllerClientset, - Insecure: true, - ListenHost: *address, - RepoClientset: &forwardRepoClientset{namespace: namespace, context: ctxStr, repoServerName: clientOpts.RepoServerName, kubeClientset: kubeClientset}, - EnableProxyExtension: false, - }, server.ApplicationSetOpts{}) + EnableGZip: false, + Namespace: namespace, + ListenPort: *port, + AppClientset: appClientset, + DisableAuth: true, + RedisClient: redis.NewClient(redisOptions), + Cache: servercache.NewCache(appstateCache, 0, 0, 0), + KubeClientset: kubeClientset, + Insecure: true, + ListenHost: *address, + RepoClientset: &forwardRepoClientset{namespace: namespace, context: ctxStr, repoServerName: clientOpts.RepoServerName, kubeClientset: kubeClientset}, + EnableProxyExtension: false, + }) srv.Init(ctx) lns, err := srv.Listen()