diff --git a/config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml b/config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml index 8a39cc717e..1cc93ee9f6 100644 --- a/config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml +++ b/config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml @@ -5063,7 +5063,8 @@ spec: - message: change port using .spec.port instead rule: '!has(self.port)' - message: TLS is always enabled - rule: '!has(self.ssl) && !self.exists(k, k.startsWith("ssl_"))' + rule: '!has(self.ssl) && !self.exists(k, k.startsWith("ssl_") + && !(k == ''ssl_groups'' || k == ''ssl_ecdh_curve''))' - message: domain socket paths cannot be changed rule: '!self.exists(k, k.startsWith("unix_socket_"))' - message: wal_level must be "replica" or higher @@ -18699,6 +18700,9 @@ spec: - postgresVersion type: object x-kubernetes-validations: + - message: The ssl_groups parameter is only available in pg18 and greater + rule: '!has(self.?config.parameters.ssl_groups) || self.postgresVersion + > 17' - fieldPath: .config.parameters.log_directory message: all instances need "volumes.temp" to log in "/pgtmp" rule: self.?config.parameters.log_directory.optMap(v, type(v) != string @@ -24149,7 +24153,8 @@ spec: - message: change port using .spec.port instead rule: '!has(self.port)' - message: TLS is always enabled - rule: '!has(self.ssl) && !self.exists(k, k.startsWith("ssl_"))' + rule: '!has(self.ssl) && !self.exists(k, k.startsWith("ssl_") + && !(k == ''ssl_groups'' || k == ''ssl_ecdh_curve''))' - message: domain socket paths cannot be changed rule: '!self.exists(k, k.startsWith("unix_socket_"))' - message: wal_level must be "replica" or higher @@ -37767,6 +37772,10 @@ spec: - instances - postgresVersion type: object + x-kubernetes-validations: + - message: The ssl_groups parameter is only available in pg18 and greater + rule: '!has(self.?config.parameters.ssl_groups) || self.postgresVersion + > 17' status: description: PostgresClusterStatus defines the observed state of PostgresCluster properties: diff --git a/internal/crd/validation/postgrescluster/postgres_config_test.go b/internal/crd/validation/postgrescluster/postgres_config_test.go index 5a636ac439..83b46e2437 100644 --- a/internal/crd/validation/postgrescluster/postgres_config_test.go +++ b/internal/crd/validation/postgrescluster/postgres_config_test.go @@ -70,6 +70,124 @@ func TestPostgresConfigParametersV1beta1(t *testing.T) { } }) }) + + t.Run("ssl_groups and ssl_ecdh_curve", func(t *testing.T) { + t.Run("ssl_groups not allowed for pg17", func(t *testing.T) { + for _, tt := range []struct { + key string + value any + }{ + {key: "ssl_groups", value: "anything"}, + } { + t.Run(tt.key, func(t *testing.T) { + cluster := u.DeepCopy() + require.UnmarshalIntoField(t, cluster, + require.Value(yaml.Marshal(17)), + "spec", "postgresVersion") + require.UnmarshalIntoField(t, cluster, + require.Value(yaml.Marshal(tt.value)), + "spec", "config", "parameters", tt.key) + + err := cc.Create(ctx, cluster, client.DryRunAll) + assert.Assert(t, apierrors.IsInvalid(err)) + + details := require.StatusErrorDetails(t, err) + assert.Assert(t, cmp.Len(details.Causes, 1)) + }) + } + }) + + t.Run("ssl_groups allowed for pg18", func(t *testing.T) { + for _, tt := range []struct { + key string + value any + }{ + {key: "ssl_groups", value: "anything"}, + } { + t.Run(tt.key, func(t *testing.T) { + cluster := u.DeepCopy() + require.UnmarshalIntoField(t, cluster, + require.Value(yaml.Marshal(18)), + "spec", "postgresVersion") + require.UnmarshalIntoField(t, cluster, + require.Value(yaml.Marshal(tt.value)), + "spec", "config", "parameters", tt.key) + + assert.NilError(t, cc.Create(ctx, cluster, client.DryRunAll)) + }) + } + }) + + t.Run("ssl_ecdh_curve allowed for both", func(t *testing.T) { + for _, tt := range []struct { + key string + value any + }{ + {key: "ssl_ecdh_curve", value: "anything"}, + } { + t.Run(tt.key, func(t *testing.T) { + cluster := u.DeepCopy() + require.UnmarshalIntoField(t, cluster, + require.Value(yaml.Marshal(17)), + "spec", "postgresVersion") + require.UnmarshalIntoField(t, cluster, + require.Value(yaml.Marshal(tt.value)), + "spec", "config", "parameters", tt.key) + + assert.NilError(t, cc.Create(ctx, cluster, client.DryRunAll)) + + cluster2 := u.DeepCopy() + require.UnmarshalIntoField(t, cluster2, + require.Value(yaml.Marshal(18)), + "spec", "postgresVersion") + require.UnmarshalIntoField(t, cluster2, + require.Value(yaml.Marshal(tt.value)), + "spec", "config", "parameters", tt.key) + + assert.NilError(t, cc.Create(ctx, cluster2, client.DryRunAll)) + }) + } + }) + + t.Run("other ssl_* parameters not allowed for any pg version", func(t *testing.T) { + for _, tt := range []struct { + key string + value any + }{ + {key: "ssl_anything", value: "anything"}, + } { + t.Run(tt.key, func(t *testing.T) { + cluster := u.DeepCopy() + require.UnmarshalIntoField(t, cluster, + require.Value(yaml.Marshal(17)), + "spec", "postgresVersion") + require.UnmarshalIntoField(t, cluster, + require.Value(yaml.Marshal(tt.value)), + "spec", "config", "parameters", tt.key) + + err := cc.Create(ctx, cluster, client.DryRunAll) + assert.Assert(t, apierrors.IsInvalid(err)) + + details := require.StatusErrorDetails(t, err) + assert.Assert(t, cmp.Len(details.Causes, 1)) + + cluster1 := u.DeepCopy() + require.UnmarshalIntoField(t, cluster1, + require.Value(yaml.Marshal(18)), + "spec", "postgresVersion") + require.UnmarshalIntoField(t, cluster1, + require.Value(yaml.Marshal(tt.value)), + "spec", "config", "parameters", tt.key) + + err = cc.Create(ctx, cluster1, client.DryRunAll) + assert.Assert(t, apierrors.IsInvalid(err)) + + details = require.StatusErrorDetails(t, err) + assert.Assert(t, cmp.Len(details.Causes, 1)) + }) + } + }) + }) } func TestPostgresConfigParametersV1(t *testing.T) { diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1/postgres_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1/postgres_types.go index de16442f6d..555f0ddd52 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1/postgres_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1/postgres_types.go @@ -37,7 +37,7 @@ type PostgresConfigSpec struct { // // +kubebuilder:validation:XValidation:rule=`!has(self.listen_addresses)`,message=`network connectivity is always enabled: listen_addresses` // +kubebuilder:validation:XValidation:rule=`!has(self.port)`,message=`change port using .spec.port instead` - // +kubebuilder:validation:XValidation:rule=`!has(self.ssl) && !self.exists(k, k.startsWith("ssl_"))`,message=`TLS is always enabled` + // +kubebuilder:validation:XValidation:rule=`!has(self.ssl) && !self.exists(k, k.startsWith("ssl_") && !(k == 'ssl_groups' || k == 'ssl_ecdh_curve'))`,message=`TLS is always enabled` // +kubebuilder:validation:XValidation:rule=`!self.exists(k, k.startsWith("unix_socket_"))`,message=`domain socket paths cannot be changed` // // # Write Ahead Log diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1/postgrescluster_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1/postgrescluster_types.go index fdd53df52b..9817f176dc 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1/postgrescluster_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1/postgrescluster_types.go @@ -17,6 +17,10 @@ import ( // PostgresClusterSpec defines the desired state of PostgresCluster // --- // +// # Postgres 18 +// +// +kubebuilder:validation:XValidation:rule=`!has(self.?config.parameters.ssl_groups) || self.postgresVersion > 17`,message=`The ssl_groups parameter is only available in pg18 and greater` +// // # Postgres Logging // // +kubebuilder:validation:XValidation:fieldPath=`.config.parameters.log_directory`,message=`all instances need "volumes.temp" to log in "/pgtmp"`,rule=`self.?config.parameters.log_directory.optMap(v, type(v) != string || !v.startsWith("/pgtmp/logs/postgres") || self.instances.all(i, i.?volumes.temp.hasValue())).orValue(true)` diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgres_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgres_types.go index 2880c565e0..d1b4c732a1 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgres_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgres_types.go @@ -54,7 +54,7 @@ type PostgresConfigSpec struct { // // +kubebuilder:validation:XValidation:rule=`!has(self.listen_addresses)`,message=`network connectivity is always enabled: listen_addresses` // +kubebuilder:validation:XValidation:rule=`!has(self.port)`,message=`change port using .spec.port instead` - // +kubebuilder:validation:XValidation:rule=`!has(self.ssl) && !self.exists(k, k.startsWith("ssl_"))`,message=`TLS is always enabled` + // +kubebuilder:validation:XValidation:rule=`!has(self.ssl) && !self.exists(k, k.startsWith("ssl_") && !(k == 'ssl_groups' || k == 'ssl_ecdh_curve'))`,message=`TLS is always enabled` // +kubebuilder:validation:XValidation:rule=`!self.exists(k, k.startsWith("unix_socket_"))`,message=`domain socket paths cannot be changed` // // # Write Ahead Log diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go index 1b6f3e1c77..e8a9f0055a 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go @@ -13,6 +13,11 @@ import ( ) // PostgresClusterSpec defines the desired state of PostgresCluster +// --- +// +// # Postgres 18 +// +// +kubebuilder:validation:XValidation:rule=`!has(self.?config.parameters.ssl_groups) || self.postgresVersion > 17`,message=`The ssl_groups parameter is only available in pg18 and greater` type PostgresClusterSpec struct { // +optional Metadata *Metadata `json:"metadata,omitempty"`