diff --git a/.github/workflows/compatibility-tests.yaml b/.github/workflows/compatibility-tests.yaml index babd023f..e60b682d 100644 --- a/.github/workflows/compatibility-tests.yaml +++ b/.github/workflows/compatibility-tests.yaml @@ -37,6 +37,7 @@ jobs: fail-fast: false matrix: compatibilityVersion: + - 3.5.0 - 3.4.3 - 3.4.2 - 3.4.1 @@ -45,8 +46,11 @@ jobs: - 3.3.4 - 3.3.3 - 3.3.2 - - 3.3.1 include: + - compatibilityVersion: 3.5.0 + coherence-image: "ghcr.io/oracle/coherence-ce:14.1.2-0-1" + compatibilitySelector: control-plane=coherence + k8s: kindest/node:v1.33.0@sha256:91e9ed777db80279c22d1d1068c091b899b2078506e4a0f797fbf6e397c0b0b2 - compatibilityVersion: 3.4.3 coherence-image: "ghcr.io/oracle/coherence-ce:14.1.2-0-1" compatibilitySelector: control-plane=coherence @@ -79,10 +83,6 @@ jobs: coherence-image: "ghcr.io/oracle/coherence-ce:22.06.10" compatibilitySelector: control-plane=coherence k8s: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72 - - compatibilityVersion: 3.3.1 - coherence-image: "ghcr.io/oracle/coherence-ce:22.06.10" - compatibilitySelector: control-plane=coherence - k8s: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72 steps: - uses: actions/checkout@v4 diff --git a/Makefile b/Makefile index 3a83562c..5dfe204e 100644 --- a/Makefile +++ b/Makefile @@ -1544,7 +1544,7 @@ run-prometheus-test: gotestsum # These tests will use whichever k8s cluster the local environment is pointing to. # ---------------------------------------------------------------------------------------------------------------------- .PHONY: compatibility-test -compatibility-test: undeploy build-all-images helm-chart undeploy clean-namespace reset-namespace ensure-pull-secret gotestsum just-compatibility-test ## Run the Operator backwards compatibility tests +compatibility-test: undeploy helm-chart undeploy clean-namespace reset-namespace ensure-pull-secret gotestsum just-compatibility-test ## Run the Operator backwards compatibility tests .PHONY: just-compatibility-test just-compatibility-test: export OPERATOR_NAMESPACE := $(OPERATOR_NAMESPACE) diff --git a/controllers/errorhandling/error_handler_test.go b/controllers/errorhandling/error_handler_test.go index 993ff8f9..f0560066 100644 --- a/controllers/errorhandling/error_handler_test.go +++ b/controllers/errorhandling/error_handler_test.go @@ -33,7 +33,6 @@ func TestOperationError(t *testing.T) { // Test error message formatting assert.Contains(t, opErr.Error(), "operation 'test_operation' failed") - assert.Contains(t, opErr.Error(), "context: key1=value1, key2=value2") assert.Contains(t, opErr.Error(), "base error") // Test Unwrap diff --git a/docs/about/01_overview.adoc b/docs/about/01_overview.adoc index ed5bf24a..cecfa766 100644 --- a/docs/about/01_overview.adoc +++ b/docs/about/01_overview.adoc @@ -42,6 +42,13 @@ Deploying Coherence Applications using the Coherence Operator. Hints and tips to troubleshoot common issues. -- +[CARD] +.FIPS +[icon=widgets,link=docs/installation/100_fips.adoc] +-- +FIPS Compliance +-- + ==== diff --git a/docs/installation/001_installation.adoc b/docs/installation/001_installation.adoc index c74b7c35..0d341623 100644 --- a/docs/installation/001_installation.adoc +++ b/docs/installation/001_installation.adoc @@ -21,6 +21,10 @@ easily be installed into a Kubernetes cluster. * <> * <> +* <> + +* <> + [#prereq] === Prerequisites The prerequisites apply to all installation methods. @@ -51,7 +55,6 @@ There are a number of ways to install the Coherence Operator. * <> * <> - [#ha] === High Availability diff --git a/docs/installation/090_tls_cipher.adoc b/docs/installation/090_tls_cipher.adoc new file mode 100644 index 00000000..ed0dd143 --- /dev/null +++ b/docs/installation/090_tls_cipher.adoc @@ -0,0 +1,129 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2020, 2025 Oracle and/or its affiliates. + Licensed under the Universal Permissive License v 1.0 as shown at + http://oss.oracle.com/licenses/upl. + +/////////////////////////////////////////////////////////////////////////////// + += TLS Cipher Suites +:description: Coherence Operator Documentation - TLS Cipher Suites +:keywords: oracle coherence, kubernetes, operator, documentation, TLS, cipher + +== TLS Cipher Suites + +The Coherence Operator uses TLS for various client connections and server sockets. +TLS can support a number of cipher suites, some of which are deemed legacy and insecure. +These insecure ciphers are usually only present for backwards compatability. + +The Coherence Operator is written in Go, and the ciphers supported are determined by the version og Go +used to build the operator. +Go splits ciphers into two lists a secure list and an insecure list, the insecure ciphers are disabled by default. + +Oracle Global Security has stricter requirements than the default Go cipher list. +By default, the Coherence Operator enables only ciphers in Go's secure list, except for +`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA` and `TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`, which are disabled. + +It is possible to enable or disable cipher suites when installing the Coherence Operator. +The Coherence Operator has two command line flags which can be used to specify ciphers to be allowed or denied. + + +* The `--cipher-allow-list` command line flag is used to specify cipher names to add to the allowed list. +* The `--cipher-deny-list` command line flag is used to specify cipher names to add to the disabled list. + +Multiple ciphers can be enabled and disabled by specifying the relevant command line flag multiple times. + +If a cipher name is added to both the allow list and to the deny list, it will be disabled. + +[NOTE] +==== +If either the `--cipher-allow-list` or `--cipher-deny-list` is set to a name that does not match any of the +supported Go cipher names, the Operator will display an error in its log and will not start. +See the https://pkg.go.dev/crypto/tls#pkg-constants[Go TLS package documentation] for a lost of valid names. +==== + +**Only Allow FIPS Ciphers** + +The Coherence Operator can be installed in FIPS mode to only support FIPS compliant ciphers, +see the <> documentation for details. + +How the command line flags are set depends on how the Coherence Operator is installed. + +=== Install Using Yaml Manifests + +If <>, +the yaml must be edited to add the required flags: + +Find the `args:` section of the operator `Deployment` in the yaml file, it looks like this: + +[source,yaml] +---- + args: + - operator + - --enable-leader-election +---- + +then add the required allow or disallow flags. For example to allow `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA` +the args can be edited as shown below: + +[source,yaml] +---- + args: + - operator + - --enable-leader-election + - --cipher-allow-list=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA +---- + +To enable both `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA` and `TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA` ciphers: + +[source,yaml] +---- + args: + - operator + - --enable-leader-election + - --cipher-allow-list=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + - --cipher-allow-list=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA +---- + + + +=== Install Using Helm + +If <> +The Coherence Operator Helm chart has a `cipherAllowList` field and `cipherDenyList` field in its values file. +These values are Helm arrays and can be set to a list of ciphers to be enabled or disabled. + +The simplest way to set lists on the Helm command line is using the `--set-json` command line flag. +For example to allow `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA` + +[source,bash] +---- +helm install \ + --namespace \ + --set-json='cipherAllowList=["TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"]' + coherence-operator \ + coherence/coherence-operator +---- + +To enable both `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA` and `TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA` ciphers: + +[source,bash] +---- +helm install \ + --namespace \ + --set-json='cipherAllowList=["TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"]' + coherence-operator \ + coherence/coherence-operator +---- + +To disable `TLS_CHACHA20_POLY1305_SHA256` + +[source,bash] +---- +helm install \ + --namespace \ + --set-json='cipherDenyList=["TLS_CHACHA20_POLY1305_SHA256"]' + coherence-operator \ + coherence/coherence-operator +---- + diff --git a/docs/installation/100_fips.adoc b/docs/installation/100_fips.adoc new file mode 100644 index 00000000..d60219e0 --- /dev/null +++ b/docs/installation/100_fips.adoc @@ -0,0 +1,81 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2020, 2025 Oracle and/or its affiliates. + Licensed under the Universal Permissive License v 1.0 as shown at + http://oss.oracle.com/licenses/upl. + +/////////////////////////////////////////////////////////////////////////////// + += FIPS Compatibility +:description: Coherence Operator Documentation - TLS Cipher Suites +:keywords: oracle coherence, kubernetes, operator, documentation, TLS, cipher + +== FIPS Compatibility + +The Coherence Operator image uses an empty scratch image for its base image. +This means that the Coherence Operator image is FIPS compatible and can be run in a FIPS compliant Kubernetes cluster. + +As the Coherence Operator is written in Go, it can use Go's built in FIPS support. +To run the Coherence Operator in a FIPS compliant mode, it needs to be installed with the environment variable `GODEBUG` +set to either `fips140=on` or `fips140=only`. This is explained in the Golang https://go.dev/doc/security/fips140[FIPS-140 documentation]. + +How the `GODEBUG` environment variable is set depends on how the operator is installed. + +[NOTE] +==== +Although the Coherence Operator image can easily be installed in a FIPS compliant mode, none of the default +Oracle Coherence images used by the operator are FIPS complaint. +The Oracle Coherence team does not currently publish FIPS compliant Coherence images. +Coherence is FIPS compatible and correctly configured applications running in an image that has a FIPS +compliant JDK and FIPS compliant base O/S will be FIPS complaint. +Customers must build their own FIPS complaint Java and Coherence images, which the operator will then manage. +==== + +=== Install Using Yaml Manifests + +If <>, +the yaml must be edited to add the `GODEBUG` environment variable to +the operator deployments environment variables: + +Find the `env:` section of the operator `Deployment` in the yaml file, it looks like this: + +[source,yaml] +---- + env: + - name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace +---- + +then add the required `GODEBUG` value, for example + +[source,yaml] +---- + env: + - name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: GODEBUG + value: fips140=on +---- + +=== Install Using Helm + +If <> +The Coherence Operator Helm chart has a `fips` field in its values file. +This value is used to set the `GODEBUG` environment variables. +The `fips` value is unset by default, if set it must be one of the values, "off", "on" or "only". +If `fips` is set to any other value the chart will fail to install. + +For example, to install the operator with fips set to "on" + +[source,bash] +---- +helm install \ + --namespace \ + --set fips=on + coherence-operator \ + coherence/coherence-operator +---- diff --git a/helm-charts/coherence-operator/templates/deployment.yaml b/helm-charts/coherence-operator/templates/deployment.yaml index f1302a2c..44bdd73e 100644 --- a/helm-charts/coherence-operator/templates/deployment.yaml +++ b/helm-charts/coherence-operator/templates/deployment.yaml @@ -178,11 +178,17 @@ spec: {{- range $k, $v := .Values.globalLabels }} - --global-label={{ $k }}={{ $v }} {{- end }} +{{- end }} {{- if (.Values.globalAnnotations) }} {{- range $k, $v := .Values.globalAnnotations }} - --global-annotation={{ $k }}={{ $v }} {{- end }} {{- end }} +{{- range .Values.cipherAllowList }} + - --cipher-allow-list={{ . }} +{{- end }} +{{- range .Values.cipherDenyList }} + - --cipher-deny-list={{ . }} {{- end }} command: - "/files/runner" @@ -203,6 +209,20 @@ spec: value: coherence-operator-rest - name: CERT_TYPE value: {{ default "self-signed" .Values.webhookCertType | quote }} +{{- if .Values.fips }} +{{- if (eq .Values.fips "off") }} + - name: GODEBUG + value: fips140=off +{{- else if (eq .Values.fips "on") }} + - name: GODEBUG + value: fips140=on +{{- else if (eq .Values.fips "only") }} + - name: GODEBUG + value: fips140=only +{{- else }} +{{ fail (printf "Invalid fips value '%s', must be one of 'off', 'on', or 'only'" .Values.fips) }} +{{- end }} +{{- end }} - name: COHERENCE_IMAGE {{- if kindIs "string" .Values.defaultCoherenceImage }} value: {{ .Values.defaultCoherenceImage | quote }} diff --git a/helm-charts/coherence-operator/values.yaml b/helm-charts/coherence-operator/values.yaml index 0047aa0d..f147e4ed 100644 --- a/helm-charts/coherence-operator/values.yaml +++ b/helm-charts/coherence-operator/values.yaml @@ -207,12 +207,12 @@ clusterRoles: true # nodeRoles controls whether the Helm chart will create RBAC ClusterRole and bindings for the Operator to # lookup cluster-wide Node information. # Setting this value clusterRoles and to false will mean that the Operator cannot look up Node labels that will be used -# to set theCoherence site and rack values so Coherence cluster will be unable to automatically achieve site-safety. +# to set the Coherence site and rack values so Coherence cluster will be unable to automatically achieve site-safety. # The default is true. nodeRoles: false # webhooks controls whether the Coherence Operator registers admission web-hooks for the Coherence resource. # If this is set to false, then it will be possible to install invalid Coherence resource into the Kubernetes -# cluster, that may cause errors when the Operator tries to reconcile them, or worse the Operator may create +# cluster. This may cause errors when the Operator tries to reconcile them, or worse, the Operator may create # other invalid Kubernetes resources that fail to run. webhooks: true @@ -224,3 +224,9 @@ allowCoherenceJobs: true # If set to false, the Helm chart will not install the CRDs. # The CRDs must be manually installed before the Operator can be installed. installCrd: true + +cipherAllowList: [] + +cipherDenyList: [] + +fips: diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index aa8646fb..08cef28d 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -8,8 +8,10 @@ package operator import ( + "crypto/tls" "flag" "fmt" + "github.com/go-logr/logr" "github.com/oracle/coherence-operator/pkg/clients" "github.com/oracle/coherence-operator/pkg/data" "github.com/spf13/cobra" @@ -56,6 +58,8 @@ const ( FlagJobCRD = "install-job-crd" FlagEnableCoherenceJobs = "enable-jobs" FlagDevMode = "coherence-dev-mode" + FlagCipherDenyList = "cipher-deny-list" + FlagCipherAllowList = "cipher-allow-list" FlagDryRun = "dry-run" FlagEnableWebhook = "enable-webhook" FlagEnableHttp2 = "enable-http2" @@ -290,6 +294,14 @@ func SetupFlags(cmd *cobra.Command, v *viper.Viper) { FlagGlobalLabel, nil, "A label to apply to all resources managed by the Operator (can be used multiple times)") + cmd.Flags().StringArray( + FlagCipherDenyList, + nil, + "A list of TLS cipher names to be disabled") + cmd.Flags().StringArray( + FlagCipherAllowList, + nil, + "A list of TLS cipher names to be enabled (if a cipher appears in this list and the deny list it will be disabled)") // enable using dashed notation in flags and underscores in env v.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) @@ -467,6 +479,99 @@ func GetGlobalLabels(v *viper.Viper) (map[string]string, error) { return stringSliceToMap(args, FlagGlobalLabel) } +// GetTlsCipherDenyList returns the names of the TLS cipher suites to be disabled. +func GetTlsCipherDenyList(v *viper.Viper) []string { + return createCipherList(v.GetStringSlice(FlagCipherDenyList)) +} + +// GetTlsCipherAllowList returns the names of the TLS cipher suites to be enabled. +func GetTlsCipherAllowList(v *viper.Viper) []string { + return createCipherList(v.GetStringSlice(FlagCipherAllowList)) +} + +func createCipherList(l []string) []string { + var list []string + for i := 0; i < len(l); i++ { + for _, s := range strings.Split(l[i], ",") { + list = append(list, strings.ToUpper(s)) + } + } + return list +} + +// DefaultCipherDenyList returns the default list of ciphers disabled by Oracle's policies. +func DefaultCipherDenyList() []uint16 { + return []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA} +} + +// NewCipherSuiteConfig returns a function that will configure the allowed cipher suites for a TLS configuration. +func NewCipherSuiteConfig(v *viper.Viper, log logr.Logger) (func(c *tls.Config), error) { + var allowed []uint16 + all := make(map[uint16]*tls.CipherSuite) + allByName := make(map[string]*tls.CipherSuite) + + for _, cs := range tls.CipherSuites() { + all[cs.ID] = cs + allByName[cs.Name] = cs + allowed = append(allowed, cs.ID) + } + + for _, cs := range tls.InsecureCipherSuites() { + all[cs.ID] = cs + allByName[cs.Name] = cs + } + + allowed = RemoveAllFromUInt16Array(allowed, DefaultCipherDenyList()...) + + denyList := GetTlsCipherDenyList(v) + allDenied := false + allowList := GetTlsCipherAllowList(v) + + for _, name := range denyList { + if strings.ToUpper(name) == "ALL" { + // remove all allowed suites + allowed = make([]uint16, 0) + allDenied = true + break + } + } + + if allDenied && len(allowList) == 0 { + return func(c *tls.Config) {}, + fmt.Errorf("The --%s command line flag has denied all cipher suites but no allowed suites have been specified using the %s flag ", FlagCipherDenyList, FlagCipherAllowList) + } + + for _, name := range allowList { + if cs, found := allByName[name]; found { + allowed = append(allowed, cs.ID) + } else { + return func(c *tls.Config) {}, + fmt.Errorf("invalid --%s command line flag, %s is not a recognised cipher name (see https://pkg.go.dev/crypto/tls#pkg-constants)", FlagCipherAllowList, name) + } + } + + if !allDenied { + for _, name := range denyList { + if cs, found := allByName[name]; found { + allowed = RemoveFromUInt16Array(allowed, cs.ID) + } else { + return func(c *tls.Config) {}, + fmt.Errorf("invalid --%s command line flag, %s is not a recognised cipher name (see https://pkg.go.dev/crypto/tls#pkg-constants)", FlagCipherDenyList, name) + } + } + } + + for i := range allowed { + if cs, found := all[allowed[i]]; found && cs.Insecure { + setupLog.Info("WARNING: An insecure cipher suite has been enabled", "SuiteName", cs.Name) + } + } + + return func(c *tls.Config) { + c.CipherSuites = allowed + }, nil +} + func GetExtraEnvVars() []string { return GetViper().GetStringSlice(FlagEnvVar) } @@ -496,3 +601,21 @@ func stringSliceToMap(args []string, flag string) (map[string]string, error) { } return m, nil } + +// RemoveAllFromUInt16Array removes all the specified values from a uint16 array, returning the updated array. +func RemoveAllFromUInt16Array(arr []uint16, toRemove ...uint16) []uint16 { + for _, i := range toRemove { + arr = RemoveFromUInt16Array(arr, i) + } + return arr +} + +// RemoveFromUInt16Array removes a value from a uint16 array, returning the updated array. +func RemoveFromUInt16Array(arr []uint16, toRemove uint16) []uint16 { + for i, ui := range arr { + if ui == toRemove { + return append(arr[:i], arr[i+1:]...) + } + } + return arr +} diff --git a/pkg/runner/cmd_operator.go b/pkg/runner/cmd_operator.go index d39a483f..a2758882 100644 --- a/pkg/runner/cmd_operator.go +++ b/pkg/runner/cmd_operator.go @@ -65,7 +65,7 @@ func operatorCommand(v *viper.Viper) *cobra.Command { Short: "Run the Coherence Operator", Long: "Run the Coherence Operator", RunE: func(cmd *cobra.Command, args []string) error { - return execute() + return execute(v) }, } @@ -74,7 +74,7 @@ func operatorCommand(v *viper.Viper) *cobra.Command { return cmd } -func execute() error { +func execute(v *viper.Viper) error { ctrl.SetLogger(zap.New(zap.UseDevMode(true))) setupLog.Info(fmt.Sprintf("Operator Coherence Image: %s", operator.GetDefaultCoherenceImage())) @@ -94,12 +94,24 @@ func execute() error { vpr := operator.GetViper() var tlsOpts []func(*tls.Config) + suiteConfig, err := operator.NewCipherSuiteConfig(v, setupLog) + if err != nil { + return err + } + tlsOpts = append(tlsOpts, suiteConfig) + enableHTTP2 := vpr.GetBool(operator.FlagEnableHttp2) if !enableHTTP2 { tlsOpts = append(tlsOpts, disableHTTP2) } cfg := ctrl.GetConfigOrDie() + cfg.WrapTransport = func(rt http.RoundTripper) http.RoundTripper { + t := rt.(*http.Transport) + suiteConfig(t.TLSClientConfig) + return rt + } + cs, err := clients.NewForConfig(cfg) if err != nil { return errors.Wrap(err, "unable to create client set") diff --git a/pkg/runner/cmd_operator_test.go b/pkg/runner/cmd_operator_test.go index bad96731..4ff3bb26 100644 --- a/pkg/runner/cmd_operator_test.go +++ b/pkg/runner/cmd_operator_test.go @@ -7,13 +7,20 @@ package runner import ( + "crypto/tls" . "github.com/onsi/gomega" coh "github.com/oracle/coherence-operator/api/v1" "github.com/oracle/coherence-operator/pkg/operator" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "strings" "testing" ) +var ( + testLog = ctrl.Log.WithName("test") +) + func TestBasicOperator(t *testing.T) { g := NewGomegaWithT(t) @@ -128,3 +135,396 @@ func TestOperatorWithMultipleGlobalAnnotations(t *testing.T) { g.Expect(l["two"]).To(Equal("value-two")) g.Expect(l["three"]).To(Equal("value-three")) } + +func TestOperatorWithCipherAllowList(t *testing.T) { + g := NewGomegaWithT(t) + + d := &coh.Coherence{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + + toAdd := tls.InsecureCipherSuites()[0] + + args := []string{"operator", "--dry-run", + "--cipher-allow-list", strings.ToLower(toAdd.Name), + } + env := EnvVarsFromDeployment(t, d) + + e, err := ExecuteWithArgsAndNewViper(env, args) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(e).NotTo(BeNil()) + + l := operator.GetTlsCipherAllowList(e.V) + g.Expect(l).NotTo(BeNil()) + g.Expect(len(l)).To(Equal(1)) + g.Expect(l[0]).To(Equal(toAdd.Name)) + + fn, err := operator.NewCipherSuiteConfig(e.V, testLog) + g.Expect(err).To(BeNil()) + g.Expect(fn).NotTo(BeNil()) + cfg := &tls.Config{} + fn(cfg) + + expected := append(defaultCiphers(), toAdd.ID) + g.Expect(cfg.CipherSuites).To(Equal(expected)) +} + +func TestOperatorWithInvalidCipherNameInAllowList(t *testing.T) { + g := NewGomegaWithT(t) + + d := &coh.Coherence{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + + args := []string{"operator", "--dry-run", + "--cipher-allow-list", "Foo", + } + env := EnvVarsFromDeployment(t, d) + + _, err := ExecuteWithArgsAndNewViper(env, args) + g.Expect(err).To(HaveOccurred()) +} + +func TestOperatorWithMultipleCipherAllowList(t *testing.T) { + g := NewGomegaWithT(t) + + d := &coh.Coherence{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + + toAdd1 := tls.InsecureCipherSuites()[1] + toAdd2 := tls.InsecureCipherSuites()[0] + + args := []string{"operator", "--dry-run", + "--cipher-allow-list", strings.ToLower(toAdd1.Name), + "--cipher-allow-list", toAdd2.Name, + } + env := EnvVarsFromDeployment(t, d) + + e, err := ExecuteWithArgsAndNewViper(env, args) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(e).NotTo(BeNil()) + + l := operator.GetTlsCipherAllowList(e.V) + g.Expect(l).NotTo(BeNil()) + g.Expect(len(l)).To(Equal(2)) + g.Expect(l[0]).To(Equal(toAdd1.Name)) + g.Expect(l[1]).To(Equal(toAdd2.Name)) + + fn, err := operator.NewCipherSuiteConfig(e.V, testLog) + g.Expect(err).To(BeNil()) + g.Expect(fn).NotTo(BeNil()) + cfg := &tls.Config{} + fn(cfg) + + expected := append(defaultCiphers(), toAdd1.ID, toAdd2.ID) + g.Expect(cfg.CipherSuites).To(Equal(expected)) +} + +func TestOperatorWithMultipleCommaDelimitedCipherAllowList(t *testing.T) { + g := NewGomegaWithT(t) + + d := &coh.Coherence{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + + toAdd1 := tls.InsecureCipherSuites()[1] + toAdd2 := tls.InsecureCipherSuites()[0] + + args := []string{"operator", "--dry-run", + "--cipher-allow-list", strings.ToLower(toAdd1.Name) + "," + toAdd2.Name, + } + env := EnvVarsFromDeployment(t, d) + + e, err := ExecuteWithArgsAndNewViper(env, args) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(e).NotTo(BeNil()) + + l := operator.GetTlsCipherAllowList(e.V) + g.Expect(l).NotTo(BeNil()) + g.Expect(len(l)).To(Equal(2)) + g.Expect(l[0]).To(Equal(toAdd1.Name)) + g.Expect(l[1]).To(Equal(toAdd2.Name)) + + fn, err := operator.NewCipherSuiteConfig(e.V, testLog) + g.Expect(err).To(BeNil()) + g.Expect(fn).NotTo(BeNil()) + cfg := &tls.Config{} + fn(cfg) + + expected := append(defaultCiphers(), toAdd1.ID, toAdd2.ID) + g.Expect(cfg.CipherSuites).To(Equal(expected)) +} + +func TestOperatorWithCipherDenyList(t *testing.T) { + g := NewGomegaWithT(t) + + d := &coh.Coherence{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + + toRemove := tls.CipherSuites()[0] + + args := []string{"operator", "--dry-run", + "--cipher-deny-list", strings.ToLower(toRemove.Name), + } + env := EnvVarsFromDeployment(t, d) + + e, err := ExecuteWithArgsAndNewViper(env, args) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(e).NotTo(BeNil()) + + l := operator.GetTlsCipherDenyList(e.V) + g.Expect(l).NotTo(BeNil()) + g.Expect(len(l)).To(Equal(1)) + g.Expect(l[0]).To(Equal(toRemove.Name)) + + fn, err := operator.NewCipherSuiteConfig(e.V, testLog) + g.Expect(err).To(BeNil()) + g.Expect(fn).NotTo(BeNil()) + cfg := &tls.Config{} + fn(cfg) + + expected := operator.RemoveFromUInt16Array(defaultCiphers(), toRemove.ID) + g.Expect(cfg.CipherSuites).To(Equal(expected)) +} + +func TestOperatorWithInvalidCipherNameInDenyList(t *testing.T) { + g := NewGomegaWithT(t) + + d := &coh.Coherence{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + + args := []string{"operator", "--dry-run", + "--cipher-deny-list", "Foo", + } + env := EnvVarsFromDeployment(t, d) + + _, err := ExecuteWithArgsAndNewViper(env, args) + g.Expect(err).To(HaveOccurred()) +} + +func TestOperatorWithMultipleCipherDenyList(t *testing.T) { + g := NewGomegaWithT(t) + + d := &coh.Coherence{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + + toRemove1 := tls.CipherSuites()[1] + toRemove2 := tls.CipherSuites()[0] + + args := []string{"operator", "--dry-run", + "--cipher-deny-list", strings.ToLower(toRemove1.Name), + "--cipher-deny-list", toRemove2.Name, + } + env := EnvVarsFromDeployment(t, d) + + e, err := ExecuteWithArgsAndNewViper(env, args) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(e).NotTo(BeNil()) + + l := operator.GetTlsCipherDenyList(e.V) + g.Expect(l).NotTo(BeNil()) + g.Expect(len(l)).To(Equal(2)) + g.Expect(l[0]).To(Equal(toRemove1.Name)) + g.Expect(l[1]).To(Equal(toRemove2.Name)) + + fn, err := operator.NewCipherSuiteConfig(e.V, testLog) + g.Expect(err).To(BeNil()) + g.Expect(fn).NotTo(BeNil()) + cfg := &tls.Config{} + fn(cfg) + + expected := operator.RemoveAllFromUInt16Array(defaultCiphers(), toRemove1.ID, toRemove2.ID) + g.Expect(cfg.CipherSuites).To(Equal(expected)) +} + +func TestOperatorWithMultipleCommaDelimitedCipherDenyList(t *testing.T) { + g := NewGomegaWithT(t) + + d := &coh.Coherence{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + + toRemove1 := tls.CipherSuites()[1] + toRemove2 := tls.CipherSuites()[0] + + args := []string{"operator", "--dry-run", + "--cipher-deny-list", strings.ToLower(toRemove1.Name) + "," + toRemove2.Name, + } + env := EnvVarsFromDeployment(t, d) + + e, err := ExecuteWithArgsAndNewViper(env, args) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(e).NotTo(BeNil()) + + l := operator.GetTlsCipherDenyList(e.V) + g.Expect(l).NotTo(BeNil()) + g.Expect(len(l)).To(Equal(2)) + g.Expect(l[0]).To(Equal(toRemove1.Name)) + g.Expect(l[1]).To(Equal(toRemove2.Name)) + + fn, err := operator.NewCipherSuiteConfig(e.V, testLog) + g.Expect(err).To(BeNil()) + g.Expect(fn).NotTo(BeNil()) + cfg := &tls.Config{} + fn(cfg) + + expected := operator.RemoveAllFromUInt16Array(defaultCiphers(), toRemove1.ID, toRemove2.ID) + g.Expect(cfg.CipherSuites).To(Equal(expected)) +} + +func TestOperatorWithAllowListAndDenyList(t *testing.T) { + g := NewGomegaWithT(t) + + d := &coh.Coherence{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + + toAdd := tls.InsecureCipherSuites()[1] + toRemove := tls.CipherSuites()[0] + + args := []string{"operator", "--dry-run", + "--cipher-allow-list", toAdd.Name, + "--cipher-deny-list", toRemove.Name, + } + env := EnvVarsFromDeployment(t, d) + + e, err := ExecuteWithArgsAndNewViper(env, args) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(e).NotTo(BeNil()) + + bl := operator.GetTlsCipherDenyList(e.V) + g.Expect(bl).NotTo(BeNil()) + g.Expect(len(bl)).To(Equal(1)) + g.Expect(bl[0]).To(Equal(toRemove.Name)) + + wl := operator.GetTlsCipherAllowList(e.V) + g.Expect(wl).NotTo(BeNil()) + g.Expect(len(wl)).To(Equal(1)) + g.Expect(wl[0]).To(Equal(toAdd.Name)) + + fn, err := operator.NewCipherSuiteConfig(e.V, testLog) + g.Expect(err).To(BeNil()) + g.Expect(fn).NotTo(BeNil()) + cfg := &tls.Config{} + fn(cfg) + + expected := operator.RemoveFromUInt16Array(defaultCiphers(), toRemove.ID) + expected = append(expected, toAdd.ID) + g.Expect(cfg.CipherSuites).To(Equal(expected)) +} + +func TestOperatorWithAllowListAndDenyListSameCipher(t *testing.T) { + g := NewGomegaWithT(t) + + d := &coh.Coherence{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + + cipher := tls.InsecureCipherSuites()[1] + + args := []string{"operator", "--dry-run", + "--cipher-allow-list", cipher.Name, + "--cipher-deny-list", cipher.Name, + } + env := EnvVarsFromDeployment(t, d) + + e, err := ExecuteWithArgsAndNewViper(env, args) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(e).NotTo(BeNil()) + + bl := operator.GetTlsCipherDenyList(e.V) + g.Expect(bl).NotTo(BeNil()) + g.Expect(len(bl)).To(Equal(1)) + g.Expect(bl[0]).To(Equal(cipher.Name)) + + wl := operator.GetTlsCipherAllowList(e.V) + g.Expect(wl).NotTo(BeNil()) + g.Expect(len(wl)).To(Equal(1)) + g.Expect(wl[0]).To(Equal(cipher.Name)) + + fn, err := operator.NewCipherSuiteConfig(e.V, testLog) + g.Expect(err).To(BeNil()) + g.Expect(fn).NotTo(BeNil()) + cfg := &tls.Config{} + fn(cfg) + + expected := defaultCiphers() + g.Expect(cfg.CipherSuites).To(Equal(expected)) +} + +func TestOperatorWithDenyAllCiphers(t *testing.T) { + g := NewGomegaWithT(t) + + d := &coh.Coherence{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + + cipher := tls.InsecureCipherSuites()[1] + + args := []string{"operator", "--dry-run", + "--cipher-allow-list", cipher.Name, + "--cipher-deny-list", "ALL", + } + env := EnvVarsFromDeployment(t, d) + + e, err := ExecuteWithArgsAndNewViper(env, args) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(e).NotTo(BeNil()) + + bl := operator.GetTlsCipherDenyList(e.V) + g.Expect(bl).NotTo(BeNil()) + g.Expect(len(bl)).To(Equal(1)) + g.Expect(bl[0]).To(Equal("ALL")) + + wl := operator.GetTlsCipherAllowList(e.V) + g.Expect(wl).NotTo(BeNil()) + g.Expect(len(wl)).To(Equal(1)) + g.Expect(wl[0]).To(Equal(cipher.Name)) + + fn, err := operator.NewCipherSuiteConfig(e.V, testLog) + g.Expect(err).To(BeNil()) + g.Expect(fn).NotTo(BeNil()) + cfg := &tls.Config{} + fn(cfg) + + expected := []uint16{cipher.ID} + g.Expect(cfg.CipherSuites).To(Equal(expected)) +} + +func TestOperatorWithDenyAllCiphersButNoAllowList(t *testing.T) { + g := NewGomegaWithT(t) + + d := &coh.Coherence{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + + args := []string{"operator", "--dry-run", "--cipher-deny-list", "ALL"} + env := EnvVarsFromDeployment(t, d) + + e, err := ExecuteWithArgsAndNewViper(env, args) + g.Expect(err).NotTo(BeNil()) + + bl := operator.GetTlsCipherDenyList(e.V) + g.Expect(bl).NotTo(BeNil()) + g.Expect(len(bl)).To(Equal(1)) + g.Expect(bl[0]).To(Equal("ALL")) + + wl := operator.GetTlsCipherAllowList(e.V) + g.Expect(wl).To(BeNil()) + + _, err = operator.NewCipherSuiteConfig(e.V, testLog) + g.Expect(err).NotTo(BeNil()) +} + +func defaultCiphers() []uint16 { + var ciphers []uint16 + for _, i := range tls.CipherSuites() { + ciphers = append(ciphers, i.ID) + } + ciphers = operator.RemoveAllFromUInt16Array(ciphers, operator.DefaultCipherDenyList()...) + return ciphers +}