Repository: k8ssandra/k8ssandra-operator (go)

Issue:
Allow modification of server_encryption / client_encryption settings
<!--

Feel free to ask questions on Discord at https://discord.com/invite/y4s64xC9 or in the forum at https://forum.k8ssandra.io/. 
-->

**What is missing?**
If server_encryption_settings / client_encryption_settings are in the cassandraYaml, there's no way to prevent k8ssandra-operator from meddling with them. Lets allow setting it manually if no other Encryption fields are used.

**Why do we need it?**

**Environment**

* K8ssandra Operator version:

    `Insert image tag or Git SHA here`
    <!-- Try kubectl describe deployment k8ssandra-operator -->
    <!-- Note: please provide operator version and not a helm chart version -->

**Anything else we need to know?**:


Pull request:
If ServerEncryptionStores is not set, do not verify from YAML either
<!--  Thanks for sending a pull request!  Before submitting:

1. Read our CONTRIBUTING.md guide
2. Rebase your PR if it gets out of sync with main
-->

**What this PR does**:

**Which issue(s) this PR fixes**:
Fixes #991 

**Checklist**
- [ ] Changes manually tested
- [x] Automated Tests added/updated
- [ ] Documentation added/updated
- [ ] CHANGELOG.md updated (not required for documentation PRs)
- [x] CLA Signed:  [DataStax CLA](https://cla.datastax.com/)


Patch:
diff --git a/CHANGELOG/CHANGELOG-1.7.md b/CHANGELOG/CHANGELOG-1.7.md
index efd914ee..9f442e69 100644
--- a/CHANGELOG/CHANGELOG-1.7.md
+++ b/CHANGELOG/CHANGELOG-1.7.md
@@ -15,6 +15,7 @@ When cutting a new release, update the `unreleased` heading to the tag being gen
 
 ## unreleased
 
+* [CHANGE] [#991](https://github.com/k8ssandra/k8ssandra-operator/issues/991) If EncryptionStores and all the Keystore/Truststore passwords are not set, the operator will not touch the cassandra-yaml's encryption fields.
 * [ENHANCEMENT] [#932](https://github.com/k8ssandra/k8ssandra-operator/issues/932) Add ability to set variables to the secret-injection annotation. Supported are `POD_NAME`, `POD_NAMESPACE` and `POD_ORDINAL`. Also, changed JSON key from `secretName` to `name`
 * [BUGFIX] [#914](https://github.com/k8ssandra/k8ssandra-operator/issues/914) Don't parse logs by default when Vector telemetry is enabled.
 * [BUGFIX] [#916](https://github.com/k8ssandra/k8ssandra-operator/issues/916) Deprecate jmxInitContainerImage field.
diff --git a/pkg/cassandra/encryption.go b/pkg/cassandra/encryption.go
index fa7b66f5..7c718a13 100644
--- a/pkg/cassandra/encryption.go
+++ b/pkg/cassandra/encryption.go
@@ -149,7 +149,7 @@ func checkMandatoryEncryptionFields(encryptionStores *encryption.Stores) error {
 		return fmt.Errorf("EncryptionStores is required to set up encryption")
 	}
 	if encryptionStores.KeystoreSecretRef == nil {
-		return fmt.Errorf("keystore secret ref was not set")
+		return fmt.Errorf("keystore secret ref is required when EncryptionStores is used")
 	}
 	return nil
 }
@@ -159,11 +159,20 @@ func StoreMountFullPath(storeType encryption.StoreType, storeName encryption.Sto
 }
 
 func ClientEncryptionEnabled(template *DatacenterConfig) bool {
+	if template.ClientEncryptionStores == nil && (template.ClientKeystorePassword == "" && template.ClientTruststorePassword == "") {
+		return false
+	}
+
 	enabled, _ := template.CassandraConfig.CassandraYaml.Get("client_encryption_options/enabled")
 	return enabled == true
 }
 
 func ServerEncryptionEnabled(template *DatacenterConfig) bool {
+	// This allows to bypass the automated behavior, but of course removes the idea of validating this field also
+	if template.ServerEncryptionStores == nil && (template.ServerKeystorePassword == "" && template.ServerTruststorePassword == "") {
+		return false
+	}
+
 	internodeEncryption, _ := template.CassandraConfig.CassandraYaml.Get("server_encryption_options/internode_encryption")
 	return internodeEncryption != nil && internodeEncryption != "none"
 }


Test patch:
diff --git a/controllers/k8ssandra/auth_test.go b/controllers/k8ssandra/auth_test.go
index 55b02e31..578827b0 100644
--- a/controllers/k8ssandra/auth_test.go
+++ b/controllers/k8ssandra/auth_test.go
@@ -10,6 +10,8 @@ import (
 	reaperapi "github.com/k8ssandra/k8ssandra-operator/apis/reaper/v1alpha1"
 	stargateapi "github.com/k8ssandra/k8ssandra-operator/apis/stargate/v1alpha1"
 	"github.com/k8ssandra/k8ssandra-operator/pkg/reaper"
+	"github.com/k8ssandra/k8ssandra-operator/pkg/unstructured"
+	"github.com/k8ssandra/k8ssandra-operator/pkg/utils"
 	"github.com/k8ssandra/k8ssandra-operator/test/framework"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -371,3 +373,78 @@ func createSingleDcClusterAuthExternalSecrets(t *testing.T, ctx context.Context,
 	f.AssertObjectDoesNotExist(ctx, t, stargateKey, &stargateapi.Stargate{}, timeout, interval)
 	f.AssertObjectDoesNotExist(ctx, t, reaperKey, &reaperapi.Reaper{}, timeout, interval)
 }
+
+func createSingleDcClusterExternalInternode(t *testing.T, ctx context.Context, f *framework.Framework, namespace string) {
+	require := require.New(t)
+
+	kc := &api.K8ssandraCluster{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+			Name:      "cluster1",
+		},
+		Spec: api.K8ssandraClusterSpec{
+			Auth: pointer.BoolPtr(true),
+			Cassandra: &api.CassandraClusterTemplate{
+				Datacenters: []api.CassandraDatacenterTemplate{{
+					Meta:       api.EmbeddedObjectMeta{Name: "dc1"},
+					K8sContext: f.DataPlaneContexts[1],
+					Size:       1,
+					DatacenterOptions: api.DatacenterOptions{
+						ServerVersion: "4.1.2",
+						StorageConfig: &cassdcapi.StorageConfig{
+							CassandraDataVolumeClaimSpec: &corev1.PersistentVolumeClaimSpec{
+								StorageClassName: &defaultStorageClass,
+							},
+						},
+						CassandraConfig: &api.CassandraConfig{
+							CassandraYaml: unstructured.Unstructured{
+								"server_encryption_options": map[string]interface{}{
+									"internode_encryption": "all",
+									"keystore":             "/etc/encryption/internode/keystore.jks",
+									"keystore_password":    "changeit",
+									"truststore":           "/etc/encryption/internode/truststore.jks",
+									"truststore_password":  "changeit",
+								},
+							},
+						},
+					},
+				}},
+			},
+		},
+	}
+
+	err := f.Client.Create(ctx, kc)
+	require.NoError(err, "failed to create K8ssandraCluster")
+
+	kcKey := framework.ClusterKey{K8sContext: f.ControlPlaneContext, NamespacedName: types.NamespacedName{Namespace: namespace, Name: kc.Name}}
+	dcKey := framework.ClusterKey{K8sContext: f.DataPlaneContexts[1], NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc1"}}
+
+	verifyFinalizerAdded(ctx, t, f, kcKey.NamespacedName)
+	verifySuperuserSecretCreated(ctx, t, f, kc)
+	verifyReplicatedSecretReconciled(ctx, t, f, kc)
+
+	t.Log("check that the datacenter was created")
+	require.Eventually(f.DatacenterExists(ctx, dcKey), timeout, interval)
+
+	t.Log("update dc status to ready")
+	err = f.SetDatacenterStatusReady(ctx, dcKey)
+	require.NoError(err, "failed to set dc status ready")
+
+	t.Log("check the cassandra-yaml properties were set in the CassandraDatacenter")
+	dc := &cassdcapi.CassandraDatacenter{}
+	err = f.Get(ctx, dcKey, dc)
+	require.NoError(err, "failed to get CassandraDatacenter")
+
+	dcConfig, err := utils.UnmarshalToMap(dc.Spec.Config)
+	require.NoError(err, "failed to unmarshall CassandraDatacenter config")
+	dcConfigYaml, _ := dcConfig["cassandra-yaml"].(map[string]interface{})
+
+	require.Equal("all", dcConfigYaml["server_encryption_options"].(map[string]interface{})["internode_encryption"].(string))
+	require.Equal("changeit", dcConfigYaml["server_encryption_options"].(map[string]interface{})["keystore_password"].(string))
+	require.Equal("/etc/encryption/internode/keystore.jks", dcConfigYaml["server_encryption_options"].(map[string]interface{})["keystore"].(string))
+
+	t.Log("deleting K8ssandraCluster")
+	err = f.DeleteK8ssandraCluster(ctx, client.ObjectKey{Namespace: kc.Namespace, Name: kc.Name}, timeout, interval)
+	require.NoError(err, "failed to delete K8ssandraCluster")
+	f.AssertObjectDoesNotExist(ctx, t, dcKey, &cassdcapi.CassandraDatacenter{}, timeout, interval)
+}
diff --git a/controllers/k8ssandra/k8ssandracluster_controller_test.go b/controllers/k8ssandra/k8ssandracluster_controller_test.go
index 71847635..56847886 100644
--- a/controllers/k8ssandra/k8ssandracluster_controller_test.go
+++ b/controllers/k8ssandra/k8ssandracluster_controller_test.go
@@ -102,6 +102,7 @@ func TestK8ssandraCluster(t *testing.T) {
 	t.Run("CreateSingleDcClusterNoAuth", testEnv.ControllerTest(ctx, createSingleDcClusterNoAuth))
 	t.Run("CreateSingleDcClusterAuth", testEnv.ControllerTest(ctx, createSingleDcClusterAuth))
 	t.Run("CreateSingleDcClusterAuthExternalSecrets", testEnv.ControllerTest(ctx, createSingleDcClusterAuthExternalSecrets))
+	t.Run("CreateSingleDcClusterExternalInternode", testEnv.ControllerTest(ctx, createSingleDcClusterExternalInternode))
 
 	// If webhooks are installed, this testcase is handled by the webhook test
 	// t.Run("ChangeNumTokensValue", testEnv.ControllerTest(ctx, changeNumTokensValue))
diff --git a/pkg/cassandra/encryption_test.go b/pkg/cassandra/encryption_test.go
index f769f8a2..16a6428d 100644
--- a/pkg/cassandra/encryption_test.go
+++ b/pkg/cassandra/encryption_test.go
@@ -3,9 +3,10 @@ package cassandra
 import (
 	"context"
 	"fmt"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"testing"
 
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
 	api "github.com/k8ssandra/k8ssandra-operator/apis/k8ssandra/v1alpha1"
 	"github.com/k8ssandra/k8ssandra-operator/pkg/encryption"
 	"github.com/k8ssandra/k8ssandra-operator/pkg/unstructured"
@@ -17,6 +18,8 @@ import (
 )
 
 func TestCheckMandatoryEncryptionFields(t *testing.T) {
+	assert := assert.New(t)
+
 	dcConfig := &DatacenterConfig{
 		CassandraConfig: api.CassandraConfig{
 			CassandraYaml: unstructured.Unstructured{
@@ -43,11 +46,47 @@ func TestCheckMandatoryEncryptionFields(t *testing.T) {
 		},
 	}
 
-	noErr := checkMandatoryEncryptionFields(dcConfig.ClientEncryptionStores)
-	assert.NoError(t, noErr)
+	assert.NoError(checkMandatoryEncryptionFields(dcConfig.ClientEncryptionStores))
+	assert.Error(checkMandatoryEncryptionFields(dcConfig.ServerEncryptionStores))
 
-	err := checkMandatoryEncryptionFields(dcConfig.ServerEncryptionStores)
-	assert.Error(t, err)
+	dcConfig = &DatacenterConfig{
+		CassandraConfig: api.CassandraConfig{
+			CassandraYaml: unstructured.Unstructured{
+				"client_encryption_options": map[string]interface{}{
+					"enabled": true,
+				},
+				"server_encryption_options": map[string]interface{}{
+					"internode_encryption": "all",
+				},
+			},
+		},
+		ServerKeystorePassword: "flintstones",
+		ClientKeystorePassword: "flightstones",
+	}
+
+	assert.True(ServerEncryptionEnabled(dcConfig))
+	assert.True(ClientEncryptionEnabled(dcConfig))
+
+	assert.Error(checkMandatoryEncryptionFields(dcConfig.ClientEncryptionStores))
+	assert.Error(checkMandatoryEncryptionFields(dcConfig.ServerEncryptionStores))
+}
+
+func TestOverrideOfMandatoryEncryptionCheck(t *testing.T) {
+	dcConfig := &DatacenterConfig{
+		CassandraConfig: api.CassandraConfig{
+			CassandraYaml: unstructured.Unstructured{
+				"client_encryption_options": map[string]interface{}{
+					"enabled": true,
+				},
+				"server_encryption_options": map[string]interface{}{
+					"internode_encryption": "all",
+				},
+			},
+		},
+	}
+
+	assert.False(t, ServerEncryptionEnabled(dcConfig))
+	assert.False(t, ClientEncryptionEnabled(dcConfig))
 }
 
 func TestAddEncryptionMountToCassandra(t *testing.T) {