Skip to content

Commit 58aac51

Browse files
committed
feat: redis TLS encryption enabled by default for all connections
Assisted-by: Cursor Signed-off-by: Rizwana777 <rizwananaaz177@gmail.com>
1 parent 135740b commit 58aac51

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2509
-98
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ endif
7272
ifneq (${ARGOCD_AGENT_IN_CLUSTER},)
7373
./hack/dev-env/restart-all.sh
7474
endif
75+
./hack/dev-env/setup-e2e-enable-redis-tls.sh
7576

7677
.PHONY: teardown-e2e
7778
teardown-e2e:

agent/agent.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
"github.com/argoproj-labs/argocd-agent/internal/metrics"
4242
"github.com/argoproj-labs/argocd-agent/internal/queue"
4343
"github.com/argoproj-labs/argocd-agent/internal/resources"
44+
"github.com/argoproj-labs/argocd-agent/internal/tlsutil"
4445
"github.com/argoproj-labs/argocd-agent/internal/version"
4546
"github.com/argoproj-labs/argocd-agent/pkg/client"
4647
"github.com/argoproj-labs/argocd-agent/pkg/types"
@@ -171,6 +172,10 @@ func NewAgent(ctx context.Context, client *kube.KubernetesClient, namespace stri
171172
return nil, fmt.Errorf("remote not defined")
172173
}
173174

175+
if a.cacheRefreshInterval == 0 {
176+
return nil, fmt.Errorf("cache refresh interval not set")
177+
}
178+
174179
a.kubeClient = client
175180

176181
// Initial state of the agent is disconnected
@@ -330,7 +335,20 @@ func NewAgent(ctx context.Context, client *kube.KubernetesClient, namespace stri
330335
connMap: map[string]connectionEntry{},
331336
}
332337

333-
clusterCache, err := cluster.NewClusterCacheInstance(a.redisProxyMsgHandler.redisAddress, a.redisProxyMsgHandler.redisPassword, cacheutil.RedisCompressionGZip)
338+
// Create TLS config for cluster cache Redis client (same as for Redis proxy)
339+
clusterCacheTLSConfig, err := tlsutil.CreateRedisTLSConfig(
340+
a.redisProxyMsgHandler.redisTLSEnabled,
341+
a.redisProxyMsgHandler.redisAddress,
342+
a.redisProxyMsgHandler.redisTLSInsecure,
343+
a.redisProxyMsgHandler.redisTLSCA,
344+
a.redisProxyMsgHandler.redisTLSCAPath,
345+
"cluster cache",
346+
)
347+
if err != nil {
348+
return nil, err
349+
}
350+
351+
clusterCache, err := cluster.NewClusterCacheInstance(a.redisProxyMsgHandler.redisAddress, a.redisProxyMsgHandler.redisPassword, cacheutil.RedisCompressionGZip, clusterCacheTLSConfig)
334352
if err != nil {
335353
return nil, fmt.Errorf("failed to create cluster cache instance: %v", err)
336354
}

agent/agent_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"net/http"
2020
"net/http/httptest"
2121
"testing"
22+
"time"
2223

2324
"github.com/sirupsen/logrus"
2425
"k8s.io/apimachinery/pkg/runtime"
@@ -34,14 +35,14 @@ func newAgent(t *testing.T, apps ...runtime.Object) (*Agent, *kube.KubernetesCli
3435
kubec := fakekube.NewKubernetesFakeClientWithApps("argocd", apps...)
3536
remote, err := client.NewRemote("127.0.0.1", 8080)
3637
require.NoError(t, err)
37-
agent, err := NewAgent(context.TODO(), kubec, "argocd", WithRemote(remote))
38+
agent, err := NewAgent(context.TODO(), kubec, "argocd", WithRemote(remote), WithCacheRefreshInterval(10*time.Second))
3839
require.NoError(t, err)
3940
return agent, kubec
4041
}
4142

4243
func Test_NewAgent(t *testing.T) {
4344
kubec := fakekube.NewKubernetesFakeClientWithApps("agent")
44-
agent, err := NewAgent(context.TODO(), kubec, "agent", WithRemote(&client.Remote{}))
45+
agent, err := NewAgent(context.TODO(), kubec, "agent", WithRemote(&client.Remote{}), WithCacheRefreshInterval(10*time.Second))
4546
require.NotNil(t, agent)
4647
require.NoError(t, err)
4748
}

agent/filters_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package agent
1717
import (
1818
"context"
1919
"testing"
20+
"time"
2021

2122
"github.com/argoproj-labs/argocd-agent/internal/config"
2223
"github.com/argoproj-labs/argocd-agent/pkg/client"
@@ -32,7 +33,7 @@ func TestDefaultAppFilterChain_SkipSyncLabel(t *testing.T) {
3233
kubec := fakekube.NewKubernetesFakeClientWithApps("argocd")
3334
remote, err := client.NewRemote("127.0.0.1", 8080)
3435
require.NoError(t, err)
35-
agent, err := NewAgent(context.TODO(), kubec, "argocd", WithRemote(remote))
36+
agent, err := NewAgent(context.TODO(), kubec, "argocd", WithRemote(remote), WithCacheRefreshInterval(10*time.Second))
3637
require.NoError(t, err)
3738

3839
filterChain := agent.DefaultAppFilterChain()
@@ -152,7 +153,8 @@ func TestDefaultAppFilterChain_NamespaceAndSkipSyncInteraction(t *testing.T) {
152153
// Create agent with multiple allowed namespaces
153154
agent, err := NewAgent(context.TODO(), kubec, "argocd",
154155
WithRemote(remote),
155-
WithAllowedNamespaces("argocd", "apps", "staging"))
156+
WithAllowedNamespaces("argocd", "apps", "staging"),
157+
WithCacheRefreshInterval(10*time.Second))
156158
require.NoError(t, err)
157159

158160
filterChain := agent.DefaultAppFilterChain()
@@ -216,7 +218,7 @@ func TestDefaultAppFilterChain_ProcessChange(t *testing.T) {
216218
kubec := fakekube.NewKubernetesFakeClientWithApps("argocd")
217219
remote, err := client.NewRemote("127.0.0.1", 8080)
218220
require.NoError(t, err)
219-
agent, err := NewAgent(context.TODO(), kubec, "argocd", WithRemote(remote))
221+
agent, err := NewAgent(context.TODO(), kubec, "argocd", WithRemote(remote), WithCacheRefreshInterval(10*time.Second))
220222
require.NoError(t, err)
221223

222224
filterChain := agent.DefaultAppFilterChain()

agent/inbound_redis.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ package agent
1616

1717
import (
1818
"context"
19-
"crypto/tls"
19+
"crypto/x509"
2020
"errors"
2121
"fmt"
2222
"strings"
@@ -25,6 +25,7 @@ import (
2525

2626
"github.com/argoproj-labs/argocd-agent/internal/event"
2727
"github.com/argoproj-labs/argocd-agent/internal/logging/logfields"
28+
"github.com/argoproj-labs/argocd-agent/internal/tlsutil"
2829
rediscache "github.com/go-redis/cache/v9"
2930
"github.com/redis/go-redis/v9"
3031
"github.com/redis/go-redis/v9/maintnotifications"
@@ -45,6 +46,12 @@ type redisProxyMsgHandler struct {
4546

4647
// connections maintains statistics about redis connections from principal
4748
connections *connectionEntries
49+
50+
// Redis TLS configuration
51+
redisTLSEnabled bool
52+
redisTLSCAPath string
53+
redisTLSCA *x509.CertPool // CA cert pool loaded from secret
54+
redisTLSInsecure bool
4855
}
4956

5057
// connectionEntries maintains statistics about redis connections from principal
@@ -333,7 +340,18 @@ func stripNamespaceFromRedisKey(key string, logCtx *logrus.Entry) (string, error
333340
}
334341

335342
func (a *Agent) getRedisClientAndCache() (*redis.Client, *rediscache.Cache, error) {
336-
var tlsConfig *tls.Config = nil
343+
// Create TLS config for Redis client
344+
tlsConfig, err := tlsutil.CreateRedisTLSConfig(
345+
a.redisProxyMsgHandler.redisTLSEnabled,
346+
a.redisProxyMsgHandler.redisAddress,
347+
a.redisProxyMsgHandler.redisTLSInsecure,
348+
a.redisProxyMsgHandler.redisTLSCA,
349+
a.redisProxyMsgHandler.redisTLSCAPath,
350+
"Redis client",
351+
)
352+
if err != nil {
353+
return nil, nil, err
354+
}
337355

338356
opts := &redis.Options{
339357
Addr: a.redisProxyMsgHandler.redisAddress,

agent/options.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515
package agent
1616

1717
import (
18+
"context"
1819
"fmt"
1920
"time"
2021

22+
"k8s.io/client-go/kubernetes"
23+
24+
"github.com/argoproj-labs/argocd-agent/internal/tlsutil"
2125
"github.com/argoproj-labs/argocd-agent/pkg/client"
2226
"github.com/argoproj-labs/argocd-agent/pkg/types"
2327
)
@@ -114,3 +118,39 @@ func WithHeartbeatInterval(interval time.Duration) AgentOption {
114118
return nil
115119
}
116120
}
121+
122+
// WithRedisTLSEnabled enables or disables TLS for Redis connections
123+
func WithRedisTLSEnabled(enabled bool) AgentOption {
124+
return func(o *Agent) error {
125+
o.redisProxyMsgHandler.redisTLSEnabled = enabled
126+
return nil
127+
}
128+
}
129+
130+
// WithRedisTLSCAPath sets the CA certificate path for Redis TLS
131+
func WithRedisTLSCAPath(caPath string) AgentOption {
132+
return func(o *Agent) error {
133+
o.redisProxyMsgHandler.redisTLSCAPath = caPath
134+
return nil
135+
}
136+
}
137+
138+
// WithRedisTLSCAFromSecret loads the CA certificate from a Kubernetes secret for Redis TLS
139+
func WithRedisTLSCAFromSecret(kube kubernetes.Interface, namespace, name, field string) AgentOption {
140+
return func(o *Agent) error {
141+
pool, err := tlsutil.X509CertPoolFromSecret(context.Background(), kube, namespace, name, field)
142+
if err != nil {
143+
return err
144+
}
145+
o.redisProxyMsgHandler.redisTLSCA = pool
146+
return nil
147+
}
148+
}
149+
150+
// WithRedisTLSInsecure enables insecure Redis TLS (for testing only)
151+
func WithRedisTLSInsecure(insecure bool) AgentOption {
152+
return func(o *Agent) error {
153+
o.redisProxyMsgHandler.redisTLSInsecure = insecure
154+
return nil
155+
}
156+
}

agent/outbound_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package agent
33
import (
44
"context"
55
"testing"
6+
"time"
67

78
"github.com/alicebob/miniredis/v2"
89
"github.com/argoproj-labs/argocd-agent/internal/argocd/cluster"
@@ -454,14 +455,14 @@ func Test_addClusterCacheInfoUpdateToQueue(t *testing.T) {
454455
remote, err := client.NewRemote("127.0.0.1", 8080)
455456
require.NoError(t, err)
456457

457-
a, err := NewAgent(context.TODO(), kubec, "argocd", WithRemote(remote), WithRedisHost(miniRedis.Addr()))
458+
a, err := NewAgent(context.TODO(), kubec, "argocd", WithRemote(remote), WithRedisHost(miniRedis.Addr()), WithCacheRefreshInterval(10*time.Second))
458459
require.NoError(t, err)
459460

460461
a.remote.SetClientID("agent")
461462
a.emitter = event.NewEventSource("principal")
462463

463464
// First populate the cache with dummy data
464-
clusterMgr, err := cluster.NewManager(a.context, a.namespace, miniRedis.Addr(), "", cacheutil.RedisCompressionGZip, a.kubeClient.Clientset)
465+
clusterMgr, err := cluster.NewManager(a.context, a.namespace, miniRedis.Addr(), "", cacheutil.RedisCompressionGZip, a.kubeClient.Clientset, nil)
465466
require.NoError(t, err)
466467
err = clusterMgr.MapCluster("test-agent", &v1alpha1.Cluster{
467468
Name: "test-cluster",

cmd/argocd-agent/agent.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ func NewAgentRunCommand() *cobra.Command {
8080
// OpenTelemetry configuration
8181
otlpAddress string
8282
otlpInsecure bool
83+
// Redis TLS configuration
84+
redisTLSEnabled bool
85+
redisTLSCAPath string
86+
redisTLSCASecretName string
87+
redisTLSInsecure bool
8388
)
8489
command := &cobra.Command{
8590
Use: "agent",
@@ -207,6 +212,40 @@ func NewAgentRunCommand() *cobra.Command {
207212
agentOpts = append(agentOpts, agent.WithRedisUsername(redisUsername))
208213
agentOpts = append(agentOpts, agent.WithRedisPassword(redisPassword))
209214

215+
// Configure Redis TLS
216+
agentOpts = append(agentOpts, agent.WithRedisTLSEnabled(redisTLSEnabled))
217+
if redisTLSEnabled {
218+
// Validate Redis TLS configuration - only one mode can be specified
219+
// This validation works for both CLI flags and environment variables
220+
modesSet := 0
221+
if redisTLSInsecure {
222+
modesSet++
223+
}
224+
if redisTLSCAPath != "" {
225+
modesSet++
226+
}
227+
// For secret name: count it if explicitly set (CLI) or if set to non-default value (env var)
228+
// This allows the default secret name to be used as a fallback when no mode is explicitly specified
229+
if c.Flags().Changed("redis-tls-ca-secret-name") || (redisTLSCASecretName != "" && redisTLSCASecretName != "argocd-redis-tls") {
230+
modesSet++
231+
}
232+
if modesSet > 1 {
233+
cmdutil.Fatal("Only one Redis TLS mode can be specified: --redis-tls-insecure, --redis-tls-ca-path, or --redis-tls-ca-secret-name")
234+
}
235+
236+
// Redis TLS (for connections to agent's argocd-redis)
237+
if redisTLSInsecure {
238+
logrus.Warn("INSECURE: Not verifying Redis TLS certificate")
239+
agentOpts = append(agentOpts, agent.WithRedisTLSInsecure(true))
240+
} else if redisTLSCAPath != "" {
241+
logrus.Infof("Loading Redis CA certificate from file %s", redisTLSCAPath)
242+
agentOpts = append(agentOpts, agent.WithRedisTLSCAPath(redisTLSCAPath))
243+
} else {
244+
logrus.Infof("Loading Redis CA certificate from secret %s/%s", namespace, redisTLSCASecretName)
245+
agentOpts = append(agentOpts, agent.WithRedisTLSCAFromSecret(kubeConfig.Clientset, namespace, redisTLSCASecretName, "ca.crt"))
246+
}
247+
}
248+
210249
agentOpts = append(agentOpts, agent.WithEnableResourceProxy(enableResourceProxy))
211250
agentOpts = append(agentOpts, agent.WithCacheRefreshInterval(cacheRefreshInterval))
212251
agentOpts = append(agentOpts, agent.WithHeartbeatInterval(heartbeatInterval))
@@ -248,6 +287,20 @@ func NewAgentRunCommand() *cobra.Command {
248287
env.StringWithDefault("REDIS_PASSWORD", nil, ""),
249288
"The password to connect to redis with")
250289

290+
// Redis TLS flags
291+
command.Flags().BoolVar(&redisTLSEnabled, "redis-tls-enabled",
292+
env.BoolWithDefault("ARGOCD_AGENT_REDIS_TLS_ENABLED", true),
293+
"Enable TLS for Redis connections (enabled by default for security)")
294+
command.Flags().StringVar(&redisTLSCAPath, "redis-tls-ca-path",
295+
env.StringWithDefault("ARGOCD_AGENT_REDIS_TLS_CA_PATH", nil, ""),
296+
"Path to CA certificate for Redis TLS (for local development)")
297+
command.Flags().StringVar(&redisTLSCASecretName, "redis-tls-ca-secret-name",
298+
env.StringWithDefault("ARGOCD_AGENT_REDIS_TLS_CA_SECRET_NAME", nil, "argocd-redis-tls"),
299+
"Secret name containing CA certificate for Redis TLS (for production deployment)")
300+
command.Flags().BoolVar(&redisTLSInsecure, "redis-tls-insecure",
301+
env.BoolWithDefault("ARGOCD_AGENT_REDIS_TLS_INSECURE", false),
302+
"INSECURE: Do not verify Redis TLS certificate")
303+
251304
command.Flags().StringVar(&logFormat, "log-format",
252305
env.StringWithDefault("ARGOCD_PRINCIPAL_LOG_FORMAT", nil, "text"),
253306
"The log format to use (one of: text, json)")

0 commit comments

Comments
 (0)