diff --git a/cmd/argocd/commands/admin/cluster.go b/cmd/argocd/commands/admin/cluster.go index 64c68002a86b5..3d0b20472674e 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 8fa1e70ad890e..adf48871f510f 100644 --- a/cmd/argocd/commands/admin/redis_initial_password.go +++ b/cmd/argocd/commands/admin/redis_initial_password.go @@ -6,23 +6,20 @@ import ( "fmt" "math/big" - "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - "github.com/argoproj/argo-cd/v2/util/cli" + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" apierr "k8s.io/apimachinery/pkg/api/errors" - - "github.com/argoproj/argo-cd/v2/util/errors" 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/spf13/cobra" - corev1 "k8s.io/api/core/v1" + "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" ) -const defaulRedisInitialPasswordSecretName = "argocd-redis" -const defaultResisInitialPasswordKey = "auth" - func generateRandomPassword() (string, error) { const initialPasswordLength = 16 const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-" @@ -50,8 +47,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 eca3cb0fb498a..0b7e2aa851acc 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" @@ -25,6 +21,8 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/utils/pointer" + "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" @@ -48,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 { @@ -64,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,14 +238,19 @@ 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(&redis.Options{Addr: mr.Addr()}), + RedisClient: redis.NewClient(redisOptions), Cache: servercache.NewCache(appstateCache, 0, 0, 0), KubeClientset: kubeClientset, Insecure: true, diff --git a/common/common.go b/common/common.go index f4b176946bcbd..ba3fb7cbddcfc 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 @@ -406,3 +411,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 f11970ca4e466..b7055a51a6712 100644 --- a/go.mod +++ b/go.mod @@ -240,7 +240,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.3.0 github.com/prometheus/common v0.42.0 // indirect