Repository: argoproj-labs/argocd-operator
Pull request #647 (2022-05-11 17:49:00)

feat: Secure communication with redis
**What type of PR is this?**
/kind enhancement

**What does this PR do / why we need it**:

This PR adds support to watch for changes to the `argocd-operator-redis-tls` secret, which should contain the TLS configuration (cert/key combination) that the argocd components should use to communicate with the redis server. If a change is detected, the appropriate workloads will be restarted.  Note that this secret is something defined by the operator and not by Argo CD itself.

The PR adds two new settings
-  `.spec.redis.disableTLSVerification: <bool>`: if set to `true`, the application controller, the repo server and the api server will not validate the TLS certificate used by the redis server (by setting the `--redis-insecure-skip-tls-verify` flag when starting these components).
- `.spec.redis.autotls: <string>`: Can be set to a provider which does automatic provisioning of the `argocd-operator-redis-tls` secret. Currently, the only provider supported is openshift 

If the `argocd-operator-redis-tls` secret was created using the AutoTLS setting, no further configuration will be necessary. If the secret was manually added to the namespace, it will require an annotation argocds.argoproj.io/name: <instance_name> to be successfully mapped back to the managing ArgoCD instance.

OpenShift Container Platform provides a cluster-internal CA to issue service certificates, as described [here](https://docs.openshift.com/container-platform/4.10/security/certificates/service-serving-certificate.html). If .spec.redis.autotls: openshift is specified, the controller will add the appropriate annotation to the redis servers Service in the instance's namespace, which indicates that a TLS certificate for this service should be issued by the service CA.

**Have you updated the necessary documentation?**

* [x] Documentation update is required by this PR.
* [x] Documentation has been updated.

**How to test changes / Special notes to the reviewer**:

### To test the enablement of TLS for Redis when autotls is disabled

- Create an Argo CD CR with default values
```yaml
apiVersion: argoproj.io/v1alpha1
 kind: ArgoCD
 metadata:
   name: argocd
```
- Wait until reconciliation is complete and workloads became available
- Create a self-signed certificate for the redis server. The cert will be stored in `/tmp/redis.crt`, and the key will be stored in `/tmp/redis.key`.  Set the environment variable `NAMESPACE` to the namespace where argocd is running.
```bash
openssl req -new -x509 -sha256 \
  -subj "/C=XX/ST=XX/O=Testing/CN=redis" \
  -reqexts SAN -extensions SAN \
  -config <(printf "\n[SAN]\nsubjectAltName=DNS:argocd-redis.$NAMESPACE.svc.cluster.local\n[req]\ndistinguished_name=req") \
  -keyout /tmp/redis.key \
  -out /tmp/redis.crt \
  -newkey rsa:4096 \
  -nodes \
  -sha256 \
  -days 10
```
- Create the `argocd-operator-redis-tls` secret
```bash
kubectl create secret tls argocd-operator-redis-tls --key=/tmp/redis.key --cert=/tmp/redis.crt
```
- Check that the pods for the `argocd-server`, `argocd-repo-server`, `argocd-redis` and `argocd-application-controller` were **not** restarted yet
- Annotate the secret to indicate it belongs to the `argocd` operand
```bash
kubectl annotate secret argocd-operator-redis-tls argocds.argoproj.io/name=argocd
```
- Check that the operator reconciled and the `argocd-server`, `argocd-repo-server`, `argocd-redis` and `argocd-application-controller` have been restarted.

- Check that the redis server has been started with the correct arguments.  Run
```bash
kubectl get deployments.apps argocd-redis -o jsonpath='{.spec.template.spec.containers[0].args}'
```
- The arguments should include
  - "--tls-port","6379"
  - "--port","0"
  - "--tls-cert-file","/app/config/redis/tls/tls.crt"
  - "--tls-key-file","/app/config/redis/tls/tls.key"
  - "--tls-auth-clients","no"

- Check that the repo server has been started with the correct arguments. Run
```bash
kubectl get deployments.apps argocd-repo-server -o jsonpath='{.spec.template.spec.containers[0].command}'
```
- The arguments should include
  - "--redis-use-tls"
  - "--redis-ca-certificate","/app/config/reposerver/tls/redis/tls.crt"

- Check that the api server has been started with the correct arguments. Run
```bash
kubectl get deployments.apps argocd-server -o jsonpath='{.spec.template.spec.containers[0].command}' 
```
- The arguments should include
  - "--redis-use-tls"
  - "--redis-ca-certificate","/app/config/server/tls/redis/tls.crt"

- Check that the application controller has been started with the correct arguments. Run
```bash
kubectl get statefulsets.apps argocd-application-controller -o jsonpath='{.spec.template.spec.containers[0].command}'
```
- The arguments should include
  - "--redis-use-tls"
  - "--redis-ca-certificate","/app/config/controller/tls/redis/tls.crt"

### To test the enablement of TLS for Redis when HA is enabled and autotls is disabled

**Note that this requires running on a cluster with at least three worker nodes.**

- Create an Argo CD CR with which enables HA
```yaml
apiVersion: argoproj.io/v1alpha1
 kind: ArgoCD
 metadata:
   name: argocd
spec:
  ha:
    enabled: true
```
- Wait until reconciliation is complete and workloads became available.  This may take many minutes
- Create a self-signed certificate for the redis server. Note that this cert is different from the one for non-HA mode. The cert will be stored in `/tmp/redis-ha.crt`, and the key will be stored in `/tmp/redis-ha.key`.  Set the environment variable `NAMESPACE` to the namespace where argocd is running.
```bash
openssl req -new -x509 -sha256 \
  -subj "/C=XX/ST=XX/O=Testing/CN=redis" \
  -reqexts SAN -extensions SAN \
  -config <(printf "\n[SAN]\nsubjectAltName=DNS:argocd-redis-ha-haproxy.$NAMESPACE.svc.cluster.local\n[req]\ndistinguished_name=req") \
  -keyout /tmp/redis-ha.key \
  -out /tmp/redis-ha.crt \
  -newkey rsa:4096 \
  -nodes \
  -sha256 \
  -days 10
```
- Create the `argocd-operator-redis-tls` secret
```bash
kubectl create secret tls argocd-operator-redis-tls --key=/tmp/redis-ha.key --cert=/tmp/redis-ha.crt
```
- Check that the pods for the `argocd-server`, `argocd-repo-server`, `argocd-redis-ha-server-0`, `argocd-redis-ha-server-1`, `argocd-redis-ha-server-2`, `argocd-redis-ha-haproxy` and `argocd-application-controller` were **not** restarted yet
- Annotate the secret to indicate it belongs to the `argocd` operand
```bash
kubectl annotate secret argocd-operator-redis-tls argocds.argoproj.io/name=argocd
```
- Check that the operator reconciled and the `argocd-server`, `argocd-repo-server`, `argocd-redis-ha-server-0`, `argocd-redis-ha-server-1`, `argocd-redis-ha-server-2`, `argocd-redis-ha-haproxy` and `argocd-application-controller` pods have restarted. It may take many minutes for the pods to restart.

- Check that the configuration for the redis servers is correct. Run
```bash
kubectl exec -ti argocd-redis-ha-server-0 -c redis -- sh
```
- Examine the `redis.conf` file.  Run `less conf/redis.conf`
- The file should include the following lines
```
port 0
tls-port 6379
tls-cert-file /app/config/redis/tls/tls.crt
tls-ca-cert-file /app/config/redis/tls/tls.crt
tls-key-file /app/config/redis/tls/tls.key
tls-replication yes
tls-auth-clients no
```
- Examine the `sentinel.conf` file.  Run `less conf/sentinel.conf`
- The file should include the following lines
```
port 0
tls-port 26379
tls-cert-file "/app/config/redis/tls/tls.crt"
tls-ca-cert-file "/app/config/redis/tls/tls.crt"
tls-key-file "/app/config/redis/tls/tls.key"
tls-replication yes
tls-auth-clients no
```
- Check that the repo server has been started with the correct arguments. Run
```bash
kubectl get deployments.apps argocd-repo-server -o jsonpath='{.spec.template.spec.containers[0].command}'
```
- The arguments should include
  - "--redis-use-tls"
  - "--redis-ca-certificate","/app/config/reposerver/tls/redis/tls.crt"

- Check that the api server has been started with the correct arguments. Run
```bash
kubectl get deployments.apps argocd-server -o jsonpath='{.spec.template.spec.containers[0].command}' 
```
- The arguments should include
  - "--redis-use-tls"
  - "--redis-ca-certificate","/app/config/server/tls/redis/tls.crt"

- Check that the application controller has been started with the correct arguments. Run
```bash
kubectl get statefulsets.apps argocd-application-controller -o jsonpath='{.spec.template.spec.containers[0].command}'
```
- The arguments should include
  - "--redis-use-tls"
  - "--redis-ca-certificate","/app/config/controller/tls/redis/tls.crt"

### To test the enablement of TLS for Redis when autotls is enabled (only works on OpenShift)

- Create an Argo CD CR with default values
```yaml
apiVersion: argoproj.io/v1alpha1
 kind: ArgoCD
 metadata:
   name: argocd
```
- Wait until reconciliation is complete and workloads became available
- Patch the ArgoCD CR to set `.spec.redis.autotls` to `openshift`
```bash
kubectl patch argocds.argoproj.io argocd --type=merge -p '{"spec":{"redis":{"autotls":"openshift"}}}'
```
- Check that a secret called `argocd-operator-redis-tls` was created by the CA's controller.
```bash
kubectl get secrets argocd-operator-redis-tls
```
- The secret should be of type `kubernetes.io/tls` and should have a size of `2`
- Check that the operator reconciled and the `argocd-server`, `argocd-repo-server`, `argocd-redis` and `argocd-application-controller` have been restarted.
- For each of the `argocd-server`, `argocd-repo-server`, `argocd-redis` and `argocd-application-controller` pods, do a `kubectl exec -it <podname> -- sh` and look for the following files to exist there:
  - argocd-server: /app/config/server/tls/redis/tls.crt
  - argocd-repo-server: /app/config/reposerver/tls/redis/tls.crt
  - argocd-redis-server: /app/config/redis/tls/tls.crt
  - argocd-application-controller: /app/config/controller/tls/redis/tls.crt

### To test the enablement of TLS for Redis when HA is enabled and autotls is enabled (only works on OpenShift)

**Note that this requires running on a cluster with at least three worker nodes.**

- Create an Argo CD CR with which enables HA
```yaml
apiVersion: argoproj.io/v1alpha1
 kind: ArgoCD
 metadata:
   name: argocd
spec:
  ha:
    enabled: true
```
- Wait until reconciliation is complete and workloads became available.  This may take many minutes
- Patch the ArgoCD CR to set `.spec.redis.autotls` to `openshift`
```bash
kubectl patch argocds.argoproj.io argocd --type=merge -p '{"spec":{"redis":{"autotls":"openshift"}}}'
```
- Check that a secret called `argocd-operator-redis-tls` was created by the CA's controller.
```bash
kubectl get secrets argocd-operator-redis-tls
```
- The secret should be of type `kubernetes.io/tls` and should have a size of `2`
- Check that the operator reconciled and the `argocd-server`, `argocd-repo-server`, `argocd-redis-ha-server-0`, `argocd-redis-ha-server-1`, `argocd-redis-ha-server-2`, `argocd-redis-ha-haproxy` and `argocd-application-controller` pods have restarted. It may take many minutes for the pods to restart.


Patch:
diff --git a/api/v1alpha1/argocd_types.go b/api/v1alpha1/argocd_types.go
index d3a051d1..fb0771cf 100644
--- a/api/v1alpha1/argocd_types.go
+++ b/api/v1alpha1/argocd_types.go
@@ -327,6 +327,14 @@ type ArgoCDRedisSpec struct {
 	// Version is the Redis container image tag.
 	//+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Version",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:fieldGroup:Redis","urn:alm:descriptor:com.tectonic.ui:text"}
 	Version string `json:"version,omitempty"`
+
+	// DisableTLSVerification defines whether redis server API should be accessed using strict TLS validation
+	DisableTLSVerification bool `json:"disableTLSVerification,omitempty"`
+
+	// AutoTLS specifies the method to use for automatic TLS configuration for the redis server
+	// The value specified here can currently be:
+	// - openshift - Use the OpenShift service CA to request TLS config
+	AutoTLS string `json:"autotls,omitempty"`
 }
 
 // ArgoCDRepoSpec defines the desired state for the Argo CD repo server component.
@@ -544,6 +552,14 @@ type ArgoCDSpec struct {
 	// DisableAdmin will disable the admin user.
 	DisableAdmin bool `json:"disableAdmin,omitempty"`
 
+	// ExtraConfig can be used to add fields to Argo CD configmap that are not supported by Argo CD CRD.
+	//
+	// Note: ExtraConfig takes precedence over Argo CD CRD.
+	// For example, A user sets `argocd.Spec.DisableAdmin` = true and also
+	// `a.Spec.ExtraConfig["admin.enabled"]` = true. In this case, operator updates
+	// Argo CD Configmap as follows -> argocd-cm.Data["admin.enabled"] = true.
+	ExtraConfig map[string]string `json:"extraConfig,omitempty"`
+
 	// GATrackingID is the google analytics tracking ID to use.
 	//+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Google Analytics Tracking ID'",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:text","urn:alm:descriptor:com.tectonic.ui:advanced"}
 	GATrackingID string `json:"gaTrackingID,omitempty"`
@@ -718,6 +734,9 @@ type ArgoCDStatus struct {
 	// RepoTLSChecksum contains the SHA256 checksum of the latest known state of tls.crt and tls.key in the argocd-repo-server-tls secret.
 	RepoTLSChecksum string `json:"repoTLSChecksum,omitempty"`
 
+	// RedisTLSChecksum contains the SHA256 checksum of the latest known state of tls.crt and tls.key in the argocd-operator-redis-tls secret.
+	RedisTLSChecksum string `json:"redisTLSChecksum,omitempty"`
+
 	// Host is the hostname of the Ingress.
 	Host string `json:"host,omitempty"`
 }
@@ -772,6 +791,12 @@ func (r *ArgoCDRepoSpec) WantsAutoTLS() bool {
 	return r.AutoTLS == "openshift"
 }
 
+// WantsAutoTLS returns true if the redis server configuration has set
+// the autoTLS toggle to a supported provider.
+func (r *ArgoCDRedisSpec) WantsAutoTLS() bool {
+	return r.AutoTLS == "openshift"
+}
+
 // ApplicationInstanceLabelKey returns either the custom application instance
 // label key if set, or the default value.
 func (a *ArgoCD) ApplicationInstanceLabelKey() string {
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index fc169483..e56647fe 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -791,6 +791,13 @@ func (in *ArgoCDSpec) DeepCopyInto(out *ArgoCDSpec) {
 	}
 	in.Controller.DeepCopyInto(&out.Controller)
 	in.Dex.DeepCopyInto(&out.Dex)
+	if in.ExtraConfig != nil {
+		in, out := &in.ExtraConfig, &out.ExtraConfig
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
 	in.Grafana.DeepCopyInto(&out.Grafana)
 	in.HA.DeepCopyInto(&out.HA)
 	if in.Import != nil {
diff --git a/build/redis/haproxy.cfg.tpl b/build/redis/haproxy.cfg.tpl
index db813c52..6fbdeb6c 100644
--- a/build/redis/haproxy.cfg.tpl
+++ b/build/redis/haproxy.cfg.tpl
@@ -1,3 +1,8 @@
+{{- if eq .UseTLS "true"}}
+global
+    ca-base /app/config/redis/tls
+{{- end}}
+
 defaults REDIS
     mode tcp
     timeout connect 4s
@@ -14,44 +19,74 @@ listen health_check_http_url
 backend check_if_redis_is_master_0
     mode tcp
     option tcp-check
+{{- if eq .UseTLS "false"}}
     tcp-check connect
+{{- else}}
+    tcp-check connect ssl
+{{- end}}
     tcp-check send PING\r\n
     tcp-check expect string +PONG
     tcp-check send SENTINEL\ get-master-addr-by-name\ argocd\r\n
     tcp-check expect string REPLACE_ANNOUNCE0
     tcp-check send QUIT\r\n
     tcp-check expect string +OK
+{{- if eq .UseTLS "false"}}
     server R0 {{.ServiceName}}-announce-0:26379 check inter 3s
     server R1 {{.ServiceName}}-announce-1:26379 check inter 3s
     server R2 {{.ServiceName}}-announce-2:26379 check inter 3s
+{{- else}}
+    server R0 {{.ServiceName}}-announce-0:26379 verify required ca-file tls.crt check inter 3s
+    server R1 {{.ServiceName}}-announce-1:26379 verify required ca-file tls.crt check inter 3s
+    server R2 {{.ServiceName}}-announce-2:26379 verify required ca-file tls.crt check inter 3s
+{{- end}}
 # Check Sentinel and whether they are nominated master
 backend check_if_redis_is_master_1
     mode tcp
     option tcp-check
+{{- if eq .UseTLS "false"}}
     tcp-check connect
+{{- else}}
+    tcp-check connect ssl
+{{- end}}
     tcp-check send PING\r\n
     tcp-check expect string +PONG
     tcp-check send SENTINEL\ get-master-addr-by-name\ argocd\r\n
     tcp-check expect string REPLACE_ANNOUNCE1
     tcp-check send QUIT\r\n
     tcp-check expect string +OK
+{{- if eq .UseTLS "false"}}
     server R0 {{.ServiceName}}-announce-0:26379 check inter 3s
     server R1 {{.ServiceName}}-announce-1:26379 check inter 3s
     server R2 {{.ServiceName}}-announce-2:26379 check inter 3s
+{{- else}}
+    server R0 {{.ServiceName}}-announce-0:26379 verify required ca-file tls.crt check inter 3s
+    server R1 {{.ServiceName}}-announce-1:26379 verify required ca-file tls.crt check inter 3s
+    server R2 {{.ServiceName}}-announce-2:26379 verify required ca-file tls.crt check inter 3s
+{{- end}}
 # Check Sentinel and whether they are nominated master
 backend check_if_redis_is_master_2
     mode tcp
     option tcp-check
+{{- if eq .UseTLS "false"}}
     tcp-check connect
+{{- else}}
+    tcp-check connect ssl
+{{- end}}
     tcp-check send PING\r\n
     tcp-check expect string +PONG
     tcp-check send SENTINEL\ get-master-addr-by-name\ argocd\r\n
     tcp-check expect string REPLACE_ANNOUNCE2
     tcp-check send QUIT\r\n
     tcp-check expect string +OK
+{{- if eq .UseTLS "false"}}
     server R0 {{.ServiceName}}-announce-0:26379 check inter 3s
     server R1 {{.ServiceName}}-announce-1:26379 check inter 3s
     server R2 {{.ServiceName}}-announce-2:26379 check inter 3s
+{{- else}}
+    server R0 {{.ServiceName}}-announce-0:26379 verify required ca-file tls.crt check inter 3s
+    server R1 {{.ServiceName}}-announce-1:26379 verify required ca-file tls.crt check inter 3s
+    server R2 {{.ServiceName}}-announce-2:26379 verify required ca-file tls.crt check inter 3s
+{{- end}}
 
 # decide redis backend to use
 #master
@@ -62,16 +97,29 @@ frontend ft_redis_master
 backend bk_redis_master
     mode tcp
     option tcp-check
+{{- if eq .UseTLS "false"}}
     tcp-check connect
+{{- else}}
+    tcp-check connect ssl
+{{- end}}
     tcp-check send PING\r\n
     tcp-check expect string +PONG
     tcp-check send info\ replication\r\n
     tcp-check expect string role:master
     tcp-check send QUIT\r\n
     tcp-check expect string +OK
+{{- if eq .UseTLS "false"}}
     use-server R0 if { srv_is_up(R0) } { nbsrv(check_if_redis_is_master_0) ge 2 }
     server R0 {{.ServiceName}}-announce-0:6379 check inter 3s fall 1 rise 1
     use-server R1 if { srv_is_up(R1) } { nbsrv(check_if_redis_is_master_1) ge 2 }
     server R1 {{.ServiceName}}-announce-1:6379 check inter 3s fall 1 rise 1
     use-server R2 if { srv_is_up(R2) } { nbsrv(check_if_redis_is_master_2) ge 2 }
     server R2 {{.ServiceName}}-announce-2:6379 check inter 3s fall 1 rise 1
+{{- else}}
+    use-server R0 if { srv_is_up(R0) } { nbsrv(check_if_redis_is_master_0) ge 2 }
+    server R0 {{.ServiceName}}-announce-0:6379 verify required ca-file tls.crt check inter 3s fall 1 rise 1
+    use-server R1 if { srv_is_up(R1) } { nbsrv(check_if_redis_is_master_1) ge 2 }
+    server R1 {{.ServiceName}}-announce-1:6379 verify required ca-file tls.crt check inter 3s fall 1 rise 1
+    use-server R2 if { srv_is_up(R2) } { nbsrv(check_if_redis_is_master_2) ge 2 }
+    server R2 {{.ServiceName}}-announce-2:6379 verify required ca-file tls.crt check inter 3s fall 1 rise 1
+{{- end}}
diff --git a/build/redis/init.sh.tpl b/build/redis/init.sh.tpl
index 4d38152a..9e08adad 100644
--- a/build/redis/init.sh.tpl
+++ b/build/redis/init.sh.tpl
@@ -1,24 +1,29 @@
 echo "$(date) Start..."
 HOSTNAME="$(cat /proc/sys/kernel/hostname)"
 INDEX="${HOSTNAME##*-}"
-SENTINEL_PORT=26379
+SENTINEL_PORT={{- if eq .UseTLS "false" -}}26379{{- else -}}0{{- end }}
 MASTER=''
 MASTER_GROUP="argocd"
 QUORUM="2"
 REDIS_CONF=/data/conf/redis.conf
+{{- if eq .UseTLS "false"}}
 REDIS_PORT=6379
 REDIS_TLS_PORT=
+{{- else}}
+REDIS_PORT=0
+REDIS_TLS_PORT=6379
+{{- end}}
 SENTINEL_CONF=/data/conf/sentinel.conf
-SENTINEL_TLS_PORT=
+SENTINEL_TLS_PORT={{- if eq .UseTLS "true" -}}26379{{- end }}
 SERVICE={{.ServiceName}}
-SENTINEL_TLS_REPLICATION_ENABLED=false
-REDIS_TLS_REPLICATION_ENABLED=false
+SENTINEL_TLS_REPLICATION_ENABLED={{.UseTLS}}
+REDIS_TLS_REPLICATION_ENABLED={{.UseTLS}}
 set -eu
 
 sentinel_get_master() {
 set +e
     if [ "$SENTINEL_PORT" -eq 0 ]; then
-        redis-cli -h "${SERVICE}" -p "${SENTINEL_TLS_PORT}"   --tls --cacert /tls-certs/ca.crt --cert /tls-certs/redis.crt --key /tls-certs/redis.key sentinel get-master-addr-by-name "${MASTER_GROUP}" |\
+        redis-cli -h "${SERVICE}" -p "${SENTINEL_TLS_PORT}"   --tls --cacert /app/config/redis/tls/tls.crt sentinel get-master-addr-by-name "${MASTER_GROUP}" |\
         grep -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'
     else
         redis-cli -h "${SERVICE}" -p "${SENTINEL_PORT}"  sentinel get-master-addr-by-name "${MASTER_GROUP}" |\
@@ -128,7 +133,7 @@ setup_defaults() {
 redis_ping() {
 set +e
     if [ "$REDIS_PORT" -eq 0 ]; then
-        redis-cli -h "${MASTER}" -p "${REDIS_TLS_PORT}"  --tls --cacert /tls-certs/ca.crt --cert /tls-certs/redis.crt --key /tls-certs/redis.key ping
+        redis-cli -h "${MASTER}" -p "${REDIS_TLS_PORT}"  --tls --cacert /app/config/redis/tls/tls.crt ping
     else
         redis-cli -h "${MASTER}" -p "${REDIS_PORT}" ping
     fi
@@ -165,7 +170,7 @@ find_master() {
         if [ "$SENTINEL_PORT" -eq 0 ]; then
             echo "  on sentinel (${SERVICE}:${SENTINEL_TLS_PORT}), sentinel grp (${MASTER_GROUP})"
             echo "  $(date).."
-            if redis-cli -h "${SERVICE}" -p "${SENTINEL_TLS_PORT}"   --tls --cacert /tls-certs/ca.crt --cert /tls-certs/redis.crt --key /tls-certs/redis.key sentinel failover "${MASTER_GROUP}" | grep -q 'NOGOODSLAVE' ; then
+            if redis-cli -h "${SERVICE}" -p "${SENTINEL_TLS_PORT}"   --tls --cacert /app/config/redis/tls/tls.crt sentinel failover "${MASTER_GROUP}" | grep -q 'NOGOODSLAVE' ; then
                 echo "  $(date) Failover returned with 'NOGOODSLAVE'"
                 echo "Setting defaults for this pod.."
                 setup_defaults
diff --git a/build/redis/redis.conf.tpl b/build/redis/redis.conf.tpl
index 55a878fc..1adee815 100644
--- a/build/redis/redis.conf.tpl
+++ b/build/redis/redis.conf.tpl
@@ -1,5 +1,15 @@
 dir "/data"
+{{- if eq .UseTLS "false"}}
 port 6379
+{{- else}}
+port 0
+tls-port 6379
+tls-cert-file /app/config/redis/tls/tls.crt
+tls-ca-cert-file /app/config/redis/tls/tls.crt
+tls-key-file /app/config/redis/tls/tls.key
+tls-replication yes
+tls-auth-clients no
+{{- end}}
 bind 0.0.0.0
 maxmemory 0
 maxmemory-policy volatile-lru
diff --git a/build/redis/redis_liveness.sh.tpl b/build/redis/redis_liveness.sh.tpl
index d59ef061..b6480718 100644
--- a/build/redis/redis_liveness.sh.tpl
+++ b/build/redis/redis_liveness.sh.tpl
@@ -2,6 +2,10 @@ response=$(
   redis-cli \
     -h localhost \
     -p 6379 \
+{{- if eq .UseTLS "true"}}
+    --tls \
+    --cacert /app/config/redis/tls/tls.crt \
+{{- end}}
     ping
 )
 if [ "$response" != "PONG" ] && [ "${response:0:7}" != "LOADING" ] ; then
diff --git a/build/redis/redis_readiness.sh.tpl b/build/redis/redis_readiness.sh.tpl
index 9e012458..c6caf15c 100644
--- a/build/redis/redis_readiness.sh.tpl
+++ b/build/redis/redis_readiness.sh.tpl
@@ -2,6 +2,10 @@ response=$(
   redis-cli \
     -h localhost \
     -p 6379 \
+{{- if eq .UseTLS "true"}}
+    --tls \
+    --cacert /app/config/redis/tls/tls.crt \
+{{- end}}
     ping
 )
 if [ "$response" != "PONG" ] ; then
diff --git a/build/redis/sentinel.conf.tpl b/build/redis/sentinel.conf.tpl
index 5be91286..5466fd5e 100644
--- a/build/redis/sentinel.conf.tpl
+++ b/build/redis/sentinel.conf.tpl
@@ -1,5 +1,15 @@
 dir "/data"
+{{- if eq .UseTLS "false"}}
 port 26379
+{{- else}}
+port 0
+tls-port 26379
+tls-cert-file /app/config/redis/tls/tls.crt
+tls-ca-cert-file /app/config/redis/tls/tls.crt
+tls-key-file /app/config/redis/tls/tls.key
+tls-replication yes
+tls-auth-clients no
+{{- end}}
 bind 0.0.0.0
     sentinel down-after-milliseconds argocd 10000
     sentinel failover-timeout argocd 180000
diff --git a/build/redis/sentinel_liveness.sh.tpl b/build/redis/sentinel_liveness.sh.tpl
index b5799cc7..5629c6f3 100644
--- a/build/redis/sentinel_liveness.sh.tpl
+++ b/build/redis/sentinel_liveness.sh.tpl
@@ -2,6 +2,10 @@ response=$(
   redis-cli \
     -h localhost \
     -p 26379 \
+{{- if eq .UseTLS "true"}}
+    --tls \
+    --cacert /app/config/redis/tls/tls.crt \
+{{- end}}
     ping
 )
 if [ "$response" != "PONG" ]; then
diff --git a/bundle/manifests/argoproj.io_argocds.yaml b/bundle/manifests/argoproj.io_argocds.yaml
index ff3b95fb..f434f98d 100644
--- a/bundle/manifests/argoproj.io_argocds.yaml
+++ b/bundle/manifests/argoproj.io_argocds.yaml
@@ -345,6 +345,16 @@ spec:
               disableAdmin:
                 description: DisableAdmin will disable the admin user.
                 type: boolean
+              extraConfig:
+                additionalProperties:
+                  type: string
+                description: "ExtraConfig can be used to add fields to Argo CD configmap
+                  that are not supported by Argo CD CRD. \n Note: ExtraConfig takes
+                  precedence over Argo CD CRD. For example, A user sets `argocd.Spec.DisableAdmin`
+                  = true and also `a.Spec.ExtraConfig[\"admin.enabled\"]` = true.
+                  In this case, operator updates Argo CD Configmap as follows -> argocd-cm.Data[\"admin.enabled\"]
+                  = true."
+                type: object
               gaAnonymizeUsers:
                 description: GAAnonymizeUsers toggles user IDs being hashed before
                   sending to google analytics.
@@ -856,6 +866,16 @@ spec:
               redis:
                 description: Redis defines the Redis server options for ArgoCD.
                 properties:
+                  autotls:
+                    description: 'AutoTLS specifies the method to use for automatic
+                      TLS configuration for the redis server The value specified here
+                      can currently be: - openshift - Use the OpenShift service CA
+                      to request TLS config'
+                    type: string
+                  disableTLSVerification:
+                    description: DisableTLSVerification defines whether redis server
+                      API should be accessed using strict TLS validation
+                    type: boolean
                   image:
                     description: Image is the Redis container image.
                     type: string
@@ -5716,6 +5736,11 @@ spec:
                   some reason the state of the Argo CD Redis component could not be
                   obtained.'
                 type: string
+              redisTLSChecksum:
+                description: RedisTLSChecksum contains the SHA256 checksum of the
+                  latest known state of tls.crt and tls.key in the argocd-operator-redis-tls
+                  secret.
+                type: string
               repo:
                 description: 'Repo is a simple, high-level summary of where the Argo
                   CD Repo component is in its lifecycle. There are five possible repo
diff --git a/common/values.go b/common/values.go
index 2fe13c94..1613c255 100644
--- a/common/values.go
+++ b/common/values.go
@@ -77,6 +77,9 @@ const (
 	// ArgoCDTLSCertsConfigMapName is the upstream hard-coded TLS certificate data ConfigMap name.
 	ArgoCDTLSCertsConfigMapName = "argocd-tls-certs-cm"
 
+	// ArgoCDRedisServerTLSSecretName is the name of the TLS secret for the redis-server
+	ArgoCDRedisServerTLSSecretName = "argocd-operator-redis-tls"
+
 	// ArgoCDRepoServerTLSSecretName is the name of the TLS secret for the repo-server
 	ArgoCDRepoServerTLSSecretName = "argocd-repo-server-tls"
 
diff --git a/config/crd/bases/argoproj.io_argocds.yaml b/config/crd/bases/argoproj.io_argocds.yaml
index 3ed48750..2d757227 100644
--- a/config/crd/bases/argoproj.io_argocds.yaml
+++ b/config/crd/bases/argoproj.io_argocds.yaml
@@ -347,6 +347,16 @@ spec:
               disableAdmin:
                 description: DisableAdmin will disable the admin user.
                 type: boolean
+              extraConfig:
+                additionalProperties:
+                  type: string
+                description: "ExtraConfig can be used to add fields to Argo CD configmap
+                  that are not supported by Argo CD CRD. \n Note: ExtraConfig takes
+                  precedence over Argo CD CRD. For example, A user sets `argocd.Spec.DisableAdmin`
+                  = true and also `a.Spec.ExtraConfig[\"admin.enabled\"]` = true.
+                  In this case, operator updates Argo CD Configmap as follows -> argocd-cm.Data[\"admin.enabled\"]
+                  = true."
+                type: object
               gaAnonymizeUsers:
                 description: GAAnonymizeUsers toggles user IDs being hashed before
                   sending to google analytics.
@@ -858,6 +868,16 @@ spec:
               redis:
                 description: Redis defines the Redis server options for ArgoCD.
                 properties:
+                  autotls:
+                    description: 'AutoTLS specifies the method to use for automatic
+                      TLS configuration for the redis server The value specified here
+                      can currently be: - openshift - Use the OpenShift service CA
+                      to request TLS config'
+                    type: string
+                  disableTLSVerification:
+                    description: DisableTLSVerification defines whether redis server
+                      API should be accessed using strict TLS validation
+                    type: boolean
                   image:
                     description: Image is the Redis container image.
                     type: string
@@ -5718,6 +5738,11 @@ spec:
                   some reason the state of the Argo CD Redis component could not be
                   obtained.'
                 type: string
+              redisTLSChecksum:
+                description: RedisTLSChecksum contains the SHA256 checksum of the
+                  latest known state of tls.crt and tls.key in the argocd-operator-redis-tls
+                  secret.
+                type: string
               repo:
                 description: 'Repo is a simple, high-level summary of where the Argo
                   CD Repo component is in its lifecycle. There are five possible repo
diff --git a/controllers/argocd/argocd_controller.go b/controllers/argocd/argocd_controller.go
index 3f5d4511..6362f00c 100644
--- a/controllers/argocd/argocd_controller.go
+++ b/controllers/argocd/argocd_controller.go
@@ -93,8 +93,10 @@ func (r *ReconcileArgoCD) Reconcile(ctx context.Context, request ctrl.Request) (
 				return reconcile.Result{}, fmt.Errorf("failed to delete ClusterResources: %w", err)
 			}
 
-			if err := r.removeManagedByLabelFromNamespaces(argocd.Namespace); err != nil {
-				return reconcile.Result{}, fmt.Errorf("failed to remove label from namespace[%v], error: %w", argocd.Namespace, err)
+			if isRemoveManagedByLabelOnArgoCDDeletion() {
+				if err := r.removeManagedByLabelFromNamespaces(argocd.Namespace); err != nil {
+					return reconcile.Result{}, fmt.Errorf("failed to remove label from namespace[%v], error: %w", argocd.Namespace, err)
+				}
 			}
 
 			if err := r.removeDeletionFinalizer(argocd); err != nil {
diff --git a/controllers/argocd/configmap.go b/controllers/argocd/configmap.go
index 06ca06b5..3a790aef 100644
--- a/controllers/argocd/configmap.go
+++ b/controllers/argocd/configmap.go
@@ -19,6 +19,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"path/filepath"
+	"reflect"
 	"strings"
 	"time"
 
@@ -74,7 +75,11 @@ func getConfigManagementPlugins(cr *argoprojv1a1.ArgoCD) string {
 func getDexConfig(cr *argoprojv1a1.ArgoCD) string {
 	config := common.ArgoCDDefaultDexConfig
 	if len(cr.Spec.Dex.Config) > 0 {
-		config = cr.Spec.Dex.Config
+		if cr.Spec.ExtraConfig["dex.config"] != "" {
+			config = cr.Spec.ExtraConfig["dex.config"]
+		} else {
+			config = cr.Spec.Dex.Config
+		}
 	}
 	return config
 }
@@ -260,12 +265,12 @@ func newConfigMapWithSuffix(suffix string, cr *argoprojv1a1.ArgoCD) *corev1.Conf
 }
 
 // reconcileConfigMaps will ensure that all ArgoCD ConfigMaps are present.
-func (r *ReconcileArgoCD) reconcileConfigMaps(cr *argoprojv1a1.ArgoCD) error {
+func (r *ReconcileArgoCD) reconcileConfigMaps(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) error {
 	if err := r.reconcileArgoConfigMap(cr); err != nil {
 		return err
 	}
 
-	if err := r.reconcileRedisConfiguration(cr); err != nil {
+	if err := r.reconcileRedisConfiguration(cr, useTLSForRedis); err != nil {
 		return err
 	}
 
@@ -319,18 +324,8 @@ func (r *ReconcileArgoCD) reconcileCAConfigMap(cr *argoprojv1a1.ArgoCD) error {
 // reconcileConfiguration will ensure that the main ConfigMap for ArgoCD is present.
 func (r *ReconcileArgoCD) reconcileArgoConfigMap(cr *argoprojv1a1.ArgoCD) error {
 	cm := newConfigMapWithName(common.ArgoCDConfigMapName, cr)
-	if argoutil.IsObjectFound(r.Client, cr.Namespace, cm.Name, cm) {
-		if cr.Spec.SSO == nil {
-			if err := r.reconcileDexConfiguration(cm, cr); err != nil {
-				return err
-			}
-		}
-		return r.reconcileExistingArgoConfigMap(cm, cr)
-	}
 
-	if cm.Data == nil {
-		cm.Data = make(map[string]string)
-	}
+	cm.Data = make(map[string]string)
 
 	cm.Data[common.ArgoCDKeyApplicationInstanceLabelKey] = getApplicationInstanceLabelKey(cr)
 	cm.Data[common.ArgoCDKeyConfigManagementPlugins] = getConfigManagementPlugins(cr)
@@ -383,10 +378,32 @@ func (r *ReconcileArgoCD) reconcileArgoConfigMap(cr *argoprojv1a1.ArgoCD) error
 		}
 	}
 
+	if len(cr.Spec.ExtraConfig) > 0 {
+		for k, v := range cr.Spec.ExtraConfig {
+			cm.Data[k] = v
+		}
+	}
+
 	if err := controllerutil.SetControllerReference(cr, cm, r.Scheme); err != nil {
 		return err
 	}
+
+	existingCM := &corev1.ConfigMap{}
+	if argoutil.IsObjectFound(r.Client, cr.Namespace, cm.Name, existingCM) {
+		if cr.Spec.SSO == nil {
+			if err := r.reconcileDexConfiguration(existingCM, cr); err != nil {
+				return err
+			}
+		}
+
+		if !reflect.DeepEqual(cm.Data, existingCM.Data) {
+			existingCM.Data = cm.Data
+			return r.Client.Update(context.TODO(), existingCM)
+		}
+		return nil // Do nothing as there is no change in the configmap.
+	}
 	return r.Client.Create(context.TODO(), cm)
+
 }
 
 // reconcileDexConfiguration will ensure that Dex is configured properly.
@@ -421,137 +438,6 @@ func (r *ReconcileArgoCD) reconcileDexConfiguration(cm *corev1.ConfigMap, cr *ar
 	return nil
 }
 
-func (r *ReconcileArgoCD) reconcileExistingArgoConfigMap(cm *corev1.ConfigMap, cr *argoprojv1a1.ArgoCD) error {
-	changed := false
-
-	if cm.Data == nil {
-		cm.Data = make(map[string]string)
-	}
-
-	if cm.Data[common.ArgoCDKeyAdminEnabled] == fmt.Sprintf("%t", cr.Spec.DisableAdmin) {
-		cm.Data[common.ArgoCDKeyAdminEnabled] = fmt.Sprintf("%t", !cr.Spec.DisableAdmin)
-		changed = true
-	}
-
-	if cm.Data[common.ArgoCDKeyApplicationInstanceLabelKey] != cr.Spec.ApplicationInstanceLabelKey {
-		cm.Data[common.ArgoCDKeyApplicationInstanceLabelKey] = cr.Spec.ApplicationInstanceLabelKey
-		changed = true
-	}
-
-	if cm.Data[common.ArgoCDKeyConfigManagementPlugins] != cr.Spec.ConfigManagementPlugins {
-		cm.Data[common.ArgoCDKeyConfigManagementPlugins] = cr.Spec.ConfigManagementPlugins
-		changed = true
-	}
-
-	if cm.Data[common.ArgoCDKeyGATrackingID] != cr.Spec.GATrackingID {
-		cm.Data[common.ArgoCDKeyGATrackingID] = cr.Spec.GATrackingID
-		changed = true
-	}
-
-	if cm.Data[common.ArgoCDKeyGAAnonymizeUsers] != fmt.Sprint(cr.Spec.GAAnonymizeUsers) {
-		cm.Data[common.ArgoCDKeyGAAnonymizeUsers] = fmt.Sprint(cr.Spec.GAAnonymizeUsers)
-		changed = true
-	}
-
-	if cm.Data[common.ArgoCDKeyHelpChatURL] != cr.Spec.HelpChatURL {
-		cm.Data[common.ArgoCDKeyHelpChatURL] = cr.Spec.HelpChatURL
-		changed = true
-	}
-
-	if cm.Data[common.ArgoCDKeyHelpChatText] != cr.Spec.HelpChatText {
-		cm.Data[common.ArgoCDKeyHelpChatText] = cr.Spec.HelpChatText
-		changed = true
-	}
-
-	if cm.Data[common.ArgoCDKeyKustomizeBuildOptions] != cr.Spec.KustomizeBuildOptions {
-		cm.Data[common.ArgoCDKeyKustomizeBuildOptions] = cr.Spec.KustomizeBuildOptions
-		changed = true
-	}
-
-	if len(cr.Spec.KustomizeVersions) > 0 {
-		for _, kv := range cr.Spec.KustomizeVersions {
-			if cm.Data["kustomize.version"+kv.Version] != kv.Path {
-				cm.Data["kustomize.version."+kv.Version] = kv.Path
-				changed = true
-			}
-		}
-	}
-
-	if cr.Spec.SSO == nil {
-		if cm.Data[common.ArgoCDKeyOIDCConfig] != cr.Spec.OIDCConfig {
-			cm.Data[common.ArgoCDKeyOIDCConfig] = cr.Spec.OIDCConfig
-			changed = true
-		}
-	}
-
-	if cm.Data[common.ArgoCDKeyResourceCustomizations] != cr.Spec.ResourceCustomizations {
-		cm.Data[common.ArgoCDKeyResourceCustomizations] = cr.Spec.ResourceCustomizations
-		changed = true
-	}
-
-	if cm.Data[common.ArgoCDKeyResourceExclusions] != cr.Spec.ResourceExclusions {
-		cm.Data[common.ArgoCDKeyResourceExclusions] = cr.Spec.ResourceExclusions
-		changed = true
-	}
-
-	if cm.Data[common.ArgoCDKeyResourceInclusions] != cr.Spec.ResourceInclusions {
-		cm.Data[common.ArgoCDKeyResourceInclusions] = cr.Spec.ResourceInclusions
-		changed = true
-	}
-
-	if cm.Data[common.ArgoCDKeyResourceTrackingMethod] != cr.Spec.ResourceTrackingMethod {
-		cm.Data[common.ArgoCDKeyResourceTrackingMethod] = getResourceTrackingMethod(cr)
-		changed = true
-	}
-
-	uri := r.getArgoServerURI(cr)
-	if cm.Data[common.ArgoCDKeyServerURL] != uri {
-		cm.Data[common.ArgoCDKeyServerURL] = uri
-		changed = true
-	}
-
-	if cm.Data[common.ArgoCDKeyStatusBadgeEnabled] != fmt.Sprint(cr.Spec.StatusBadgeEnabled) {
-		cm.Data[common.ArgoCDKeyStatusBadgeEnabled] = fmt.Sprint(cr.Spec.StatusBadgeEnabled)
-		changed = true
-	}
-
-	if cm.Data[common.ArgoCDKeyUsersAnonymousEnabled] != fmt.Sprint(cr.Spec.UsersAnonymousEnabled) {
-		cm.Data[common.ArgoCDKeyUsersAnonymousEnabled] = fmt.Sprint(cr.Spec.UsersAnonymousEnabled)
-		changed = true
-	}
-
-	if cm.Data[common.ArgoCDKeyRepositoryCredentials] != cr.Spec.RepositoryCredentials {
-		cm.Data[common.ArgoCDKeyRepositoryCredentials] = cr.Spec.RepositoryCredentials
-		changed = true
-	}
-
-	if cr.Spec.Banner != nil {
-		if cm.Data[common.ArgoCDKeyBannerContent] != fmt.Sprint(cr.Spec.Banner.Content) {
-			cm.Data[common.ArgoCDKeyBannerContent] = fmt.Sprint(cr.Spec.Banner.Content)
-			changed = true
-		}
-		if cm.Data[common.ArgoCDKeyBannerURL] != fmt.Sprint(cr.Spec.Banner.URL) {
-			cm.Data[common.ArgoCDKeyBannerURL] = fmt.Sprint(cr.Spec.Banner.URL)
-			changed = true
-		}
-	} else {
-		if _, ok := cm.Data[common.ArgoCDKeyBannerContent]; ok {
-			delete(cm.Data, common.ArgoCDKeyBannerContent)
-			changed = true
-		}
-		if _, ok := cm.Data[common.ArgoCDKeyBannerURL]; ok {
-			delete(cm.Data, common.ArgoCDKeyBannerURL)
-			changed = true
-		}
-	}
-
-	if changed {
-		return r.Client.Update(context.TODO(), cm) // TODO: Reload Argo CD server after ConfigMap change (which properties)?
-	}
-
-	return nil // Nothing changed, no update needed...
-}
-
 // reconcileGrafanaConfiguration will ensure that the Grafana configuration ConfigMap is present.
 func (r *ReconcileArgoCD) reconcileGrafanaConfiguration(cr *argoprojv1a1.ArgoCD) error {
 	if !cr.Spec.Grafana.Enabled {
@@ -672,18 +558,18 @@ func (r *ReconcileArgoCD) reconcileRBACConfigMap(cm *corev1.ConfigMap, cr *argop
 }
 
 // reconcileRedisConfiguration will ensure that all of the Redis ConfigMaps are present for the given ArgoCD.
-func (r *ReconcileArgoCD) reconcileRedisConfiguration(cr *argoprojv1a1.ArgoCD) error {
-	if err := r.reconcileRedisHAConfigMap(cr); err != nil {
+func (r *ReconcileArgoCD) reconcileRedisConfiguration(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) error {
+	if err := r.reconcileRedisHAConfigMap(cr, useTLSForRedis); err != nil {
 		return err
 	}
-	if err := r.reconcileRedisHAHealthConfigMap(cr); err != nil {
+	if err := r.reconcileRedisHAHealthConfigMap(cr, useTLSForRedis); err != nil {
 		return err
 	}
 	return nil
 }
 
 // reconcileRedisHAConfigMap will ensure that the Redis HA Health ConfigMap is present for the given ArgoCD.
-func (r *ReconcileArgoCD) reconcileRedisHAHealthConfigMap(cr *argoprojv1a1.ArgoCD) error {
+func (r *ReconcileArgoCD) reconcileRedisHAHealthConfigMap(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) error {
 	cm := newConfigMapWithName(common.ArgoCDRedisHAHealthConfigMapName, cr)
 	if argoutil.IsObjectFound(r.Client, cr.Namespace, cm.Name, cm) {
 		if !cr.Spec.HA.Enabled {
@@ -698,9 +584,9 @@ func (r *ReconcileArgoCD) reconcileRedisHAHealthConfigMap(cr *argoprojv1a1.ArgoC
 	}
 
 	cm.Data = map[string]string{
-		"redis_liveness.sh":    getRedisLivenessScript(),
-		"redis_readiness.sh":   getRedisReadinessScript(),
-		"sentinel_liveness.sh": getSentinelLivenessScript(),
+		"redis_liveness.sh":    getRedisLivenessScript(useTLSForRedis),
+		"redis_readiness.sh":   getRedisReadinessScript(useTLSForRedis),
+		"sentinel_liveness.sh": getSentinelLivenessScript(useTLSForRedis),
 	}
 
 	if err := controllerutil.SetControllerReference(cr, cm, r.Scheme); err != nil {
@@ -710,7 +596,7 @@ func (r *ReconcileArgoCD) reconcileRedisHAHealthConfigMap(cr *argoprojv1a1.ArgoC
 }
 
 // reconcileRedisHAConfigMap will ensure that the Redis HA ConfigMap is present for the given ArgoCD.
-func (r *ReconcileArgoCD) reconcileRedisHAConfigMap(cr *argoprojv1a1.ArgoCD) error {
+func (r *ReconcileArgoCD) reconcileRedisHAConfigMap(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) error {
 	cm := newConfigMapWithName(common.ArgoCDRedisHAConfigMapName, cr)
 	if argoutil.IsObjectFound(r.Client, cr.Namespace, cm.Name, cm) {
 		if !cr.Spec.HA.Enabled {
@@ -725,11 +611,11 @@ func (r *ReconcileArgoCD) reconcileRedisHAConfigMap(cr *argoprojv1a1.ArgoCD) err
 	}
 
 	cm.Data = map[string]string{
-		"haproxy.cfg":     getRedisHAProxyConfig(cr),
+		"haproxy.cfg":     getRedisHAProxyConfig(cr, useTLSForRedis),
 		"haproxy_init.sh": getRedisHAProxyScript(cr),
-		"init.sh":         getRedisInitScript(cr),
-		"redis.conf":      getRedisConf(),
-		"sentinel.conf":   getRedisSentinelConf(),
+		"init.sh":         getRedisInitScript(cr, useTLSForRedis),
+		"redis.conf":      getRedisConf(useTLSForRedis),
+		"sentinel.conf":   getRedisSentinelConf(useTLSForRedis),
 	}
 
 	if err := controllerutil.SetControllerReference(cr, cm, r.Scheme); err != nil {
@@ -738,6 +624,26 @@ func (r *ReconcileArgoCD) reconcileRedisHAConfigMap(cr *argoprojv1a1.ArgoCD) err
 	return r.Client.Create(context.TODO(), cm)
 }
 
+func (r *ReconcileArgoCD) recreateRedisHAConfigMap(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) error {
+	cm := newConfigMapWithName(common.ArgoCDRedisHAConfigMapName, cr)
+	if argoutil.IsObjectFound(r.Client, cr.Namespace, cm.Name, cm) {
+		if err := r.Client.Delete(context.TODO(), cm); err != nil {
+			return err
+		}
+	}
+	return r.reconcileRedisHAConfigMap(cr, useTLSForRedis)
+}
+
+func (r *ReconcileArgoCD) recreateRedisHAHealthConfigMap(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) error {
+	cm := newConfigMapWithName(common.ArgoCDRedisHAHealthConfigMapName, cr)
+	if argoutil.IsObjectFound(r.Client, cr.Namespace, cm.Name, cm) {
+		if err := r.Client.Delete(context.TODO(), cm); err != nil {
+			return err
+		}
+	}
+	return r.reconcileRedisHAHealthConfigMap(cr, useTLSForRedis)
+}
+
 // reconcileSSHKnownHosts will ensure that the ArgoCD SSH Known Hosts ConfigMap is present.
 func (r *ReconcileArgoCD) reconcileSSHKnownHosts(cr *argoprojv1a1.ArgoCD) error {
 	cm := newConfigMapWithName(common.ArgoCDKnownHostsConfigMapName, cr)
diff --git a/controllers/argocd/custommapper.go b/controllers/argocd/custommapper.go
index 3dc0729a..eba5a335 100644
--- a/controllers/argocd/custommapper.go
+++ b/controllers/argocd/custommapper.go
@@ -9,6 +9,7 @@ import (
 	"github.com/argoproj-labs/argocd-operator/common"
 
 	corev1 "k8s.io/api/core/v1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 )
@@ -34,13 +35,40 @@ func (r *ReconcileArgoCD) clusterResourceMapper(o client.Object) []reconcile.Req
 	return result
 }
 
+// isSecretOfInterest returns true if the name of the given secret matches one of the
+// well-known tls secrets used to secure communication amongst the Argo CD components.
+func isSecretOfInterest(o client.Object) bool {
+	if strings.HasSuffix(o.GetName(), "-repo-server-tls") {
+		return true
+	}
+	if o.GetName() == common.ArgoCDRedisServerTLSSecretName {
+		return true
+	}
+	return false
+}
+
+// isOwnerOfInterest returns true if the given owner is one of the Argo CD services that
+// may have been made the owner of the tls secret created by the OpenShift service CA, used
+// to secure communication amongst the Argo CD components.
+func isOwnerOfInterest(owner v1.OwnerReference) bool {
+	if owner.Kind != "Service" {
+		return false
+	}
+	if strings.HasSuffix(owner.Name, "-repo-server") {
+		return true
+	}
+	if strings.HasSuffix(owner.Name, "-redis") {
+		return true
+	}
+	return false
+}
+
 // tlsSecretMapper maps a watch event on a secret of type TLS back to the
 // ArgoCD object that we want to reconcile.
 func (r *ReconcileArgoCD) tlsSecretMapper(o client.Object) []reconcile.Request {
 	var result = []reconcile.Request{}
 
-	// The secret must end with '-repo-server-tls'
-	if !strings.HasSuffix(o.GetName(), "-repo-server-tls") {
+	if !isSecretOfInterest(o) {
 		return result
 	}
 	namespacedArgoCDObject := client.ObjectKey{}
@@ -51,7 +79,7 @@ func (r *ReconcileArgoCD) tlsSecretMapper(o client.Object) []reconcile.Request {
 		// service, which in turn is owned by the controller. This method performs
 		// a lookup of the controller through the intermediate owning service.
 		for _, secretOwner := range secretOwnerRefs {
-			if secretOwner.Kind == "Service" && strings.HasSuffix(secretOwner.Name, "-repo-server") {
+			if isOwnerOfInterest(secretOwner) {
 				key := client.ObjectKey{Name: secretOwner.Name, Namespace: o.GetNamespace()}
 				svc := &corev1.Service{}
 
diff --git a/controllers/argocd/deployment.go b/controllers/argocd/deployment.go
index 9f855e0a..7ccb6c16 100644
--- a/controllers/argocd/deployment.go
+++ b/controllers/argocd/deployment.go
@@ -207,8 +207,26 @@ func getArgoImportVolumes(cr *argoprojv1a1.ArgoCDExport) []corev1.Volume {
 	return volumes
 }
 
+func getArgoRedisArgs(useTLS bool) []string {
+	args := make([]string, 0)
+
+	args = append(args, "--save", "")
+	args = append(args, "--appendonly", "no")
+
+	if useTLS {
+		args = append(args, "--tls-port", "6379")
+		args = append(args, "--port", "0")
+
+		args = append(args, "--tls-cert-file", "/app/config/redis/tls/tls.crt")
+		args = append(args, "--tls-key-file", "/app/config/redis/tls/tls.key")
+		args = append(args, "--tls-auth-clients", "no")
+	}
+
+	return args
+}
+
 // getArgoRepoCommand will return the command for the ArgoCD Repo component.
-func getArgoRepoCommand(cr *argoprojv1a1.ArgoCD) []string {
+func getArgoRepoCommand(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) []string {
 	cmd := make([]string, 0)
 
 	cmd = append(cmd, "uid_entrypoint.sh")
@@ -217,6 +235,15 @@ func getArgoRepoCommand(cr *argoprojv1a1.ArgoCD) []string {
 	cmd = append(cmd, "--redis")
 	cmd = append(cmd, getRedisServerAddress(cr))
 
+	if useTLSForRedis {
+		cmd = append(cmd, "--redis-use-tls")
+		if isRedisTLSVerificationDisabled(cr) {
+			cmd = append(cmd, "--redis-insecure-skip-tls-verify")
+		} else {
+			cmd = append(cmd, "--redis-ca-certificate", "/app/config/reposerver/tls/redis/tls.crt")
+		}
+	}
+
 	cmd = append(cmd, "--loglevel")
 	cmd = append(cmd, getLogLevel(cr.Spec.Repo.LogLevel))
 
@@ -237,7 +264,7 @@ func getArgoCmpServerInitCommand() []string {
 }
 
 // getArgoServerCommand will return the command for the ArgoCD server component.
-func getArgoServerCommand(cr *argoprojv1a1.ArgoCD) []string {
+func getArgoServerCommand(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) []string {
 	cmd := make([]string, 0)
 	cmd = append(cmd, "argocd-server")
 
@@ -261,6 +288,15 @@ func getArgoServerCommand(cr *argoprojv1a1.ArgoCD) []string {
 	cmd = append(cmd, "--redis")
 	cmd = append(cmd, getRedisServerAddress(cr))
 
+	if useTLSForRedis {
+		cmd = append(cmd, "--redis-use-tls")
+		if isRedisTLSVerificationDisabled(cr) {
+			cmd = append(cmd, "--redis-insecure-skip-tls-verify")
+		} else {
+			cmd = append(cmd, "--redis-ca-certificate", "/app/config/server/tls/redis/tls.crt")
+		}
+	}
+
 	cmd = append(cmd, "--loglevel")
 	cmd = append(cmd, getLogLevel(cr.Spec.Server.LogLevel))
 
@@ -352,13 +388,13 @@ func newDeploymentWithSuffix(suffix string, component string, cr *argoprojv1a1.A
 }
 
 // reconcileDeployments will ensure that all Deployment resources are present for the given ArgoCD.
-func (r *ReconcileArgoCD) reconcileDeployments(cr *argoprojv1a1.ArgoCD) error {
+func (r *ReconcileArgoCD) reconcileDeployments(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) error {
 	err := r.reconcileDexDeployment(cr)
 	if err != nil {
 		return err
 	}
 
-	err = r.reconcileRedisDeployment(cr)
+	err = r.reconcileRedisDeployment(cr, useTLSForRedis)
 	if err != nil {
 		return err
 	}
@@ -368,12 +404,12 @@ func (r *ReconcileArgoCD) reconcileDeployments(cr *argoprojv1a1.ArgoCD) error {
 		return err
 	}
 
-	err = r.reconcileRepoDeployment(cr)
+	err = r.reconcileRepoDeployment(cr, useTLSForRedis)
 	if err != nil {
 		return err
 	}
 
-	err = r.reconcileServerDeployment(cr)
+	err = r.reconcileServerDeployment(cr, useTLSForRedis)
 	if err != nil {
 		return err
 	}
@@ -636,15 +672,10 @@ func (r *ReconcileArgoCD) reconcileGrafanaDeployment(cr *argoprojv1a1.ArgoCD) er
 }
 
 // reconcileRedisDeployment will ensure the Deployment resource is present for the ArgoCD Redis component.
-func (r *ReconcileArgoCD) reconcileRedisDeployment(cr *argoprojv1a1.ArgoCD) error {
+func (r *ReconcileArgoCD) reconcileRedisDeployment(cr *argoprojv1a1.ArgoCD, useTLS bool) error {
 	deploy := newDeploymentWithSuffix("redis", "redis", cr)
 	deploy.Spec.Template.Spec.Containers = []corev1.Container{{
-		Args: []string{
-			"--save",
-			"",
-			"--appendonly",
-			"no",
-		},
+		Args:            getArgoRedisArgs(useTLS),
 		Image:           getRedisContainerImage(cr),
 		ImagePullPolicy: corev1.PullAlways,
 		Name:            "redis",
@@ -655,8 +686,26 @@ func (r *ReconcileArgoCD) reconcileRedisDeployment(cr *argoprojv1a1.ArgoCD) erro
 		},
 		Resources: getRedisResources(cr),
 		Env:       proxyEnvVars(),
+		VolumeMounts: []corev1.VolumeMount{
+			{
+				Name:      common.ArgoCDRedisServerTLSSecretName,
+				MountPath: "/app/config/redis/tls",
+			},
+		},
 	}}
 
+	deploy.Spec.Template.Spec.Volumes = []corev1.Volume{
+		{
+			Name: common.ArgoCDRedisServerTLSSecretName,
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: common.ArgoCDRedisServerTLSSecretName,
+					Optional:   boolPtr(true),
+				},
+			},
+		},
+	}
+
 	if err := applyReconcilerHook(cr, deploy, ""); err != nil {
 		return err
 	}
@@ -676,6 +725,12 @@ func (r *ReconcileArgoCD) reconcileRedisDeployment(cr *argoprojv1a1.ArgoCD) erro
 			changed = true
 		}
 		updateNodePlacement(existing, deploy, &changed)
+
+		if !reflect.DeepEqual(deploy.Spec.Template.Spec.Containers[0].Args, existing.Spec.Template.Spec.Containers[0].Args) {
+			existing.Spec.Template.Spec.Containers[0].Args = deploy.Spec.Template.Spec.Containers[0].Args
+			changed = true
+		}
+
 		if !reflect.DeepEqual(existing.Spec.Template.Spec.Containers[0].Env,
 			deploy.Spec.Template.Spec.Containers[0].Env) {
 			existing.Spec.Template.Spec.Containers[0].Env = deploy.Spec.Template.Spec.Containers[0].Env
@@ -791,6 +846,10 @@ func (r *ReconcileArgoCD) reconcileRedisHAProxyDeployment(cr *argoprojv1a1.ArgoC
 				Name:      "shared-socket",
 				MountPath: "/run/haproxy",
 			},
+			{
+				Name:      common.ArgoCDRedisServerTLSSecretName,
+				MountPath: "/app/config/redis/tls",
+			},
 		},
 	}}
 
@@ -842,6 +901,15 @@ func (r *ReconcileArgoCD) reconcileRedisHAProxyDeployment(cr *argoprojv1a1.ArgoC
 				EmptyDir: &corev1.EmptyDirVolumeSource{},
 			},
 		},
+		{
+			Name: common.ArgoCDRedisServerTLSSecretName,
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: common.ArgoCDRedisServerTLSSecretName,
+					Optional:   boolPtr(true),
+				},
+			},
+		},
 	}
 
 	deploy.Spec.Template.Spec.ServiceAccountName = fmt.Sprintf("%s-%s", cr.Name, "argocd-redis-ha")
@@ -857,7 +925,7 @@ func (r *ReconcileArgoCD) reconcileRedisHAProxyDeployment(cr *argoprojv1a1.ArgoC
 }
 
 // reconcileRepoDeployment will ensure the Deployment resource is present for the ArgoCD Repo component.
-func (r *ReconcileArgoCD) reconcileRepoDeployment(cr *argoprojv1a1.ArgoCD) error {
+func (r *ReconcileArgoCD) reconcileRepoDeployment(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) error {
 	deploy := newDeploymentWithSuffix("repo-server", "repo-server", cr)
 	automountToken := false
 	if cr.Spec.Repo.MountSAToken {
@@ -922,6 +990,10 @@ func (r *ReconcileArgoCD) reconcileRepoDeployment(cr *argoprojv1a1.ArgoCD) error
 			Name:      "argocd-repo-server-tls",
 			MountPath: "/app/config/reposerver/tls",
 		},
+		{
+			Name:      common.ArgoCDRedisServerTLSSecretName,
+			MountPath: "/app/config/reposerver/tls/redis",
+		},
 		{
 			Name:      "plugins",
 			MountPath: "/home/argocd/cmp-server/plugins",
@@ -933,7 +1005,7 @@ func (r *ReconcileArgoCD) reconcileRepoDeployment(cr *argoprojv1a1.ArgoCD) error
 	}
 
 	deploy.Spec.Template.Spec.Containers = []corev1.Container{{
-		Command:         getArgoRepoCommand(cr),
+		Command:         getArgoRepoCommand(cr, useTLSForRedis),
 		Image:           getRepoServerContainerImage(cr),
 		ImagePullPolicy: corev1.PullAlways,
 		LivenessProbe: &corev1.Probe{
@@ -1025,6 +1097,15 @@ func (r *ReconcileArgoCD) reconcileRepoDeployment(cr *argoprojv1a1.ArgoCD) error
 				},
 			},
 		},
+		{
+			Name: common.ArgoCDRedisServerTLSSecretName,
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: common.ArgoCDRedisServerTLSSecretName,
+					Optional:   boolPtr(true),
+				},
+			},
+		},
 		{
 			Name: "var-files",
 			VolumeSource: corev1.VolumeSource{
@@ -1114,12 +1195,12 @@ func (r *ReconcileArgoCD) reconcileRepoDeployment(cr *argoprojv1a1.ArgoCD) error
 }
 
 // reconcileServerDeployment will ensure the Deployment resource is present for the ArgoCD Server component.
-func (r *ReconcileArgoCD) reconcileServerDeployment(cr *argoprojv1a1.ArgoCD) error {
+func (r *ReconcileArgoCD) reconcileServerDeployment(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) error {
 	deploy := newDeploymentWithSuffix("server", "server", cr)
 	serverEnv := cr.Spec.Server.Env
 	serverEnv = argoutil.EnvMerge(serverEnv, proxyEnvVars(), false)
 	deploy.Spec.Template.Spec.Containers = []corev1.Container{{
-		Command:         getArgoServerCommand(cr),
+		Command:         getArgoServerCommand(cr, useTLSForRedis),
 		Image:           getArgoContainerImage(cr),
 		ImagePullPolicy: corev1.PullAlways,
 		Env:             serverEnv,
@@ -1164,6 +1245,10 @@ func (r *ReconcileArgoCD) reconcileServerDeployment(cr *argoprojv1a1.ArgoCD) err
 				Name:      "argocd-repo-server-tls",
 				MountPath: "/app/config/server/tls",
 			},
+			{
+				Name:      common.ArgoCDRedisServerTLSSecretName,
+				MountPath: "/app/config/server/tls/redis",
+			},
 		},
 	}}
 	deploy.Spec.Template.Spec.ServiceAccountName = fmt.Sprintf("%s-%s", cr.Name, "argocd-server")
@@ -1177,7 +1262,8 @@ func (r *ReconcileArgoCD) reconcileServerDeployment(cr *argoprojv1a1.ArgoCD) err
 					},
 				},
 			},
-		}, {
+		},
+		{
 			Name: "tls-certs",
 			VolumeSource: corev1.VolumeSource{
 				ConfigMap: &corev1.ConfigMapVolumeSource{
@@ -1186,7 +1272,8 @@ func (r *ReconcileArgoCD) reconcileServerDeployment(cr *argoprojv1a1.ArgoCD) err
 					},
 				},
 			},
-		}, {
+		},
+		{
 			Name: "argocd-repo-server-tls",
 			VolumeSource: corev1.VolumeSource{
 				Secret: &corev1.SecretVolumeSource{
@@ -1195,6 +1282,15 @@ func (r *ReconcileArgoCD) reconcileServerDeployment(cr *argoprojv1a1.ArgoCD) err
 				},
 			},
 		},
+		{
+			Name: common.ArgoCDRedisServerTLSSecretName,
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: common.ArgoCDRedisServerTLSSecretName,
+					Optional:   boolPtr(true),
+				},
+			},
+		},
 	}
 
 	if replicas := getArgoCDServerReplicas(cr); replicas != nil {
@@ -1293,6 +1389,13 @@ func isDexDisabled() bool {
 	return false
 }
 
+func isRemoveManagedByLabelOnArgoCDDeletion() bool {
+	if v := os.Getenv("REMOVE_MANAGED_BY_LABEL_ON_ARGOCD_DELETION"); v != "" {
+		return strings.ToLower(v) == "true"
+	}
+	return false
+}
+
 // to update nodeSelector and tolerations in reconciler
 func updateNodePlacement(existing *appsv1.Deployment, deploy *appsv1.Deployment, changed *bool) {
 	if !reflect.DeepEqual(existing.Spec.Template.Spec.NodeSelector, deploy.Spec.Template.Spec.NodeSelector) {
diff --git a/controllers/argocd/secret.go b/controllers/argocd/secret.go
index e78ca848..c98c95d9 100644
--- a/controllers/argocd/secret.go
+++ b/controllers/argocd/secret.go
@@ -541,6 +541,108 @@ func (r *ReconcileArgoCD) reconcileRepoServerTLSSecret(cr *argoprojv1a1.ArgoCD)
 	return nil
 }
 
+// reconcileRedisTLSSecret checks whether the argocd-operator-redis-tls secret
+// has changed since our last reconciliation loop. It does so by comparing the
+// checksum of tls.crt and tls.key in the status of the ArgoCD CR against the
+// values calculated from the live state in the cluster.
+func (r *ReconcileArgoCD) reconcileRedisTLSSecret(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) error {
+	var tlsSecretObj corev1.Secret
+	var sha256sum string
+
+	log.Info("reconciling redis-server TLS secret")
+
+	tlsSecretName := types.NamespacedName{Namespace: cr.Namespace, Name: common.ArgoCDRedisServerTLSSecretName}
+	err := r.Client.Get(context.TODO(), tlsSecretName, &tlsSecretObj)
+	if err != nil {
+		if !apierrors.IsNotFound(err) {
+			return err
+		}
+	} else if tlsSecretObj.Type != corev1.SecretTypeTLS {
+		// We only process secrets of type kubernetes.io/tls
+		return nil
+	} else {
+		// We do the checksum over a concatenated byte stream of cert + key
+		crt, crtOk := tlsSecretObj.Data[corev1.TLSCertKey]
+		key, keyOk := tlsSecretObj.Data[corev1.TLSPrivateKeyKey]
+		if crtOk && keyOk {
+			var sumBytes []byte
+			sumBytes = append(sumBytes, crt...)
+			sumBytes = append(sumBytes, key...)
+			sha256sum = fmt.Sprintf("%x", sha256.Sum256(sumBytes))
+		}
+	}
+
+	// The content of the TLS secret has changed since we last looked if the
+	// calculated checksum doesn't match the one stored in the status.
+	if cr.Status.RedisTLSChecksum != sha256sum {
+		// We store the value early to prevent a possible restart loop, for the
+		// cost of a possibly missed restart when we cannot update the status
+		// field of the resource.
+		cr.Status.RedisTLSChecksum = sha256sum
+		err = r.Client.Status().Update(context.TODO(), cr)
+		if err != nil {
+			return err
+		}
+
+		// Trigger rollout of redis
+		if cr.Spec.HA.Enabled {
+			err = r.recreateRedisHAConfigMap(cr, useTLSForRedis)
+			if err != nil {
+				return err
+			}
+			err = r.recreateRedisHAHealthConfigMap(cr, useTLSForRedis)
+			if err != nil {
+				return err
+			}
+			haProxyDepl := newDeploymentWithSuffix("redis-ha-haproxy", "redis", cr)
+			err = r.triggerRollout(haProxyDepl, "redis.tls.cert.changed")
+			if err != nil {
+				return err
+			}
+			// If we use triggerRollout on the redis stateful set, kubernetes will attempt to restart the  pods
+			// one at a time, and the first one to restart (which will be using tls) will hang as it tries to
+			// communicate with the existing pods (which are not using tls) to establish which is the master.
+			// So instead we delete the stateful set, which will delete all the pods.
+			redisSts := newStatefulSetWithSuffix("redis-ha-server", "redis", cr)
+			if argoutil.IsObjectFound(r.Client, redisSts.Namespace, redisSts.Name, redisSts) {
+				err = r.Client.Delete(context.TODO(), redisSts)
+				if err != nil {
+					return err
+				}
+			}
+		} else {
+			redisDepl := newDeploymentWithSuffix("redis", "redis", cr)
+			err = r.triggerRollout(redisDepl, "redis.tls.cert.changed")
+			if err != nil {
+				return err
+			}
+		}
+
+		// Trigger rollout of API server
+		apiDepl := newDeploymentWithSuffix("server", "server", cr)
+		err = r.triggerRollout(apiDepl, "redis.tls.cert.changed")
+		if err != nil {
+			return err
+		}
+
+		// Trigger rollout of repository server
+		repoDepl := newDeploymentWithSuffix("repo-server", "repo-server", cr)
+		err = r.triggerRollout(repoDepl, "redis.tls.cert.changed")
+		if err != nil {
+			return err
+		}
+
+		// Trigger rollout of application controller
+		controllerSts := newStatefulSetWithSuffix("application-controller", "application-controller", cr)
+		err = r.triggerRollout(controllerSts, "redis.tls.cert.changed")
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 // reconcileSecrets will reconcile all ArgoCD Secret resources.
 func (r *ReconcileArgoCD) reconcileSecrets(cr *argoprojv1a1.ArgoCD) error {
 	if err := r.reconcileClusterSecrets(cr); err != nil {
diff --git a/controllers/argocd/service.go b/controllers/argocd/service.go
index 9eb0715d..18f244d8 100644
--- a/controllers/argocd/service.go
+++ b/controllers/argocd/service.go
@@ -285,10 +285,16 @@ func (r *ReconcileArgoCD) reconcileRedisHAServices(cr *argoprojv1a1.ArgoCD) erro
 // reconcileRedisService will ensure that the Service for Redis is present.
 func (r *ReconcileArgoCD) reconcileRedisService(cr *argoprojv1a1.ArgoCD) error {
 	svc := newServiceWithSuffix("redis", "redis", cr)
+
 	if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) {
+		if ensureAutoTLSAnnotation(svc, common.ArgoCDRedisServerTLSSecretName, cr.Spec.Redis.WantsAutoTLS()) {
+			return r.Client.Update(context.TODO(), svc)
+		}
 		return nil // Service found, do nothing
 	}
 
+	ensureAutoTLSAnnotation(svc, common.ArgoCDRedisServerTLSSecretName, cr.Spec.Redis.WantsAutoTLS())
+
 	svc.Spec.Selector = map[string]string{
 		common.ArgoCDKeyName: nameWithSuffix("redis", cr),
 	}
diff --git a/controllers/argocd/statefulset.go b/controllers/argocd/statefulset.go
index 0673b8c7..ca7b43b5 100644
--- a/controllers/argocd/statefulset.go
+++ b/controllers/argocd/statefulset.go
@@ -211,6 +211,10 @@ func (r *ReconcileArgoCD) reconcileRedisStatefulSet(cr *argoprojv1a1.ArgoCD) err
 					MountPath: "/health",
 					Name:      "health",
 				},
+				{
+					Name:      common.ArgoCDRedisServerTLSSecretName,
+					MountPath: "/app/config/redis/tls",
+				},
 			},
 		},
 		{
@@ -269,6 +273,10 @@ func (r *ReconcileArgoCD) reconcileRedisStatefulSet(cr *argoprojv1a1.ArgoCD) err
 					MountPath: "/health",
 					Name:      "health",
 				},
+				{
+					Name:      common.ArgoCDRedisServerTLSSecretName,
+					MountPath: "/app/config/redis/tls",
+				},
 			},
 		},
 	}
@@ -308,6 +316,10 @@ func (r *ReconcileArgoCD) reconcileRedisStatefulSet(cr *argoprojv1a1.ArgoCD) err
 				MountPath: "/data",
 				Name:      "data",
 			},
+			{
+				Name:      common.ArgoCDRedisServerTLSSecretName,
+				MountPath: "/app/config/redis/tls",
+			},
 		},
 	}}
 
@@ -355,6 +367,15 @@ func (r *ReconcileArgoCD) reconcileRedisStatefulSet(cr *argoprojv1a1.ArgoCD) err
 				EmptyDir: &corev1.EmptyDirVolumeSource{},
 			},
 		},
+		{
+			Name: common.ArgoCDRedisServerTLSSecretName,
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: common.ArgoCDRedisServerTLSSecretName,
+					Optional:   boolPtr(true),
+				},
+			},
+		},
 	}
 
 	ss.Spec.UpdateStrategy = appsv1.StatefulSetUpdateStrategy{
@@ -384,7 +405,7 @@ func getArgoControllerContainerEnv(cr *argoprojv1a1.ArgoCD) []corev1.EnvVar {
 	return env
 }
 
-func (r *ReconcileArgoCD) reconcileApplicationControllerStatefulSet(cr *argoprojv1a1.ArgoCD) error {
+func (r *ReconcileArgoCD) reconcileApplicationControllerStatefulSet(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) error {
 	var replicas int32 = common.ArgocdApplicationControllerDefaultReplicas
 
 	if cr.Spec.Controller.Sharding.Replicas != 0 && cr.Spec.Controller.Sharding.Enabled {
@@ -400,7 +421,7 @@ func (r *ReconcileArgoCD) reconcileApplicationControllerStatefulSet(cr *argoproj
 	controllerEnv = argoutil.EnvMerge(controllerEnv, proxyEnvVars(), false)
 	podSpec := &ss.Spec.Template.Spec
 	podSpec.Containers = []corev1.Container{{
-		Command:         getArgoApplicationControllerCommand(cr),
+		Command:         getArgoApplicationControllerCommand(cr, useTLSForRedis),
 		Image:           getArgoContainerImage(cr),
 		ImagePullPolicy: corev1.PullAlways,
 		Name:            "argocd-application-controller",
@@ -436,6 +457,10 @@ func (r *ReconcileArgoCD) reconcileApplicationControllerStatefulSet(cr *argoproj
 				Name:      "argocd-repo-server-tls",
 				MountPath: "/app/config/controller/tls",
 			},
+			{
+				Name:      common.ArgoCDRedisServerTLSSecretName,
+				MountPath: "/app/config/controller/tls/redis",
+			},
 		},
 	}}
 	podSpec.ServiceAccountName = nameWithSuffix("argocd-application-controller", cr)
@@ -449,6 +474,15 @@ func (r *ReconcileArgoCD) reconcileApplicationControllerStatefulSet(cr *argoproj
 				},
 			},
 		},
+		{
+			Name: common.ArgoCDRedisServerTLSSecretName,
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: common.ArgoCDRedisServerTLSSecretName,
+					Optional:   boolPtr(true),
+				},
+			},
+		},
 	}
 
 	ss.Spec.Template.Spec.Affinity = &corev1.Affinity{
@@ -513,7 +547,7 @@ func (r *ReconcileArgoCD) reconcileApplicationControllerStatefulSet(cr *argoproj
 			existing.Spec.Template.ObjectMeta.Labels["image.upgraded"] = time.Now().UTC().Format("01022006-150406-MST")
 			changed = true
 		}
-		desiredCommand := getArgoApplicationControllerCommand(cr)
+		desiredCommand := getArgoApplicationControllerCommand(cr, useTLSForRedis)
 		if isRepoServerTLSVerificationRequested(cr) {
 			desiredCommand = append(desiredCommand, "--repo-server-strict-tls")
 		}
@@ -567,8 +601,8 @@ func (r *ReconcileArgoCD) reconcileApplicationControllerStatefulSet(cr *argoproj
 }
 
 // reconcileStatefulSets will ensure that all StatefulSets are present for the given ArgoCD.
-func (r *ReconcileArgoCD) reconcileStatefulSets(cr *argoprojv1a1.ArgoCD) error {
-	if err := r.reconcileApplicationControllerStatefulSet(cr); err != nil {
+func (r *ReconcileArgoCD) reconcileStatefulSets(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) error {
+	if err := r.reconcileApplicationControllerStatefulSet(cr, useTLSForRedis); err != nil {
 		return err
 	}
 	if err := r.reconcileRedisStatefulSet(cr); err != nil {
diff --git a/controllers/argocd/util.go b/controllers/argocd/util.go
index f714614d..3c8b47c1 100644
--- a/controllers/argocd/util.go
+++ b/controllers/argocd/util.go
@@ -46,6 +46,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	networkingv1 "k8s.io/api/networking/v1"
 	v1 "k8s.io/api/rbac/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/api/resource"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
@@ -103,15 +104,26 @@ func getArgoApplicationControllerResources(cr *argoprojv1a1.ArgoCD) corev1.Resou
 }
 
 // getArgoApplicationControllerCommand will return the command for the ArgoCD Application Controller component.
-func getArgoApplicationControllerCommand(cr *argoprojv1a1.ArgoCD) []string {
+func getArgoApplicationControllerCommand(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) []string {
 	cmd := []string{
 		"argocd-application-controller",
 		"--operation-processors", fmt.Sprint(getArgoServerOperationProcessors(cr)),
 		"--redis", getRedisServerAddress(cr),
-		"--repo-server", getRepoServerAddress(cr),
-		"--status-processors", fmt.Sprint(getArgoServerStatusProcessors(cr)),
-		"--kubectl-parallelism-limit", fmt.Sprint(getArgoControllerParellismLimit(cr)),
 	}
+
+	if useTLSForRedis {
+		cmd = append(cmd, "--redis-use-tls")
+		if isRedisTLSVerificationDisabled(cr) {
+			cmd = append(cmd, "--redis-insecure-skip-tls-verify")
+		} else {
+			cmd = append(cmd, "--redis-ca-certificate", "/app/config/controller/tls/redis/tls.crt")
+		}
+	}
+
+	cmd = append(cmd, "--repo-server", getRepoServerAddress(cr))
+	cmd = append(cmd, "--status-processors", fmt.Sprint(getArgoServerStatusProcessors(cr)))
+	cmd = append(cmd, "--kubectl-parallelism-limit", fmt.Sprint(getArgoControllerParellismLimit(cr)))
+
 	if cr.Spec.Controller.AppSync != nil {
 		cmd = append(cmd, "--app-resync", strconv.FormatInt(int64(cr.Spec.Controller.AppSync.Seconds()), 10))
 	}
@@ -197,6 +209,10 @@ func isRepoServerTLSVerificationRequested(cr *argoprojv1a1.ArgoCD) bool {
 	return cr.Spec.Repo.VerifyTLS
 }
 
+func isRedisTLSVerificationDisabled(cr *argoprojv1a1.ArgoCD) bool {
+	return cr.Spec.Redis.DisableTLSVerification
+}
+
 // getArgoServerGRPCHost will return the GRPC host for the given ArgoCD.
 func getArgoServerGRPCHost(cr *argoprojv1a1.ArgoCD) string {
 	host := nameWithSuffix("grpc", cr)
@@ -447,9 +463,12 @@ func getRedisConfigPath() string {
 
 // getRedisInitScript will load the redis configuration from a template on disk for the given ArgoCD.
 // If an error occurs, an empty string value will be returned.
-func getRedisConf() string {
+func getRedisConf(useTLSForRedis bool) string {
 	path := fmt.Sprintf("%s/redis.conf.tpl", getRedisConfigPath())
-	conf, err := loadTemplateFile(path, map[string]string{})
+	params := map[string]string{
+		"UseTLS": strconv.FormatBool(useTLSForRedis),
+	}
+	conf, err := loadTemplateFile(path, params)
 	if err != nil {
 		log.Error(err, "unable to load redis configuration")
 		return ""
@@ -524,10 +543,11 @@ func getRedisHAProxyContainerImage(cr *argoprojv1a1.ArgoCD) string {
 
 // getRedisInitScript will load the redis init script from a template on disk for the given ArgoCD.
 // If an error occurs, an empty string value will be returned.
-func getRedisInitScript(cr *argoprojv1a1.ArgoCD) string {
+func getRedisInitScript(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) string {
 	path := fmt.Sprintf("%s/init.sh.tpl", getRedisConfigPath())
 	vars := map[string]string{
 		"ServiceName": nameWithSuffix("redis-ha", cr),
+		"UseTLS":      strconv.FormatBool(useTLSForRedis),
 	}
 
 	script, err := loadTemplateFile(path, vars)
@@ -540,10 +560,11 @@ func getRedisInitScript(cr *argoprojv1a1.ArgoCD) string {
 
 // getRedisHAProxySConfig will load the Redis HA Proxy configuration from a template on disk for the given ArgoCD.
 // If an error occurs, an empty string value will be returned.
-func getRedisHAProxyConfig(cr *argoprojv1a1.ArgoCD) string {
+func getRedisHAProxyConfig(cr *argoprojv1a1.ArgoCD, useTLSForRedis bool) string {
 	path := fmt.Sprintf("%s/haproxy.cfg.tpl", getRedisConfigPath())
 	vars := map[string]string{
 		"ServiceName": nameWithSuffix("redis-ha", cr),
+		"UseTLS":      strconv.FormatBool(useTLSForRedis),
 	}
 
 	script, err := loadTemplateFile(path, vars)
@@ -596,9 +617,12 @@ func getRedisHAProxyResources(cr *argoprojv1a1.ArgoCD) corev1.ResourceRequiremen
 
 // getRedisSentinelConf will load the redis sentinel configuration from a template on disk for the given ArgoCD.
 // If an error occurs, an empty string value will be returned.
-func getRedisSentinelConf() string {
+func getRedisSentinelConf(useTLSForRedis bool) string {
 	path := fmt.Sprintf("%s/sentinel.conf.tpl", getRedisConfigPath())
-	conf, err := loadTemplateFile(path, map[string]string{})
+	params := map[string]string{
+		"UseTLS": strconv.FormatBool(useTLSForRedis),
+	}
+	conf, err := loadTemplateFile(path, params)
 	if err != nil {
 		log.Error(err, "unable to load redis sentinel configuration")
 		return ""
@@ -608,9 +632,12 @@ func getRedisSentinelConf() string {
 
 // getRedisLivenessScript will load the redis liveness script from a template on disk for the given ArgoCD.
 // If an error occurs, an empty string value will be returned.
-func getRedisLivenessScript() string {
+func getRedisLivenessScript(useTLSForRedis bool) string {
 	path := fmt.Sprintf("%s/redis_liveness.sh.tpl", getRedisConfigPath())
-	conf, err := loadTemplateFile(path, map[string]string{})
+	params := map[string]string{
+		"UseTLS": strconv.FormatBool(useTLSForRedis),
+	}
+	conf, err := loadTemplateFile(path, params)
 	if err != nil {
 		log.Error(err, "unable to load redis liveness script")
 		return ""
@@ -620,9 +647,12 @@ func getRedisLivenessScript() string {
 
 // getRedisReadinessScript will load the redis readiness script from a template on disk for the given ArgoCD.
 // If an error occurs, an empty string value will be returned.
-func getRedisReadinessScript() string {
+func getRedisReadinessScript(useTLSForRedis bool) string {
 	path := fmt.Sprintf("%s/redis_readiness.sh.tpl", getRedisConfigPath())
-	conf, err := loadTemplateFile(path, map[string]string{})
+	params := map[string]string{
+		"UseTLS": strconv.FormatBool(useTLSForRedis),
+	}
+	conf, err := loadTemplateFile(path, params)
 	if err != nil {
 		log.Error(err, "unable to load redis readiness script")
 		return ""
@@ -632,9 +662,12 @@ func getRedisReadinessScript() string {
 
 // getSentinelLivenessScript will load the redis liveness script from a template on disk for the given ArgoCD.
 // If an error occurs, an empty string value will be returned.
-func getSentinelLivenessScript() string {
+func getSentinelLivenessScript(useTLSForRedis bool) string {
 	path := fmt.Sprintf("%s/sentinel_liveness.sh.tpl", getRedisConfigPath())
-	conf, err := loadTemplateFile(path, map[string]string{})
+	params := map[string]string{
+		"UseTLS": strconv.FormatBool(useTLSForRedis),
+	}
+	conf, err := loadTemplateFile(path, params)
 	if err != nil {
 		log.Error(err, "unable to load sentinel liveness script")
 		return ""
@@ -711,6 +744,55 @@ func (r *ReconcileArgoCD) reconcileCertificateAuthority(cr *argoprojv1a1.ArgoCD)
 	return nil
 }
 
+func (r *ReconcileArgoCD) redisShouldUseTLS(cr *argoprojv1a1.ArgoCD) bool {
+	var tlsSecretObj corev1.Secret
+	tlsSecretName := types.NamespacedName{Namespace: cr.Namespace, Name: common.ArgoCDRedisServerTLSSecretName}
+	err := r.Client.Get(context.TODO(), tlsSecretName, &tlsSecretObj)
+	if err != nil {
+		if !apierrors.IsNotFound(err) {
+			log.Error(err, "error looking up redis tls secret")
+		}
+		return false
+	}
+
+	secretOwnerRefs := tlsSecretObj.GetOwnerReferences()
+	if len(secretOwnerRefs) > 0 {
+		// OpenShift service CA makes the owner reference for the TLS secret to the
+		// service, which in turn is owned by the controller. This method performs
+		// a lookup of the controller through the intermediate owning service.
+		for _, secretOwner := range secretOwnerRefs {
+			if isOwnerOfInterest(secretOwner) {
+				key := client.ObjectKey{Name: secretOwner.Name, Namespace: tlsSecretObj.GetNamespace()}
+				svc := &corev1.Service{}
+
+				// Get the owning object of the secret
+				err := r.Client.Get(context.TODO(), key, svc)
+				if err != nil {
+					log.Error(err, fmt.Sprintf("could not get owner of secret %s", tlsSecretObj.GetName()))
+					return false
+				}
+
+				// If there's an object of kind ArgoCD in the owner's list,
+				// this will be our reconciled object.
+				serviceOwnerRefs := svc.GetOwnerReferences()
+				for _, serviceOwner := range serviceOwnerRefs {
+					if serviceOwner.Kind == "ArgoCD" {
+						return true
+					}
+				}
+			}
+		}
+	} else {
+		// For secrets without owner (i.e. manually created), we apply some
+		// heuristics. This may not be as accurate (e.g. if the user made a
+		// typo in the resource's name), but should be good enough for now.
+		if _, ok := tlsSecretObj.Annotations[common.AnnotationName]; ok {
+			return true
+		}
+	}
+	return false
+}
+
 // reconcileResources will reconcile common ArgoCD resources.
 func (r *ReconcileArgoCD) reconcileResources(cr *argoprojv1a1.ArgoCD) error {
 	log.Info("reconciling status")
@@ -743,8 +825,10 @@ func (r *ReconcileArgoCD) reconcileResources(cr *argoprojv1a1.ArgoCD) error {
 		return err
 	}
 
+	useTLSForRedis := r.redisShouldUseTLS(cr)
+
 	log.Info("reconciling config maps")
-	if err := r.reconcileConfigMaps(cr); err != nil {
+	if err := r.reconcileConfigMaps(cr, useTLSForRedis); err != nil {
 		return err
 	}
 
@@ -754,12 +838,12 @@ func (r *ReconcileArgoCD) reconcileResources(cr *argoprojv1a1.ArgoCD) error {
 	}
 
 	log.Info("reconciling deployments")
-	if err := r.reconcileDeployments(cr); err != nil {
+	if err := r.reconcileDeployments(cr, useTLSForRedis); err != nil {
 		return err
 	}
 
 	log.Info("reconciling statefulsets")
-	if err := r.reconcileStatefulSets(cr); err != nil {
+	if err := r.reconcileStatefulSets(cr, useTLSForRedis); err != nil {
 		return err
 	}
 
@@ -810,6 +894,10 @@ func (r *ReconcileArgoCD) reconcileResources(cr *argoprojv1a1.ArgoCD) error {
 		return err
 	}
 
+	if err := r.reconcileRedisTLSSecret(cr, useTLSForRedis); err != nil {
+		return err
+	}
+
 	if cr.Spec.SSO != nil {
 		log.Info("reconciling SSO")
 		if err := r.reconcileSSO(cr); err != nil {
diff --git a/deploy/olm-catalog/argocd-operator/0.4.0/argoproj.io_argocds.yaml b/deploy/olm-catalog/argocd-operator/0.4.0/argoproj.io_argocds.yaml
index ff3b95fb..f434f98d 100644
--- a/deploy/olm-catalog/argocd-operator/0.4.0/argoproj.io_argocds.yaml
+++ b/deploy/olm-catalog/argocd-operator/0.4.0/argoproj.io_argocds.yaml
@@ -345,6 +345,16 @@ spec:
               disableAdmin:
                 description: DisableAdmin will disable the admin user.
                 type: boolean
+              extraConfig:
+                additionalProperties:
+                  type: string
+                description: "ExtraConfig can be used to add fields to Argo CD configmap
+                  that are not supported by Argo CD CRD. \n Note: ExtraConfig takes
+                  precedence over Argo CD CRD. For example, A user sets `argocd.Spec.DisableAdmin`
+                  = true and also `a.Spec.ExtraConfig[\"admin.enabled\"]` = true.
+                  In this case, operator updates Argo CD Configmap as follows -> argocd-cm.Data[\"admin.enabled\"]
+                  = true."
+                type: object
               gaAnonymizeUsers:
                 description: GAAnonymizeUsers toggles user IDs being hashed before
                   sending to google analytics.
@@ -856,6 +866,16 @@ spec:
               redis:
                 description: Redis defines the Redis server options for ArgoCD.
                 properties:
+                  autotls:
+                    description: 'AutoTLS specifies the method to use for automatic
+                      TLS configuration for the redis server The value specified here
+                      can currently be: - openshift - Use the OpenShift service CA
+                      to request TLS config'
+                    type: string
+                  disableTLSVerification:
+                    description: DisableTLSVerification defines whether redis server
+                      API should be accessed using strict TLS validation
+                    type: boolean
                   image:
                     description: Image is the Redis container image.
                     type: string
@@ -5716,6 +5736,11 @@ spec:
                   some reason the state of the Argo CD Redis component could not be
                   obtained.'
                 type: string
+              redisTLSChecksum:
+                description: RedisTLSChecksum contains the SHA256 checksum of the
+                  latest known state of tls.crt and tls.key in the argocd-operator-redis-tls
+                  secret.
+                type: string
               repo:
                 description: 'Repo is a simple, high-level summary of where the Argo
                   CD Repo component is in its lifecycle. There are five possible repo
diff --git a/docs/reference/argocd.md b/docs/reference/argocd.md
index 4f217598..9fc6a5a6 100644
--- a/docs/reference/argocd.md
+++ b/docs/reference/argocd.md
@@ -784,6 +784,8 @@ The following properties are available for configuring the Redis component.
 
 Name | Default | Description
 --- | --- | ---
+AutoTLS | "" | Provider to use for creating the redis server's TLS certificate (one of: `openshift`). Currently only available for OpenShift.
+DisableTLSVerification | false | defines whether the redis server should be accessed using strict TLS validation
 Image | `redis` | The container image for Redis. This overrides the `ARGOCD_REDIS_IMAGE` environment variable.
 Resources | [Empty] | The container compute resources.
 Version | 5.0.3 (SHA) | The tag to use with the Redis container image.
@@ -804,6 +806,8 @@ spec:
     image: redis
     resources: {}
     version: "5.0.3"
+    disableTLSVerification: false
+    autotls: ""
 ```
 
 ## Repo Options
diff --git a/docs/usage/custom_roles.md b/docs/usage/custom_roles.md
index 94254294..04633217 100644
--- a/docs/usage/custom_roles.md
+++ b/docs/usage/custom_roles.md
@@ -38,3 +38,19 @@ spec:
           - name: SERVER_CLUSTER_ROLE
             value: custom-server-role
 ```
+
+
+When an Argo CD instance is deleted, namespaces managed by that instance (via the `argocd.argoproj.io/managed-by` label ) will retain the label by default. Users can change this behavior by setting the environment variable `REMOVE_MANAGED_BY_LABEL_ON_ARGOCD_DELETION` to `true` in the Subscription.
+
+```yaml
+apiVersion: operators.coreos.com/v1alpha1
+kind: Subscription
+metadata:
+  name: argocd-operator
+  namespace: argocd
+spec:
+  config:
+    env:
+    - name: REMOVE_MANAGED_BY_LABEL_ON_ARGOCD_DELETION
+      value: "true"
+```
\ No newline at end of file
diff --git a/docs/usage/extra-config.md b/docs/usage/extra-config.md
new file mode 100644
index 00000000..17a5ea46
--- /dev/null
+++ b/docs/usage/extra-config.md
@@ -0,0 +1,30 @@
+# Configure Argo CD ConfigMap for features not supported by Argo CD CRD
+
+## Motivation
+
+As the Argo CD upstream project evolves, new features are continuously added and new Argo CD config map configurations are constantly introduced. It is very difficult to keep Argocd Operator up to date with Argo CD’s new configurations. Argocd Operator is lagging behind to support new Argo CD features in months or more.
+
+But, oftentimes to support a new feature, it is as simple as reconciling a configmap entry.  However, the code to make this a first class configuration by making it as a field in Argocd CR requires many changes. It takes quite a bit of development effort to reconcile just one new configmap entry. Also, since we made a new field in Argo CD CR, User will have to read the Argocd Operator’s manual in order to figure out the name of the field even though the user may already know the configmap entry key.
+
+## Using ExtraConfig
+
+Users can add dynamic entries to Argo CD configmap using `ExtraConfig` in Argocd CR. It is completely optional and has no default value.
+
+When `ExtraConfig` is set, the entries specified are reconciled to the live Argo CD configmap. Users can specify arbitrary configmap entries with this `ExtraConfig`. This allows users to specify a new configuration even though the configuration is not supported by Argo CD CRD.
+
+**Note**: `ExtraConfig` takes precedence over Argo CD CRD.
+
+## Example
+
+```yaml
+apiVersion: argoproj.io/v1alpha1
+kind: ArgoCD
+metadata:
+  name: example-argocd
+spec:
+  extraConfig: 
+    "ping": "pong" // The same entry is reflected in Argo CD Configmap.
+  server:
+    ingress:
+      enabled: true
+```
diff --git a/mkdocs.yml b/mkdocs.yml
index 93a5b35d..a742f288 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -28,6 +28,7 @@ nav:
   - Usage: 
     - Basics: usage/basics.md
     - Export: usage/export.md
+    - ExtraConfig: usage/extra-config.md
     - High Availability: usage/ha.md
     - Ingress: usage/ingress.md
     - Insights: usage/insights.md


Test patch:
diff --git a/controllers/argocd/applicationset_test.go b/controllers/argocd/applicationset_test.go
index 6b89fb67..cf669c13 100644
--- a/controllers/argocd/applicationset_test.go
+++ b/controllers/argocd/applicationset_test.go
@@ -38,8 +38,9 @@ import (
 func applicationSetDefaultVolumeMounts() []corev1.VolumeMount {
 	repoMounts := repoServerDefaultVolumeMounts()
 	ignoredMounts := map[string]bool{
-		"plugins":                true,
-		"argocd-repo-server-tls": true,
+		"plugins":                             true,
+		"argocd-repo-server-tls":              true,
+		common.ArgoCDRedisServerTLSSecretName: true,
 	}
 	mounts := make([]corev1.VolumeMount, len(repoMounts)-len(ignoredMounts), len(repoMounts)-len(ignoredMounts))
 	j := 0
@@ -55,9 +56,10 @@ func applicationSetDefaultVolumeMounts() []corev1.VolumeMount {
 func applicationSetDefaultVolumes() []corev1.Volume {
 	repoVolumes := repoServerDefaultVolumes()
 	ignoredVolumes := map[string]bool{
-		"var-files":              true,
-		"plugins":                true,
-		"argocd-repo-server-tls": true,
+		"var-files":                           true,
+		"plugins":                             true,
+		"argocd-repo-server-tls":              true,
+		common.ArgoCDRedisServerTLSSecretName: true,
 	}
 	volumes := make([]corev1.Volume, len(repoVolumes)-len(ignoredVolumes), len(repoVolumes)-len(ignoredVolumes))
 	j := 0
diff --git a/controllers/argocd/argocd_controller_test.go b/controllers/argocd/argocd_controller_test.go
index 9a58399e..afdcb3af 100644
--- a/controllers/argocd/argocd_controller_test.go
+++ b/controllers/argocd/argocd_controller_test.go
@@ -17,6 +17,7 @@ package argocd
 import (
 	"context"
 	"fmt"
+	"os"
 	"testing"
 	"time"
 
@@ -102,6 +103,75 @@ func TestReconcileArgoCD_Reconcile(t *testing.T) {
 	}
 }
 
+func TestReconcileArgoCD_Reconcile_RemoveManagedByLabelOnArgocdDeletion(t *testing.T) {
+	restoreEnv(t)
+	logf.SetLogger(ZapLogger(true))
+	
+	tests := []struct {
+		testName									string
+		nsName										string
+		isRemoveManagedByLabelOnArgoCDDeletionSet	bool
+		}{
+			{
+				testName: 									"Without REMOVE_MANAGED_BY_LABEL_ON_ARGOCD_DELETION set",
+				nsName:										"newNamespaceTest1",
+				isRemoveManagedByLabelOnArgoCDDeletionSet: 	false,
+			},
+			{
+				testName: 									"With REMOVE_MANAGED_BY_LABEL_ON_ARGOCD_DELETION set",
+				nsName:										"newNamespaceTest2",
+				isRemoveManagedByLabelOnArgoCDDeletionSet: 	true,
+			},
+		}
+
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			a := makeTestArgoCD(deletedAt(time.Now()), addFinalizer(common.ArgoCDDeletionFinalizer))
+			r := makeTestReconciler(t, a)
+
+			nsArgocd := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
+				Name: a.Namespace,
+			}}
+			err := r.Client.Create(context.TODO(), nsArgocd)
+			assert.NoError(t, err)
+
+			if (test.isRemoveManagedByLabelOnArgoCDDeletionSet) {
+				os.Setenv("REMOVE_MANAGED_BY_LABEL_ON_ARGOCD_DELETION", "true")
+			}
+		
+			ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
+				Name: test.nsName,
+				Labels: map[string]string{
+					common.ArgoCDManagedByLabel: a.Namespace,
+				}},
+			}
+			err = r.Client.Create(context.TODO(), ns)
+			assert.NoError(t, err)
+			
+			req := reconcile.Request{
+				NamespacedName: types.NamespacedName{
+					Name:      a.Name,
+					Namespace: a.Namespace,
+				},
+			}
+		
+			_, err = r.Reconcile(context.TODO(), req)
+			assert.NoError(t, err)
+		
+			assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{Name: ns.Name}, ns))
+			if (test.isRemoveManagedByLabelOnArgoCDDeletionSet) {
+				// Check if the managed-by label gets removed from the new namespace
+				if _, ok := ns.Labels[common.ArgoCDManagedByLabel]; ok {
+					t.Errorf("Expected the label[%v] to be removed from the namespace[%v]", common.ArgoCDManagedByLabel, ns.Name)
+				}
+			} else {
+				// Check if the managed-by label still exists in the new namespace
+				assert.Equal(t, ns.Labels[common.ArgoCDManagedByLabel], a.Namespace)
+			}
+		})
+	}
+}
+
 func deletedAt(now time.Time) argoCDOpt {
 	return func(a *argov1alpha1.ArgoCD) {
 		wrapped := metav1.NewTime(now)
diff --git a/controllers/argocd/configmap_test.go b/controllers/argocd/configmap_test.go
index 097076fb..9fd2ac6f 100644
--- a/controllers/argocd/configmap_test.go
+++ b/controllers/argocd/configmap_test.go
@@ -571,3 +571,82 @@ func TestReconcileArgoCD_reconcileArgoConfigMap_withResourceCustomizations(t *te
 		t.Fatalf("reconcileArgoConfigMap failed got %q, want %q", c, customizations)
 	}
 }
+
+func TestReconcileArgoCD_reconcileArgoConfigMap_withExtraConfig(t *testing.T) {
+	a := makeTestArgoCD()
+	r := makeTestReconciler(t, a)
+
+	err := r.reconcileArgoConfigMap(a)
+	assert.NoError(t, err)
+
+	// Verify Argo CD configmap is created.
+	cm := &corev1.ConfigMap{}
+	err = r.Client.Get(context.TODO(), types.NamespacedName{
+		Name:      common.ArgoCDConfigMapName,
+		Namespace: testNamespace,
+	}, cm)
+	assert.NoError(t, err)
+
+	// Verify that updates to the configmap are rejected(reconciled back to default) by the operator.
+	cm.Data["ping"] = "pong"
+	err = r.Client.Update(context.TODO(), cm)
+	assert.NoError(t, err)
+
+	err = r.reconcileArgoConfigMap(a)
+	assert.NoError(t, err)
+
+	err = r.Client.Get(context.TODO(), types.NamespacedName{
+		Name:      common.ArgoCDConfigMapName,
+		Namespace: testNamespace,
+	}, cm)
+	assert.NoError(t, err)
+
+	assert.Equal(t, cm.Data["ping"], "")
+
+	// Verify that operator updates argocd-cm according to ExtraConfig.
+	a.Spec.ExtraConfig = map[string]string{
+		"foo": "bar",
+	}
+
+	err = r.reconcileArgoConfigMap(a)
+	assert.NoError(t, err)
+
+	err = r.Client.Get(context.TODO(), types.NamespacedName{
+		Name:      common.ArgoCDConfigMapName,
+		Namespace: testNamespace,
+	}, cm)
+	assert.NoError(t, err)
+
+	assert.Equal(t, cm.Data["foo"], "bar")
+
+	// Verify that ExtraConfig overrides FirstClass entries
+	a.Spec.DisableAdmin = true
+	a.Spec.ExtraConfig["admin.enabled"] = "true"
+
+	err = r.reconcileArgoConfigMap(a)
+	assert.NoError(t, err)
+
+	err = r.Client.Get(context.TODO(), types.NamespacedName{
+		Name:      common.ArgoCDConfigMapName,
+		Namespace: testNamespace,
+	}, cm)
+
+	assert.NoError(t, err)
+	assert.Equal(t, cm.Data["admin.enabled"], "true")
+
+	// Verify that deletion of a field from ExtraConfig does not delete any existing configuration
+	// created by FirstClass citizens.
+	a.Spec.ExtraConfig = make(map[string]string, 0)
+
+	err = r.reconcileArgoConfigMap(a)
+	assert.NoError(t, err)
+
+	err = r.Client.Get(context.TODO(), types.NamespacedName{
+		Name:      common.ArgoCDConfigMapName,
+		Namespace: testNamespace,
+	}, cm)
+
+	assert.NoError(t, err)
+	assert.Equal(t, cm.Data["admin.enabled"], "false")
+
+}
diff --git a/controllers/argocd/custommapper_test.go b/controllers/argocd/custommapper_test.go
index 7f30fc82..95d43e1c 100644
--- a/controllers/argocd/custommapper_test.go
+++ b/controllers/argocd/custommapper_test.go
@@ -103,7 +103,7 @@ func TestReconcileArgoCD_clusterRoleBindingMapper(t *testing.T) {
 	}
 }
 
-func TestReconcileArgoCD_tlsSecretMapper(t *testing.T) {
+func TestReconcileArgoCD_tlsSecretMapperRepoServer(t *testing.T) {
 	argocd := &v1alpha1.ArgoCD{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "argocd",
@@ -320,6 +320,223 @@ func TestReconcileArgoCD_tlsSecretMapper(t *testing.T) {
 
 }
 
+func TestReconcileArgoCD_tlsSecretMapperRedis(t *testing.T) {
+	argocd := &v1alpha1.ArgoCD{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "argocd",
+			Namespace: "argocd-operator",
+			UID:       "abcd",
+		},
+	}
+
+	t.Run("Map with proper ownerReference", func(t *testing.T) {
+		service := &v1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "argocd-redis",
+				Namespace: "argocd-operator",
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						APIVersion: "argoproj.io/v1alpha1",
+						Kind:       "ArgoCD",
+						Name:       "argocd",
+						UID:        argocd.GetUID(),
+					},
+				},
+				UID: "service-123",
+			},
+		}
+		secret := &corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "argocd-operator-redis-tls",
+				Namespace: "argocd-operator",
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						APIVersion: "v1",
+						Kind:       "Service",
+						Name:       "argocd-redis",
+						UID:        service.GetUID(),
+					},
+				},
+			},
+			Type: corev1.SecretTypeTLS,
+			Data: map[string][]byte{
+				corev1.TLSCertKey:       []byte("foo"),
+				corev1.TLSPrivateKeyKey: []byte("bar"),
+			},
+		}
+		objs := []runtime.Object{
+			argocd,
+			secret,
+			service,
+		}
+		r := makeReconciler(t, argocd, objs...)
+		want := []reconcile.Request{
+			{
+				NamespacedName: types.NamespacedName{
+					Name:      "argocd",
+					Namespace: "argocd-operator",
+				},
+			},
+		}
+		got := r.tlsSecretMapper(secret)
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("Reconciliation unsucessful: got: %v, want: %v", got, want)
+		}
+	})
+
+	t.Run("Map with ownerReference on non-existing owner", func(t *testing.T) {
+		service := &v1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "argocd-redis",
+				Namespace: "argocd-operator",
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						APIVersion: "argoproj.io/v1alpha1",
+						Kind:       "ArgoCD",
+						Name:       "argocd",
+						UID:        argocd.GetUID(),
+					},
+				},
+				UID: "service-123",
+			},
+		}
+		secret := &corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "argocd-operator-redis-tls",
+				Namespace: "argocd-operator",
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						APIVersion: "v1",
+						Kind:       "Service",
+						Name:       "argocd-redis",
+						UID:        service.GetUID(),
+					},
+				},
+			},
+			Type: corev1.SecretTypeTLS,
+			Data: map[string][]byte{
+				corev1.TLSCertKey:       []byte("foo"),
+				corev1.TLSPrivateKeyKey: []byte("bar"),
+			},
+		}
+		objs := []runtime.Object{
+			argocd,
+			secret,
+		}
+		r := makeReconciler(t, argocd, objs...)
+		want := []reconcile.Request{}
+		got := r.tlsSecretMapper(secret)
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("Reconciliation unsucessful: got: %v, want: %v", got, want)
+		}
+	})
+
+	t.Run("Map with invalid owner", func(t *testing.T) {
+		service := &v1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "argocd-redis",
+				Namespace: "argocd-operator",
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						APIVersion: "argoproj.io/v1alpha1",
+						Kind:       "ArgoCD",
+						Name:       "argocd",
+						UID:        argocd.GetUID(),
+					},
+				},
+				UID: "service-123",
+			},
+		}
+		secret := &corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "argocd-operator-redis-tls",
+				Namespace: "argocd-operator",
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						APIVersion: "v1",
+						Kind:       "Service",
+						Name:       "argocd-server",
+						UID:        service.GetUID(),
+					},
+				},
+			},
+			Type: corev1.SecretTypeTLS,
+			Data: map[string][]byte{
+				corev1.TLSCertKey:       []byte("foo"),
+				corev1.TLSPrivateKeyKey: []byte("bar"),
+			},
+		}
+		objs := []runtime.Object{
+			argocd,
+			secret,
+			service,
+		}
+		r := makeReconciler(t, argocd, objs...)
+		want := []reconcile.Request{}
+		got := r.tlsSecretMapper(secret)
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("Reconciliation unsucessful: got: %v, want: %v", got, want)
+		}
+	})
+
+	t.Run("Map with owner annotation", func(t *testing.T) {
+		secret := &corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "argocd-operator-redis-tls",
+				Namespace: "argocd-operator",
+				Annotations: map[string]string{
+					common.AnnotationName: "argocd",
+				},
+			},
+			Type: corev1.SecretTypeTLS,
+			Data: map[string][]byte{
+				corev1.TLSCertKey:       []byte("foo"),
+				corev1.TLSPrivateKeyKey: []byte("bar"),
+			},
+		}
+		objs := []runtime.Object{
+			secret,
+		}
+		r := makeReconciler(t, argocd, objs...)
+		want := []reconcile.Request{
+			{
+				NamespacedName: types.NamespacedName{
+					Name:      "argocd",
+					Namespace: "argocd-operator",
+				},
+			},
+		}
+		got := r.tlsSecretMapper(secret)
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("Reconciliation unsucessful: got: %v, want: %v", got, want)
+		}
+	})
+
+	t.Run("Map without owner and without annotation", func(t *testing.T) {
+		secret := &corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "argocd-operator-redis-tls",
+				Namespace: "argocd-operator",
+			},
+			Type: corev1.SecretTypeTLS,
+			Data: map[string][]byte{
+				corev1.TLSCertKey:       []byte("foo"),
+				corev1.TLSPrivateKeyKey: []byte("bar"),
+			},
+		}
+		objs := []runtime.Object{
+			secret,
+		}
+		r := makeReconciler(t, argocd, objs...)
+		want := []reconcile.Request{}
+		got := r.tlsSecretMapper(secret)
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("Reconciliation unsucessful: got: %v, want: %v", got, want)
+		}
+	})
+
+}
+
 func TestReconcileArgoCD_namespaceResourceMapper(t *testing.T) {
 	a := makeTestArgoCD()
 	r := makeTestReconciler(t, a)
diff --git a/controllers/argocd/deployment_test.go b/controllers/argocd/deployment_test.go
index 55da5db3..843c01b7 100644
--- a/controllers/argocd/deployment_test.go
+++ b/controllers/argocd/deployment_test.go
@@ -3,6 +3,7 @@ package argocd
 import (
 	"context"
 	"os"
+	"reflect"
 	"strings"
 	"testing"
 	"time"
@@ -65,7 +66,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_replicas(t *testing.T) {
 			})
 			r := makeTestReconciler(t, a)
 
-			err := r.reconcileRepoDeployment(a)
+			err := r.reconcileRepoDeployment(a, false)
 			assert.NoError(t, err)
 
 			deployment := &appsv1.Deployment{}
@@ -116,7 +117,7 @@ func TestReconcileArgoCD_reconcile_ServerDeployment_replicas(t *testing.T) {
 			})
 			r := makeTestReconciler(t, a)
 
-			err := r.reconcileServerDeployment(a)
+			err := r.reconcileServerDeployment(a, false)
 			assert.NoError(t, err)
 
 			deployment := &appsv1.Deployment{}
@@ -157,7 +158,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_loglevel(t *testing.T) {
 
 		r := makeTestReconciler(t, lglv)
 
-		err := r.reconcileRepoDeployment(lglv)
+		err := r.reconcileRepoDeployment(lglv, false)
 		assert.NoError(t, err)
 		deployment := &appsv1.Deployment{}
 		err = r.Client.Get(context.TODO(), types.NamespacedName{
@@ -191,7 +192,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_volumes(t *testing.T) {
 		a := makeTestArgoCD()
 		r := makeTestReconciler(t, a)
 
-		err := r.reconcileRepoDeployment(a)
+		err := r.reconcileRepoDeployment(a, false)
 		assert.NoError(t, err)
 		deployment := &appsv1.Deployment{}
 		err = r.Client.Get(context.TODO(), types.NamespacedName{
@@ -216,7 +217,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_volumes(t *testing.T) {
 		})
 		r := makeTestReconciler(t, a)
 
-		err := r.reconcileRepoDeployment(a)
+		err := r.reconcileRepoDeployment(a, false)
 		assert.NoError(t, err)
 		deployment := &appsv1.Deployment{}
 		err = r.Client.Get(context.TODO(), types.NamespacedName{
@@ -246,7 +247,7 @@ func TestReconcileArgoCD_reconcile_ServerDeployment_env(t *testing.T) {
 		a.Spec.Repo.ExecTimeout = &timeout
 		r := makeTestReconciler(t, a)
 
-		err := r.reconcileServerDeployment(a)
+		err := r.reconcileServerDeployment(a, false)
 		assert.NoError(t, err)
 		deployment := &appsv1.Deployment{}
 		err = r.Client.Get(context.TODO(), types.NamespacedName{
@@ -280,7 +281,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_env(t *testing.T) {
 		a.Spec.Repo.ExecTimeout = &timeout
 		r := makeTestReconciler(t, a)
 
-		err := r.reconcileRepoDeployment(a)
+		err := r.reconcileRepoDeployment(a, false)
 		assert.NoError(t, err)
 		deployment := &appsv1.Deployment{}
 		err = r.Client.Get(context.TODO(), types.NamespacedName{
@@ -302,7 +303,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_env(t *testing.T) {
 		a.Spec.Repo.ExecTimeout = &timeout
 		r := makeTestReconciler(t, a)
 
-		err := r.reconcileRepoDeployment(a)
+		err := r.reconcileRepoDeployment(a, false)
 		assert.NoError(t, err)
 		deployment := &appsv1.Deployment{}
 		err = r.Client.Get(context.TODO(), types.NamespacedName{
@@ -328,7 +329,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_env(t *testing.T) {
 		}
 		r := makeTestReconciler(t, a)
 
-		err := r.reconcileRepoDeployment(a)
+		err := r.reconcileRepoDeployment(a, false)
 		assert.NoError(t, err)
 		deployment := &appsv1.Deployment{}
 		err = r.Client.Get(context.TODO(), types.NamespacedName{
@@ -345,7 +346,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_env(t *testing.T) {
 		a := makeTestArgoCD()
 		r := makeTestReconciler(t, a)
 
-		err := r.reconcileRepoDeployment(a)
+		err := r.reconcileRepoDeployment(a, false)
 		assert.NoError(t, err)
 		deployment := &appsv1.Deployment{}
 		err = r.Client.Get(context.TODO(), types.NamespacedName{
@@ -365,7 +366,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_mounts(t *testing.T) {
 		a := makeTestArgoCD()
 		r := makeTestReconciler(t, a)
 
-		err := r.reconcileRepoDeployment(a)
+		err := r.reconcileRepoDeployment(a, false)
 		assert.NoError(t, err)
 
 		deployment := &appsv1.Deployment{}
@@ -389,7 +390,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_mounts(t *testing.T) {
 		})
 		r := makeTestReconciler(t, a)
 
-		err := r.reconcileRepoDeployment(a)
+		err := r.reconcileRepoDeployment(a, false)
 		assert.NoError(t, err)
 
 		deployment := &appsv1.Deployment{}
@@ -413,7 +414,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_initContainers(t *testing.T) {
 	})
 	r := makeTestReconciler(t, a)
 
-	err := r.reconcileRepoDeployment(a)
+	err := r.reconcileRepoDeployment(a, false)
 	assert.NoError(t, err)
 
 	deployment := &appsv1.Deployment{}
@@ -449,7 +450,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_missingInitContainers(t *testin
 	}
 	r := makeTestReconciler(t, a, d)
 
-	err := r.reconcileRepoDeployment(a)
+	err := r.reconcileRepoDeployment(a, false)
 	assert.NoError(t, err)
 	deployment := &appsv1.Deployment{}
 	err = r.Client.Get(context.TODO(), types.NamespacedName{
@@ -490,7 +491,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_unexpectedInitContainer(t *test
 	}
 	r := makeTestReconciler(t, a, d)
 
-	err := r.reconcileRepoDeployment(a)
+	err := r.reconcileRepoDeployment(a, false)
 	assert.NoError(t, err)
 	deployment := &appsv1.Deployment{}
 	err = r.Client.Get(context.TODO(), types.NamespacedName{
@@ -507,7 +508,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_command(t *testing.T) {
 	a := makeTestArgoCD()
 	r := makeTestReconciler(t, a)
 
-	err := r.reconcileRepoDeployment(a)
+	err := r.reconcileRepoDeployment(a, false)
 	assert.NoError(t, err)
 
 	deployment := &appsv1.Deployment{}
@@ -518,7 +519,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_command(t *testing.T) {
 	assert.NoError(t, err)
 
 	deployment.Spec.Template.Spec.Containers[0].Command[6] = "debug"
-	err = r.reconcileRepoDeployment(a)
+	err = r.reconcileRepoDeployment(a, false)
 
 	assert.Equal(t, "debug", deployment.Spec.Template.Spec.Containers[0].Command[6])
 }
@@ -606,7 +607,7 @@ func TestReconcileArgoCD_reconcileDeployments_proxy(t *testing.T) {
 	})
 	r := makeTestReconciler(t, a)
 
-	err := r.reconcileDeployments(a)
+	err := r.reconcileDeployments(a, false)
 	assert.NoError(t, err)
 
 	for _, v := range deploymentNames {
@@ -626,7 +627,7 @@ func TestReconcileArgoCD_reconcileDeployments_proxy_update_existing(t *testing.T
 		a.Spec.Grafana.Enabled = true
 	})
 	r := makeTestReconciler(t, a)
-	err := r.reconcileDeployments(a)
+	err := r.reconcileDeployments(a, false)
 	assert.NoError(t, err)
 	for _, v := range deploymentNames {
 		refuteDeploymentHasProxyVars(t, r.Client, v)
@@ -638,7 +639,7 @@ func TestReconcileArgoCD_reconcileDeployments_proxy_update_existing(t *testing.T
 
 	logf.SetLogger(ZapLogger(true))
 
-	err = r.reconcileDeployments(a)
+	err = r.reconcileDeployments(a, false)
 	assert.NoError(t, err)
 
 	for _, v := range deploymentNames {
@@ -659,7 +660,7 @@ func TestReconcileArgoCD_reconcileDeployments_HA_proxy(t *testing.T) {
 	})
 	r := makeTestReconciler(t, a)
 
-	err := r.reconcileDeployments(a)
+	err := r.reconcileDeployments(a, false)
 	assert.NoError(t, err)
 
 	assertDeploymentHasProxyVars(t, r.Client, "argocd-redis-ha-haproxy")
@@ -731,7 +732,7 @@ func TestReconcileArgoCD_reconcileRepoDeployment_updatesVolumeMounts(t *testing.
 	}
 	r := makeTestReconciler(t, a, d)
 
-	err := r.reconcileRepoDeployment(a)
+	err := r.reconcileRepoDeployment(a, false)
 	assert.NoError(t, err)
 
 	deployment := &appsv1.Deployment{}
@@ -741,8 +742,8 @@ func TestReconcileArgoCD_reconcileRepoDeployment_updatesVolumeMounts(t *testing.
 	}, deployment)
 	assert.NoError(t, err)
 
-	assert.Len(t, deployment.Spec.Template.Spec.Volumes, 8)
-	assert.Len(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts, 7)
+	assert.Len(t, deployment.Spec.Template.Spec.Volumes, 9)
+	assert.Len(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts, 8)
 }
 
 func Test_proxyEnvVars(t *testing.T) {
@@ -790,7 +791,7 @@ func TestReconcileArgoCD_reconcileDeployment_nodePlacement(t *testing.T) {
 		}
 	}))
 	r := makeTestReconciler(t, a)
-	err := r.reconcileRepoDeployment(a) //can use other deployments as well
+	err := r.reconcileRepoDeployment(a, false) //can use other deployments as well
 	assert.NoError(t, err)
 	deployment := &appsv1.Deployment{}
 	err = r.Client.Get(context.TODO(), types.NamespacedName{
@@ -1008,11 +1009,69 @@ func TestReconcileArgoCD_reconcileDexDeployment_withUpdate(t *testing.T) {
 	assert.Equal(t, want, deployment.Spec.Template.Spec)
 }
 
+func TestReconcileArgocd_reconcileRepoServerRedisTLS(t *testing.T) {
+	t.Run("with DisableTLSVerification = false (the default)", func(t *testing.T) {
+		logf.SetLogger(ZapLogger(true))
+		a := makeTestArgoCD()
+		r := makeTestReconciler(t, a)
+		assert.NoError(t, r.reconcileRepoDeployment(a, true))
+
+		deployment := &appsv1.Deployment{}
+		assert.NoError(t, r.Client.Get(
+			context.TODO(),
+			types.NamespacedName{
+				Name:      "argocd-repo-server",
+				Namespace: a.Namespace,
+			},
+			deployment))
+
+		wantCmd := []string{
+			"uid_entrypoint.sh",
+			"argocd-repo-server",
+			"--redis", "argocd-redis.argocd.svc.cluster.local:6379",
+			"--redis-use-tls",
+			"--redis-ca-certificate", "/app/config/reposerver/tls/redis/tls.crt",
+			"--loglevel", "info",
+			"--logformat", "text",
+		}
+		assert.Equal(t, wantCmd, deployment.Spec.Template.Spec.Containers[0].Command)
+	})
+
+	t.Run("with DisableTLSVerification = true", func(t *testing.T) {
+		logf.SetLogger(ZapLogger(true))
+		a := makeTestArgoCD(func(cd *argoprojv1alpha1.ArgoCD) {
+			cd.Spec.Redis.DisableTLSVerification = true
+		})
+		r := makeTestReconciler(t, a)
+		assert.NoError(t, r.reconcileRepoDeployment(a, true))
+
+		deployment := &appsv1.Deployment{}
+		assert.NoError(t, r.Client.Get(
+			context.TODO(),
+			types.NamespacedName{
+				Name:      "argocd-repo-server",
+				Namespace: a.Namespace,
+			},
+			deployment))
+
+		wantCmd := []string{
+			"uid_entrypoint.sh",
+			"argocd-repo-server",
+			"--redis", "argocd-redis.argocd.svc.cluster.local:6379",
+			"--redis-use-tls",
+			"--redis-insecure-skip-tls-verify",
+			"--loglevel", "info",
+			"--logformat", "text",
+		}
+		assert.Equal(t, wantCmd, deployment.Spec.Template.Spec.Containers[0].Command)
+	})
+}
+
 func TestReconcileArgoCD_reconcileServerDeployment(t *testing.T) {
 	logf.SetLogger(ZapLogger(true))
 	a := makeTestArgoCD()
 	r := makeTestReconciler(t, a)
-	assert.NoError(t, r.reconcileServerDeployment(a))
+	assert.NoError(t, r.reconcileServerDeployment(a, false))
 
 	deployment := &appsv1.Deployment{}
 	assert.NoError(t, r.Client.Get(
@@ -1075,6 +1134,35 @@ func TestReconcileArgoCD_reconcileServerDeployment(t *testing.T) {
 	}
 
 	assert.Equal(t, want, deployment.Spec.Template.Spec)
+
+	assert.NoError(t, r.reconcileServerDeployment(a, true))
+	deployment = &appsv1.Deployment{}
+	assert.NoError(t, r.Client.Get(
+		context.TODO(),
+		types.NamespacedName{
+			Name:      "argocd-server",
+			Namespace: a.Namespace,
+		},
+		deployment))
+	wantCmd := []string{
+		"argocd-server",
+		"--staticassets",
+		"/shared/app",
+		"--dex-server",
+		"http://argocd-dex-server.argocd.svc.cluster.local:5556",
+		"--repo-server",
+		"argocd-repo-server.argocd.svc.cluster.local:8081",
+		"--redis",
+		"argocd-redis.argocd.svc.cluster.local:6379",
+		"--redis-use-tls",
+		"--redis-ca-certificate",
+		"/app/config/server/tls/redis/tls.crt",
+		"--loglevel",
+		"info",
+		"--logformat",
+		"text",
+	}
+	assert.Equal(t, wantCmd, deployment.Spec.Template.Spec.Containers[0].Command)
 }
 
 func TestArgoCDServerDeploymentCommand(t *testing.T) {
@@ -1104,7 +1192,7 @@ func TestArgoCDServerDeploymentCommand(t *testing.T) {
 	}
 
 	deployment := &appsv1.Deployment{}
-	assert.NoError(t, r.reconcileServerDeployment(a))
+	assert.NoError(t, r.reconcileServerDeployment(a, false))
 
 	assert.NoError(t, r.Client.Get(
 		context.TODO(),
@@ -1126,7 +1214,7 @@ func TestArgoCDServerDeploymentCommand(t *testing.T) {
 		"test",
 	}
 
-	assert.NoError(t, r.reconcileServerDeployment(a))
+	assert.NoError(t, r.reconcileServerDeployment(a, false))
 	assert.NoError(t, r.Client.Get(
 		context.TODO(),
 		types.NamespacedName{
@@ -1144,7 +1232,7 @@ func TestArgoCDServerDeploymentCommand(t *testing.T) {
 		"foo.scv.cluster.local:6379",
 	}
 
-	assert.NoError(t, r.reconcileServerDeployment(a))
+	assert.NoError(t, r.reconcileServerDeployment(a, false))
 	assert.NoError(t, r.Client.Get(
 		context.TODO(),
 		types.NamespacedName{
@@ -1158,7 +1246,7 @@ func TestArgoCDServerDeploymentCommand(t *testing.T) {
 	// Remove all the command arguments that were added.
 	a.Spec.Server.ExtraCommandArgs = []string{}
 
-	assert.NoError(t, r.reconcileServerDeployment(a))
+	assert.NoError(t, r.reconcileServerDeployment(a, false))
 	assert.NoError(t, r.Client.Get(
 		context.TODO(),
 		types.NamespacedName{
@@ -1187,7 +1275,7 @@ func TestReconcileArgoCD_reconcileServerDeploymentWithInsecure(t *testing.T) {
 	})
 	r := makeTestReconciler(t, a)
 
-	assert.NoError(t, r.reconcileServerDeployment(a))
+	assert.NoError(t, r.reconcileServerDeployment(a, false))
 
 	deployment := &appsv1.Deployment{}
 	assert.NoError(t, r.Client.Get(
@@ -1258,12 +1346,12 @@ func TestReconcileArgoCD_reconcileServerDeploymentChangedToInsecure(t *testing.T
 	a := makeTestArgoCD()
 	r := makeTestReconciler(t, a)
 
-	assert.NoError(t, r.reconcileServerDeployment(a))
+	assert.NoError(t, r.reconcileServerDeployment(a, false))
 
 	a = makeTestArgoCD(func(a *argoprojv1alpha1.ArgoCD) {
 		a.Spec.Server.Insecure = true
 	})
-	assert.NoError(t, r.reconcileServerDeployment(a))
+	assert.NoError(t, r.reconcileServerDeployment(a, false))
 
 	deployment := &appsv1.Deployment{}
 	assert.NoError(t, r.Client.Get(
@@ -1329,6 +1417,48 @@ func TestReconcileArgoCD_reconcileServerDeploymentChangedToInsecure(t *testing.T
 	assert.Equal(t, want, deployment.Spec.Template.Spec)
 }
 
+func TestReconcileArgoCD_reconcileRedisDeploymentWithoutTLS(t *testing.T) {
+	cr := makeTestArgoCD()
+	r := makeTestReconciler(t, cr)
+
+	want := []string{
+		"--save",
+		"",
+		"--appendonly", "no",
+	}
+
+	assert.NoError(t, r.reconcileRedisDeployment(cr, false))
+	d := &appsv1.Deployment{}
+	assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{Name: cr.Name + "-redis", Namespace: cr.Namespace}, d))
+	got := d.Spec.Template.Spec.Containers[0].Args
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("Reconciliation unsucessful: got: %v, want: %v", got, want)
+	}
+}
+
+func TestReconcileArgoCD_reconcileRedisDeploymentWithTLS(t *testing.T) {
+	cr := makeTestArgoCD()
+	r := makeTestReconciler(t, cr)
+
+	want := []string{
+		"--save", "",
+		"--appendonly", "no",
+		"--tls-port", "6379",
+		"--port", "0",
+		"--tls-cert-file", "/app/config/redis/tls/tls.crt",
+		"--tls-key-file", "/app/config/redis/tls/tls.key",
+		"--tls-auth-clients", "no",
+	}
+
+	assert.NoError(t, r.reconcileRedisDeployment(cr, true))
+	d := &appsv1.Deployment{}
+	assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{Name: cr.Name + "-redis", Namespace: cr.Namespace}, d))
+	got := d.Spec.Template.Spec.Containers[0].Args
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("Reconciliation unsucessful: got: %v, want: %v", got, want)
+	}
+}
+
 func TestReconcileArgoCD_reconcileRedisDeployment(t *testing.T) {
 	// tests reconciler hook for redis deployment
 	cr := makeTestArgoCD()
@@ -1337,7 +1467,7 @@ func TestReconcileArgoCD_reconcileRedisDeployment(t *testing.T) {
 	defer resetHooks()()
 	Register(testDeploymentHook)
 
-	assert.NoError(t, r.reconcileRedisDeployment(cr))
+	assert.NoError(t, r.reconcileRedisDeployment(cr, false))
 	d := &appsv1.Deployment{}
 	assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{Name: cr.Name + "-redis", Namespace: cr.Namespace}, d))
 	assert.Equal(t, int32(3), *d.Spec.Replicas)
@@ -1351,7 +1481,7 @@ func TestReconcileArgoCD_reconcileRedisDeployment_with_error(t *testing.T) {
 	defer resetHooks()()
 	Register(testErrorHook)
 
-	assert.Error(t, r.reconcileRedisDeployment(cr), "this is a test error")
+	assert.Error(t, r.reconcileRedisDeployment(cr, false), "this is a test error")
 }
 
 func restoreEnv(t *testing.T) {
@@ -1563,6 +1693,15 @@ func repoServerDefaultVolumes() []corev1.Volume {
 				},
 			},
 		},
+		{
+			Name: common.ArgoCDRedisServerTLSSecretName,
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: common.ArgoCDRedisServerTLSSecretName,
+					Optional:   boolPtr(true),
+				},
+			},
+		},
 		{
 			Name: "var-files",
 			VolumeSource: corev1.VolumeSource{
@@ -1588,6 +1727,7 @@ func repoServerDefaultVolumeMounts() []corev1.VolumeMount {
 		{Name: "gpg-keyring", MountPath: "/app/config/gpg/keys"},
 		{Name: "tmp", MountPath: "/tmp"},
 		{Name: "argocd-repo-server-tls", MountPath: "/app/config/reposerver/tls"},
+		{Name: common.ArgoCDRedisServerTLSSecretName, MountPath: "/app/config/reposerver/tls/redis"},
 		{Name: "plugins", MountPath: "/home/argocd/cmp-server/plugins"},
 	}
 	return mounts
@@ -1604,7 +1744,8 @@ func serverDefaultVolumes() []corev1.Volume {
 					},
 				},
 			},
-		}, {
+		},
+		{
 			Name: "tls-certs",
 			VolumeSource: corev1.VolumeSource{
 				ConfigMap: &corev1.ConfigMapVolumeSource{
@@ -1613,7 +1754,8 @@ func serverDefaultVolumes() []corev1.Volume {
 					},
 				},
 			},
-		}, {
+		},
+		{
 			Name: "argocd-repo-server-tls",
 			VolumeSource: corev1.VolumeSource{
 				Secret: &corev1.SecretVolumeSource{
@@ -1622,6 +1764,15 @@ func serverDefaultVolumes() []corev1.Volume {
 				},
 			},
 		},
+		{
+			Name: common.ArgoCDRedisServerTLSSecretName,
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: common.ArgoCDRedisServerTLSSecretName,
+					Optional:   boolPtr(true),
+				},
+			},
+		},
 	}
 	return volumes
 }
@@ -1637,6 +1788,9 @@ func serverDefaultVolumeMounts() []corev1.VolumeMount {
 		}, {
 			Name:      "argocd-repo-server-tls",
 			MountPath: "/app/config/server/tls",
+		}, {
+			Name:      common.ArgoCDRedisServerTLSSecretName,
+			MountPath: "/app/config/server/tls/redis",
 		},
 	}
 	return mounts
diff --git a/controllers/argocd/secret_test.go b/controllers/argocd/secret_test.go
index 7ccb2a9c..991c459b 100644
--- a/controllers/argocd/secret_test.go
+++ b/controllers/argocd/secret_test.go
@@ -132,7 +132,7 @@ func Test_ReconcileArgoCD_ReconcileRepoTLSSecret(t *testing.T) {
 			t.Errorf("Expected rollout of argocd-server, but it didn't happen: %v", serverDepl.Spec.Template.ObjectMeta.Labels)
 		}
 		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-repo-server", Namespace: "argocd-operator"}, repoDepl)
-		repoRollout, ok := serverDepl.Spec.Template.ObjectMeta.Labels["repo.tls.cert.changed"]
+		repoRollout, ok := repoDepl.Spec.Template.ObjectMeta.Labels["repo.tls.cert.changed"]
 		if !ok {
 			t.Errorf("Expected rollout of argocd-repo-server, but it didn't happen: %v", repoDepl.Spec.Template.ObjectMeta.Labels)
 		}
@@ -158,14 +158,14 @@ func Test_ReconcileArgoCD_ReconcileRepoTLSSecret(t *testing.T) {
 			t.Errorf("Did not expect rollout of argocd-server, but it did happen: %v", serverDepl.Spec.Template.ObjectMeta.Labels)
 		}
 		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-repo-server", Namespace: "argocd-operator"}, repoDepl)
-		repoRolloutNew, ok := serverDepl.Spec.Template.ObjectMeta.Labels["repo.tls.cert.changed"]
+		repoRolloutNew, ok := repoDepl.Spec.Template.ObjectMeta.Labels["repo.tls.cert.changed"]
 		if !ok || repoRollout != repoRolloutNew {
-			t.Errorf("Did not expect rollout of argocd-repo-server, but it did happen: %v", serverDepl.Spec.Template.ObjectMeta.Labels)
+			t.Errorf("Did not expect rollout of argocd-repo-server, but it did happen: %v", repoDepl.Spec.Template.ObjectMeta.Labels)
 		}
 		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-application-controller", Namespace: "argocd-operator"}, ctrlSts)
 		ctrlRolloutNew, ok := ctrlSts.Spec.Template.ObjectMeta.Labels["repo.tls.cert.changed"]
 		if !ok || ctrlRollout != ctrlRolloutNew {
-			t.Errorf("Did not expect rollout of argocd-application-server, but it did happen: %v", serverDepl.Spec.Template.ObjectMeta.Labels)
+			t.Errorf("Did not expect rollout of argocd-application-server, but it did happen: %v", ctrlSts.Spec.Template.ObjectMeta.Labels)
 		}
 
 		// Update certificate in the secret must trigger new rollout
@@ -187,14 +187,14 @@ func Test_ReconcileArgoCD_ReconcileRepoTLSSecret(t *testing.T) {
 			t.Errorf("Error in SHA256 sum of secret, want=%s got=%s", shasum, argocd.Status.RepoTLSChecksum)
 		}
 
-		// This time, label should not have changed
+		// This time, label should have changed
 		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-server", Namespace: "argocd-operator"}, serverDepl)
 		deplRolloutNew, ok = serverDepl.Spec.Template.ObjectMeta.Labels["repo.tls.cert.changed"]
 		if !ok || deplRollout == deplRolloutNew {
 			t.Errorf("Expected rollout of argocd-server, but it didn't happen: %v", serverDepl.Spec.Template.ObjectMeta.Labels)
 		}
 		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-repo-server", Namespace: "argocd-operator"}, repoDepl)
-		repoRolloutNew, ok = serverDepl.Spec.Template.ObjectMeta.Labels["repo.tls.cert.changed"]
+		repoRolloutNew, ok = repoDepl.Spec.Template.ObjectMeta.Labels["repo.tls.cert.changed"]
 		if !ok || repoRollout == repoRolloutNew {
 			t.Errorf("Expected rollout of argocd-repo-server, but it didn't happen: %v", repoDepl.Spec.Template.ObjectMeta.Labels)
 		}
@@ -208,6 +208,177 @@ func Test_ReconcileArgoCD_ReconcileRepoTLSSecret(t *testing.T) {
 
 }
 
+func Test_ReconcileArgoCD_ReconcileRedisTLSSecret(t *testing.T) {
+	argocd := &v1alpha1.ArgoCD{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "argocd",
+			Namespace: "argocd-operator",
+			UID:       "abcd",
+		},
+	}
+	crt := []byte("foo")
+	key := []byte("bar")
+	t.Run("Reconcile TLS secret", func(t *testing.T) {
+		service := &v1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "argocd-redis",
+				Namespace: "argocd-operator",
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						APIVersion: "argoproj.io/v1alpha1",
+						Kind:       "ArgoCD",
+						Name:       "argocd",
+						UID:        argocd.GetUID(),
+					},
+				},
+				UID: "service-123",
+			},
+		}
+		secret := &corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "argocd-operator-redis-tls",
+				Namespace: "argocd-operator",
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						APIVersion: "v1",
+						Kind:       "Service",
+						Name:       "argocd-redis",
+						UID:        service.GetUID(),
+					},
+				},
+			},
+			Type: corev1.SecretTypeTLS,
+			Data: map[string][]byte{
+				corev1.TLSCertKey:       crt,
+				corev1.TLSPrivateKeyKey: key,
+			},
+		}
+		var sumOver []byte
+		sumOver = append(sumOver, crt...)
+		sumOver = append(sumOver, key...)
+		shasum := fmt.Sprintf("%x", sha256.Sum256(sumOver))
+		serverDepl := newDeploymentWithSuffix("server", "server", argocd)
+		repoDepl := newDeploymentWithSuffix("repo-server", "repo-server", argocd)
+		redisDepl := newDeploymentWithSuffix("redis", "redis", argocd)
+		ctrlSts := newStatefulSetWithSuffix("application-controller", "application-controller", argocd)
+		objs := []runtime.Object{
+			argocd,
+			secret,
+			service,
+			serverDepl,
+			repoDepl,
+			redisDepl,
+			ctrlSts,
+		}
+
+		r := makeReconciler(t, argocd, objs...)
+
+		err := r.reconcileRedisTLSSecret(argocd, true)
+		if err != nil {
+			t.Errorf("Error should be nil, but is %v", err)
+		}
+		if shasum != argocd.Status.RedisTLSChecksum {
+			t.Errorf("Error in SHA256 sum of secret, want=%s got=%s", shasum, argocd.Status.RedisTLSChecksum)
+		}
+
+		certChangedLabel := "redis.tls.cert.changed"
+
+		// Workloads should have been requested to re-rollout on a change
+		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-server", Namespace: "argocd-operator"}, serverDepl)
+		deplRollout, ok := serverDepl.Spec.Template.ObjectMeta.Labels[certChangedLabel]
+		if !ok {
+			t.Errorf("Expected rollout of argocd-server, but it didn't happen: %v", serverDepl.Spec.Template.ObjectMeta.Labels)
+		}
+		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-repo-server", Namespace: "argocd-operator"}, repoDepl)
+		repoRollout, ok := repoDepl.Spec.Template.ObjectMeta.Labels[certChangedLabel]
+		if !ok {
+			t.Errorf("Expected rollout of argocd-repo-server, but it didn't happen: %v", repoDepl.Spec.Template.ObjectMeta.Labels)
+		}
+		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-redis", Namespace: "argocd-operator"}, redisDepl)
+		redisRollout, ok := redisDepl.Spec.Template.ObjectMeta.Labels[certChangedLabel]
+		if !ok {
+			t.Errorf("Expected rollout of argocd-redis, but it didn't happen: %v", redisDepl.Spec.Template.ObjectMeta.Labels)
+		}
+		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-application-controller", Namespace: "argocd-operator"}, ctrlSts)
+		ctrlRollout, ok := ctrlSts.Spec.Template.ObjectMeta.Labels[certChangedLabel]
+		if !ok {
+			t.Errorf("Expected rollout of argocd-application-server, but it didn't happen: %v", ctrlSts.Spec.Template.ObjectMeta.Labels)
+		}
+
+		// Second run - no change
+		err = r.reconcileRedisTLSSecret(argocd, true)
+		if err != nil {
+			t.Errorf("Error should be nil, but is %v", err)
+		}
+		if shasum != argocd.Status.RedisTLSChecksum {
+			t.Errorf("Error in SHA256 sum of secret, want=%s got=%s", shasum, argocd.Status.RepoTLSChecksum)
+		}
+
+		// This time, label should not have changed
+		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-server", Namespace: "argocd-operator"}, serverDepl)
+		deplRolloutNew, ok := serverDepl.Spec.Template.ObjectMeta.Labels[certChangedLabel]
+		if !ok || deplRollout != deplRolloutNew {
+			t.Errorf("Did not expect rollout of argocd-server, but it did happen: %v", serverDepl.Spec.Template.ObjectMeta.Labels)
+		}
+		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-repo-server", Namespace: "argocd-operator"}, repoDepl)
+		repoRolloutNew, ok := repoDepl.Spec.Template.ObjectMeta.Labels[certChangedLabel]
+		if !ok || repoRollout != repoRolloutNew {
+			t.Errorf("Did not expect rollout of argocd-repo-server, but it did happen: %v", repoDepl.Spec.Template.ObjectMeta.Labels)
+		}
+		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-redis", Namespace: "argocd-operator"}, redisDepl)
+		redisRolloutNew, ok := redisDepl.Spec.Template.ObjectMeta.Labels[certChangedLabel]
+		if !ok || redisRollout != redisRolloutNew {
+			t.Errorf("Did not expect rollout of argocd-redis, but it did happen: %v", redisDepl.Spec.Template.ObjectMeta.Labels)
+		}
+		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-application-controller", Namespace: "argocd-operator"}, ctrlSts)
+		ctrlRolloutNew, ok := ctrlSts.Spec.Template.ObjectMeta.Labels[certChangedLabel]
+		if !ok || ctrlRollout != ctrlRolloutNew {
+			t.Errorf("Did not expect rollout of argocd-application-server, but it did happen: %v", ctrlSts.Spec.Template.ObjectMeta.Labels)
+		}
+
+		// Update certificate in the secret must trigger new rollout
+		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-repo-server-tls", Namespace: "argocd-operator"}, secret)
+		secret.Data["tls.crt"] = []byte("bar")
+		r.Client.Update(context.TODO(), secret)
+
+		sumOver = []byte{}
+		sumOver = append(sumOver, []byte("bar")...)
+		sumOver = append(sumOver, key...)
+		shasum = fmt.Sprintf("%x", sha256.Sum256(sumOver))
+
+		// Second run - no change
+		err = r.reconcileRedisTLSSecret(argocd, true)
+		if err != nil {
+			t.Errorf("Error should be nil, but is %v", err)
+		}
+		if shasum != argocd.Status.RedisTLSChecksum {
+			t.Errorf("Error in SHA256 sum of secret, want=%s got=%s", shasum, argocd.Status.RedisTLSChecksum)
+		}
+
+		// This time, label should have changed
+		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-server", Namespace: "argocd-operator"}, serverDepl)
+		deplRolloutNew, ok = serverDepl.Spec.Template.ObjectMeta.Labels[certChangedLabel]
+		if !ok || deplRollout == deplRolloutNew {
+			t.Errorf("Expected rollout of argocd-server, but it didn't happen: %v", serverDepl.Spec.Template.ObjectMeta.Labels)
+		}
+		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-repo-server", Namespace: "argocd-operator"}, repoDepl)
+		repoRolloutNew, ok = repoDepl.Spec.Template.ObjectMeta.Labels[certChangedLabel]
+		if !ok || repoRollout == repoRolloutNew {
+			t.Errorf("Expected rollout of argocd-repo-server, but it didn't happen: %v", repoDepl.Spec.Template.ObjectMeta.Labels)
+		}
+		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-redis", Namespace: "argocd-operator"}, redisDepl)
+		redisRolloutNew, ok = repoDepl.Spec.Template.ObjectMeta.Labels[certChangedLabel]
+		if !ok || redisRollout == redisRolloutNew {
+			t.Errorf("Expected rollout of argocd-redis, but it didn't happen: %v", redisDepl.Spec.Template.ObjectMeta.Labels)
+		}
+		r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-application-controller", Namespace: "argocd-operator"}, ctrlSts)
+		ctrlRolloutNew, ok = ctrlSts.Spec.Template.ObjectMeta.Labels[certChangedLabel]
+		if !ok || ctrlRollout == ctrlRolloutNew {
+			t.Errorf("Expected rollout of argocd-application-controller, but it didn't happen: %v", ctrlSts.Spec.Template.ObjectMeta.Labels)
+		}
+	})
+}
+
 func Test_ReconcileArgoCD_ClusterPermissionsSecret(t *testing.T) {
 	logf.SetLogger(ZapLogger(true))
 	a := makeTestArgoCD()
diff --git a/controllers/argocd/statefulset_test.go b/controllers/argocd/statefulset_test.go
index 846d917e..2394b32d 100644
--- a/controllers/argocd/statefulset_test.go
+++ b/controllers/argocd/statefulset_test.go
@@ -39,6 +39,15 @@ func controllerDefaultVolumes() []corev1.Volume {
 				},
 			},
 		},
+		{
+			Name: common.ArgoCDRedisServerTLSSecretName,
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: common.ArgoCDRedisServerTLSSecretName,
+					Optional:   boolPtr(true),
+				},
+			},
+		},
 	}
 	return volumes
 }
@@ -49,6 +58,10 @@ func controllerDefaultVolumeMounts() []corev1.VolumeMount {
 			Name:      "argocd-repo-server-tls",
 			MountPath: "/app/config/controller/tls",
 		},
+		{
+			Name:      common.ArgoCDRedisServerTLSSecretName,
+			MountPath: "/app/config/controller/tls/redis",
+		},
 	}
 	return mounts
 }
@@ -95,7 +108,7 @@ func TestReconcileArgoCD_reconcileApplicationController(t *testing.T) {
 	a := makeTestArgoCD()
 	r := makeTestReconciler(t, a)
 
-	assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a))
+	assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, false))
 
 	ss := &appsv1.StatefulSet{}
 	assert.NoError(t, r.Client.Get(
@@ -128,15 +141,47 @@ func TestReconcileArgoCD_reconcileApplicationController(t *testing.T) {
 	}
 }
 
+func TestReconcileArgoCD_reconcileApplicationController_withRedisTLS(t *testing.T) {
+	logf.SetLogger(ZapLogger(true))
+	a := makeTestArgoCD()
+	r := makeTestReconciler(t, a)
+
+	assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, true))
+
+	ss := &appsv1.StatefulSet{}
+	assert.NoError(t, r.Client.Get(
+		context.TODO(),
+		types.NamespacedName{
+			Name:      "argocd-application-controller",
+			Namespace: a.Namespace,
+		},
+		ss))
+	command := ss.Spec.Template.Spec.Containers[0].Command
+	want := []string{
+		"argocd-application-controller",
+		"--operation-processors", "10",
+		"--redis", "argocd-redis.argocd.svc.cluster.local:6379",
+		"--redis-use-tls",
+		"--redis-ca-certificate", "/app/config/controller/tls/redis/tls.crt",
+		"--repo-server", "argocd-repo-server.argocd.svc.cluster.local:8081",
+		"--status-processors", "20",
+		"--kubectl-parallelism-limit", "10",
+		"--loglevel", "info",
+		"--logformat", "text"}
+	if diff := cmp.Diff(want, command); diff != "" {
+		t.Fatalf("reconciliation failed:\n%s", diff)
+	}
+}
+
 func TestReconcileArgoCD_reconcileApplicationController_withUpdate(t *testing.T) {
 	logf.SetLogger(ZapLogger(true))
 	a := makeTestArgoCD()
 	r := makeTestReconciler(t, a)
 
-	assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a))
+	assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, false))
 
 	a = makeTestArgoCD(controllerProcessors(30))
-	assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a))
+	assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, false))
 
 	ss := &appsv1.StatefulSet{}
 	assert.NoError(t, r.Client.Get(
@@ -169,7 +214,7 @@ func TestReconcileArgoCD_reconcileApplicationController_withUpgrade(t *testing.T
 	deploy := newDeploymentWithSuffix("application-controller", "application-controller", a)
 	assert.NoError(t, r.Client.Create(context.TODO(), deploy))
 
-	assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a))
+	assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, false))
 	err := r.Client.Get(context.TODO(), types.NamespacedName{Name: deploy.Name, Namespace: deploy.Namespace}, deploy)
 	assert.Errorf(t, err, "not found")
 }
@@ -192,7 +237,7 @@ func TestReconcileArgoCD_reconcileApplicationController_withResources(t *testing
 	}
 	r := makeTestReconciler(t, a, &ex)
 
-	assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a))
+	assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, false))
 
 	ss := &appsv1.StatefulSet{}
 	assert.NoError(t, r.Client.Get(
@@ -280,7 +325,7 @@ func TestReconcileArgoCD_reconcileApplicationController_withSharding(t *testing.
 		})
 		r := makeTestReconciler(t, a)
 
-		assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a))
+		assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, false))
 
 		ss := &appsv1.StatefulSet{}
 		assert.NoError(t, r.Client.Get(
diff --git a/controllers/argocd/util_test.go b/controllers/argocd/util_test.go
index adf95370..0acfe5b7 100644
--- a/controllers/argocd/util_test.go
+++ b/controllers/argocd/util_test.go
@@ -427,7 +427,7 @@ func TestGetArgoApplicationControllerCommand(t *testing.T) {
 
 	for _, tt := range cmdTests {
 		cr := makeTestArgoCD(tt.opts...)
-		cmd := getArgoApplicationControllerCommand(cr)
+		cmd := getArgoApplicationControllerCommand(cr, false)
 
 		if !reflect.DeepEqual(cmd, tt.want) {
 			t.Fatalf("got %#v, want %#v", cmd, tt.want)
@@ -627,6 +627,34 @@ func TestSetManagedNamespaces(t *testing.T) {
 	}
 }
 
+func TestGetRedisConf(t *testing.T) {
+	s1 := getRedisConf(true)
+	//assert.Equal(t, "", s1)
+
+	s1 = getRedisConf(false)
+	//assert.Equal(t, "", s1)
+
+	s1 = getRedisSentinelConf(true)
+	//assert.Equal(t, "", s1)
+
+	s1 = getRedisSentinelConf(false)
+	//assert.Equal(t, "", s1)
+
+	a := makeTestArgoCD()
+
+	s1 = getRedisInitScript(a, true)
+	//assert.Equal(t, "", s1)
+
+	s1 = getRedisLivenessScript(false)
+	//assert.Equal(t, "", s1)
+
+	s1 = getRedisReadinessScript(true)
+	//assert.Equal(t, "", s1)
+
+	s1 = getSentinelLivenessScript(true)
+	assert.Equal(t, "", s1)
+}
+
 func TestGenerateRandomString(t *testing.T) {
 
 	// verify the creation of unique strings
diff --git a/tests/k8s/1-007_validate_volume_mounts/01-assert.yaml b/tests/k8s/1-007_validate_volume_mounts/01-assert.yaml
index 5dec0f03..0f1b3047 100644
--- a/tests/k8s/1-007_validate_volume_mounts/01-assert.yaml
+++ b/tests/k8s/1-007_validate_volume_mounts/01-assert.yaml
@@ -24,6 +24,8 @@ spec:
           name: tls-certs
         - mountPath: /app/config/server/tls
           name: argocd-repo-server-tls
+        - mountPath: /app/config/server/tls/redis
+          name: argocd-operator-redis-tls
       volumes:
       - configMap:
           defaultMode: 420
@@ -38,6 +40,11 @@ spec:
           defaultMode: 420
           optional: true
           secretName: argocd-repo-server-tls
+      - name: argocd-operator-redis-tls
+        secret:
+          defaultMode: 420
+          optional: true
+          secretName: argocd-operator-redis-tls
 ---
 apiVersion: apps/v1
 kind: Deployment
@@ -60,6 +67,8 @@ spec:
           name: tmp
         - mountPath: /app/config/reposerver/tls
           name: argocd-repo-server-tls
+        - mountPath: /app/config/reposerver/tls/redis
+          name: argocd-operator-redis-tls
         - mountPath: /home/argocd/cmp-server/plugins
           name: plugins
       volumes:
@@ -84,6 +93,11 @@ spec:
           defaultMode: 420
           optional: true
           secretName: argocd-repo-server-tls
+      - name: argocd-operator-redis-tls
+        secret:
+          defaultMode: 420
+          optional: true
+          secretName: argocd-operator-redis-tls
       - emptyDir: {}
         name: var-files
       - emptyDir: {}
@@ -100,10 +114,16 @@ spec:
       - volumeMounts: 
         - mountPath: /app/config/controller/tls
           name: argocd-repo-server-tls
+        - mountPath: /app/config/controller/tls/redis
+          name: argocd-operator-redis-tls
       volumes:
       - name: argocd-repo-server-tls
         secret:
           defaultMode: 420
           optional: true
           secretName: argocd-repo-server-tls
-    
\ No newline at end of file
+      - name: argocd-operator-redis-tls
+        secret:
+          defaultMode: 420
+          optional: true
+          secretName: argocd-operator-redis-tls
diff --git a/tests/k8s/1-018_validate_extra_config/01-argocd-with-extraconfig.yaml b/tests/k8s/1-018_validate_extra_config/01-argocd-with-extraconfig.yaml
new file mode 100644
index 00000000..8c10115f
--- /dev/null
+++ b/tests/k8s/1-018_validate_extra_config/01-argocd-with-extraconfig.yaml
@@ -0,0 +1,7 @@
+apiVersion: argoproj.io/v1alpha1
+kind: ArgoCD
+metadata:
+  name: example-argocd
+spec:
+  extraConfig:
+    "admin.enabled": "true" # enable admin user through extraConfig
\ No newline at end of file
diff --git a/tests/k8s/1-018_validate_extra_config/01-assert.yaml b/tests/k8s/1-018_validate_extra_config/01-assert.yaml
new file mode 100644
index 00000000..78e6be9b
--- /dev/null
+++ b/tests/k8s/1-018_validate_extra_config/01-assert.yaml
@@ -0,0 +1,17 @@
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+timeout: 120
+---
+apiVersion: argoproj.io/v1alpha1
+kind: ArgoCD
+metadata:
+  name: example-argocd
+status:
+  phase: Available
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: argocd-cm
+data:
+  admin.enabled: "true"
\ No newline at end of file
diff --git a/tests/k8s/1-018_validate_extra_config/02-argocd-with-firstclass-and-extraconfig copy.yaml b/tests/k8s/1-018_validate_extra_config/02-argocd-with-firstclass-and-extraconfig copy.yaml
new file mode 100644
index 00000000..bbac50ba
--- /dev/null
+++ b/tests/k8s/1-018_validate_extra_config/02-argocd-with-firstclass-and-extraconfig copy.yaml
@@ -0,0 +1,8 @@
+apiVersion: argoproj.io/v1alpha1
+kind: ArgoCD
+metadata:
+  name: example-argocd
+spec:
+  disableAdmin: true
+  extraConfig:
+    "admin.enabled": "true" # override admin user through extraConfig
\ No newline at end of file
diff --git a/tests/k8s/1-018_validate_extra_config/02-assert.yaml b/tests/k8s/1-018_validate_extra_config/02-assert.yaml
new file mode 100644
index 00000000..e5470d42
--- /dev/null
+++ b/tests/k8s/1-018_validate_extra_config/02-assert.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: argocd-cm
+data:
+  admin.enabled: "true"
\ No newline at end of file
diff --git a/tests/k8s/1-018_validate_extra_config/03-assert.yaml b/tests/k8s/1-018_validate_extra_config/03-assert.yaml
new file mode 100644
index 00000000..3f172ffd
--- /dev/null
+++ b/tests/k8s/1-018_validate_extra_config/03-assert.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: argocd-cm
+data:
+  admin.enabled: "true" # operator should reject any manual updates to the configmap.
\ No newline at end of file
diff --git a/tests/k8s/1-018_validate_extra_config/03-update-configmap.yaml b/tests/k8s/1-018_validate_extra_config/03-update-configmap.yaml
new file mode 100644
index 00000000..aff583e2
--- /dev/null
+++ b/tests/k8s/1-018_validate_extra_config/03-update-configmap.yaml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: argocd-cm
+data:
+  admin.enabled: "false"
\ No newline at end of file
diff --git a/tests/k8s/1-018_validate_extra_config/04-assert.yaml b/tests/k8s/1-018_validate_extra_config/04-assert.yaml
new file mode 100644
index 00000000..d005cd46
--- /dev/null
+++ b/tests/k8s/1-018_validate_extra_config/04-assert.yaml
@@ -0,0 +1,17 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: argocd-cm
+data:
+  admin.enabled: "true"
+  dex.config: |
+    connectors:
+      - type: github
+        id: github
+        name: github-using-first-class
+        config:
+          clientID: first-class
+          clientSecret: $dex.github.clientSecret
+          orgs:
+          - name: first-class
\ No newline at end of file
diff --git a/tests/k8s/1-018_validate_extra_config/04-create-dex-without-extraconfig.yaml b/tests/k8s/1-018_validate_extra_config/04-create-dex-without-extraconfig.yaml
new file mode 100644
index 00000000..0ab7807a
--- /dev/null
+++ b/tests/k8s/1-018_validate_extra_config/04-create-dex-without-extraconfig.yaml
@@ -0,0 +1,18 @@
+apiVersion: argoproj.io/v1alpha1
+kind: ArgoCD
+metadata:
+  name: example-argocd
+spec:
+  disableAdmin: true
+  dex:
+   config: |
+    connectors:
+      - type: github
+        id: github
+        name: github-using-first-class
+        config:
+          clientID: first-class
+          clientSecret: $dex.github.clientSecret
+          orgs:
+          - name: first-class
+
diff --git a/tests/k8s/1-018_validate_extra_config/05-assert.yaml b/tests/k8s/1-018_validate_extra_config/05-assert.yaml
new file mode 100644
index 00000000..747283ca
--- /dev/null
+++ b/tests/k8s/1-018_validate_extra_config/05-assert.yaml
@@ -0,0 +1,17 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: argocd-cm
+data:
+  admin.enabled: "true"
+  dex.config: |
+    connectors:
+      - type: github
+        id: github
+        name: github-using-extra-config
+        config:
+          clientID: extra-config
+          clientSecret: $dex.github.clientSecret
+          orgs:
+          - name: extra-config
\ No newline at end of file
diff --git a/tests/k8s/1-018_validate_extra_config/05-override-dex-using-extraconfig.yaml b/tests/k8s/1-018_validate_extra_config/05-override-dex-using-extraconfig.yaml
new file mode 100644
index 00000000..49cb7201
--- /dev/null
+++ b/tests/k8s/1-018_validate_extra_config/05-override-dex-using-extraconfig.yaml
@@ -0,0 +1,29 @@
+apiVersion: argoproj.io/v1alpha1
+kind: ArgoCD
+metadata:
+  name: example-argocd
+spec:
+  disableAdmin: true
+  dex:
+   config: |
+    connectors:
+      - type: github
+        id: github
+        name: github-using-first-class
+        config:
+          clientID: first-class
+          clientSecret: $dex.github.clientSecret
+          orgs:
+          - name: first-class
+  extraConfig:
+    "admin.enabled": "true"
+    "dex.config": |
+      connectors:
+        - type: github
+          id: github
+          name: github-using-extra-config
+          config:
+            clientID: extra-config
+            clientSecret: $dex.github.clientSecret
+            orgs:
+            - name: extra-config