Skip to content

Commit 15b752f

Browse files
committed
feat: redis TLS encryption enabled by default for all connections
Assisted-by: Cursor Signed-off-by: Rizwana777 <[email protected]>
1 parent c878f38 commit 15b752f

Some content is hidden

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

44 files changed

+2511
-97
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ endif
8686
ifneq (${ARGOCD_AGENT_IN_CLUSTER},)
8787
./hack/dev-env/restart-all.sh
8888
endif
89+
./hack/dev-env/setup-e2e-enable-redis-tls.sh
8990

9091
.PHONY: teardown-e2e
9192
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"
@@ -191,6 +192,10 @@ func NewAgent(ctx context.Context, client *kube.KubernetesClient, namespace stri
191192
return nil, fmt.Errorf("remote not defined")
192193
}
193194

195+
if a.cacheRefreshInterval == 0 {
196+
return nil, fmt.Errorf("cache refresh interval not set")
197+
}
198+
194199
a.kubeClient = client
195200

196201
// Initial state of the agent is disconnected
@@ -353,7 +358,20 @@ func NewAgent(ctx context.Context, client *kube.KubernetesClient, namespace stri
353358
connMap: map[string]connectionEntry{},
354359
}
355360

356-
clusterCache, err := cluster.NewClusterCacheInstance(a.redisProxyMsgHandler.redisAddress, a.redisProxyMsgHandler.redisPassword, cacheutil.RedisCompressionGZip)
361+
// Create TLS config for cluster cache Redis client (same as for Redis proxy)
362+
clusterCacheTLSConfig, err := tlsutil.CreateRedisTLSConfig(
363+
a.redisProxyMsgHandler.redisTLSEnabled,
364+
a.redisProxyMsgHandler.redisAddress,
365+
a.redisProxyMsgHandler.redisTLSInsecure,
366+
a.redisProxyMsgHandler.redisTLSCA,
367+
a.redisProxyMsgHandler.redisTLSCAPath,
368+
"cluster cache",
369+
)
370+
if err != nil {
371+
return nil, err
372+
}
373+
374+
clusterCache, err := cluster.NewClusterCacheInstance(a.redisProxyMsgHandler.redisAddress, a.redisProxyMsgHandler.redisPassword, cacheutil.RedisCompressionGZip, clusterCacheTLSConfig)
357375
if err != nil {
358376
return nil, fmt.Errorf("failed to create cluster cache instance: %v", err)
359377
}

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
@@ -328,7 +335,18 @@ func stripNamespaceFromRedisKey(key string, logCtx *logrus.Entry) (string, error
328335
}
329336

330337
func (a *Agent) getRedisClientAndCache() (*redis.Client, *rediscache.Cache, error) {
331-
var tlsConfig *tls.Config = nil
338+
// Create TLS config for Redis client
339+
tlsConfig, err := tlsutil.CreateRedisTLSConfig(
340+
a.redisProxyMsgHandler.redisTLSEnabled,
341+
a.redisProxyMsgHandler.redisAddress,
342+
a.redisProxyMsgHandler.redisTLSInsecure,
343+
a.redisProxyMsgHandler.redisTLSCA,
344+
a.redisProxyMsgHandler.redisTLSCAPath,
345+
"Redis client",
346+
)
347+
if err != nil {
348+
return nil, nil, err
349+
}
332350

333351
opts := &redis.Options{
334352
Addr: a.redisProxyMsgHandler.redisAddress,

agent/options.go

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

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

2122
"github.com/argoproj-labs/argocd-agent/internal/logging"
23+
"k8s.io/client-go/kubernetes"
24+
25+
"github.com/argoproj-labs/argocd-agent/internal/tlsutil"
2226
"github.com/argoproj-labs/argocd-agent/pkg/client"
2327
"github.com/argoproj-labs/argocd-agent/pkg/types"
2428
"github.com/sirupsen/logrus"
@@ -133,3 +137,39 @@ func WithSubsystemLoggers(resourceProxy, redisProxy, grpcEvent *logrus.Logger) A
133137
return nil
134138
}
135139
}
140+
141+
// WithRedisTLSEnabled enables or disables TLS for Redis connections
142+
func WithRedisTLSEnabled(enabled bool) AgentOption {
143+
return func(o *Agent) error {
144+
o.redisProxyMsgHandler.redisTLSEnabled = enabled
145+
return nil
146+
}
147+
}
148+
149+
// WithRedisTLSCAPath sets the CA certificate path for Redis TLS
150+
func WithRedisTLSCAPath(caPath string) AgentOption {
151+
return func(o *Agent) error {
152+
o.redisProxyMsgHandler.redisTLSCAPath = caPath
153+
return nil
154+
}
155+
}
156+
157+
// WithRedisTLSCAFromSecret loads the CA certificate from a Kubernetes secret for Redis TLS
158+
func WithRedisTLSCAFromSecret(kube kubernetes.Interface, namespace, name, field string) AgentOption {
159+
return func(o *Agent) error {
160+
pool, err := tlsutil.X509CertPoolFromSecret(context.Background(), kube, namespace, name, field)
161+
if err != nil {
162+
return err
163+
}
164+
o.redisProxyMsgHandler.redisTLSCA = pool
165+
return nil
166+
}
167+
}
168+
169+
// WithRedisTLSInsecure enables insecure Redis TLS (for testing only)
170+
func WithRedisTLSInsecure(insecure bool) AgentOption {
171+
return func(o *Agent) error {
172+
o.redisProxyMsgHandler.redisTLSInsecure = insecure
173+
return nil
174+
}
175+
}

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: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ func NewAgentRunCommand() *cobra.Command {
8383
// OpenTelemetry configuration
8484
otlpAddress string
8585
otlpInsecure bool
86+
// Redis TLS configuration
87+
redisTLSEnabled bool
88+
redisTLSCAPath string
89+
redisTLSCASecretName string
90+
redisTLSInsecure bool
8691
)
8792
command := &cobra.Command{
8893
Use: "agent",
@@ -175,7 +180,7 @@ func NewAgentRunCommand() *cobra.Command {
175180
remoteOpts = append(remoteOpts, client.WithRootAuthoritiesFromFile(rootCAPath))
176181
} else {
177182
logrus.Infof("Loading root CA certificate from secret %s/%s", namespace, rootCASecretName)
178-
remoteOpts = append(remoteOpts, client.WithRootAuthoritiesFromSecret(kubeConfig.Clientset, namespace, rootCASecretName, ""))
183+
remoteOpts = append(remoteOpts, client.WithRootAuthoritiesFromSecret(kubeConfig.Clientset, namespace, rootCASecretName, "tls.crt"))
179184
}
180185

181186
// If both a certificate and a key are specified on the command
@@ -229,6 +234,40 @@ func NewAgentRunCommand() *cobra.Command {
229234
agentOpts = append(agentOpts, agent.WithRedisUsername(redisUsername))
230235
agentOpts = append(agentOpts, agent.WithRedisPassword(redisPassword))
231236

237+
// Configure Redis TLS
238+
agentOpts = append(agentOpts, agent.WithRedisTLSEnabled(redisTLSEnabled))
239+
if redisTLSEnabled {
240+
// Validate Redis TLS configuration - only one mode can be specified
241+
// This validation works for both CLI flags and environment variables
242+
modesSet := 0
243+
if redisTLSInsecure {
244+
modesSet++
245+
}
246+
if redisTLSCAPath != "" {
247+
modesSet++
248+
}
249+
// For secret name: count it if explicitly set (CLI) or if set to non-default value (env var)
250+
// This allows the default secret name to be used as a fallback when no mode is explicitly specified
251+
if c.Flags().Changed("redis-tls-ca-secret-name") || (redisTLSCASecretName != "" && redisTLSCASecretName != "argocd-redis-tls") {
252+
modesSet++
253+
}
254+
if modesSet > 1 {
255+
cmdutil.Fatal("Only one Redis TLS mode can be specified: --redis-tls-insecure, --redis-tls-ca-path, or --redis-tls-ca-secret-name")
256+
}
257+
258+
// Redis TLS (for connections to agent's argocd-redis)
259+
if redisTLSInsecure {
260+
logrus.Warn("INSECURE: Not verifying Redis TLS certificate")
261+
agentOpts = append(agentOpts, agent.WithRedisTLSInsecure(true))
262+
} else if redisTLSCAPath != "" {
263+
logrus.Infof("Loading Redis CA certificate from file %s", redisTLSCAPath)
264+
agentOpts = append(agentOpts, agent.WithRedisTLSCAPath(redisTLSCAPath))
265+
} else {
266+
logrus.Infof("Loading Redis CA certificate from secret %s/%s", namespace, redisTLSCASecretName)
267+
agentOpts = append(agentOpts, agent.WithRedisTLSCAFromSecret(kubeConfig.Clientset, namespace, redisTLSCASecretName, "ca.crt"))
268+
}
269+
}
270+
232271
agentOpts = append(agentOpts, agent.WithEnableResourceProxy(enableResourceProxy))
233272
agentOpts = append(agentOpts, agent.WithCacheRefreshInterval(cacheRefreshInterval))
234273
agentOpts = append(agentOpts, agent.WithHeartbeatInterval(heartbeatInterval))
@@ -270,6 +309,20 @@ func NewAgentRunCommand() *cobra.Command {
270309
env.StringWithDefault("REDIS_PASSWORD", nil, ""),
271310
"The password to connect to redis with")
272311

312+
// Redis TLS flags
313+
command.Flags().BoolVar(&redisTLSEnabled, "redis-tls-enabled",
314+
env.BoolWithDefault("ARGOCD_AGENT_REDIS_TLS_ENABLED", true),
315+
"Enable TLS for Redis connections (enabled by default for security)")
316+
command.Flags().StringVar(&redisTLSCAPath, "redis-tls-ca-path",
317+
env.StringWithDefault("ARGOCD_AGENT_REDIS_TLS_CA_PATH", nil, ""),
318+
"Path to CA certificate for Redis TLS (for local development)")
319+
command.Flags().StringVar(&redisTLSCASecretName, "redis-tls-ca-secret-name",
320+
env.StringWithDefault("ARGOCD_AGENT_REDIS_TLS_CA_SECRET_NAME", nil, "argocd-redis-tls"),
321+
"Secret name containing CA certificate for Redis TLS (for production deployment)")
322+
command.Flags().BoolVar(&redisTLSInsecure, "redis-tls-insecure",
323+
env.BoolWithDefault("ARGOCD_AGENT_REDIS_TLS_INSECURE", false),
324+
"INSECURE: Do not verify Redis TLS certificate")
325+
273326
command.Flags().StringVar(&logFormat, "log-format",
274327
env.StringWithDefault("ARGOCD_PRINCIPAL_LOG_FORMAT", nil, "text"),
275328
"The log format to use (one of: text, json)")

0 commit comments

Comments
 (0)