diff --git a/api/apps/v1alpha1/postgresql/types.go b/api/apps/v1alpha1/postgresql/types.go index a2ff77a8..ad8f2b36 100644 --- a/api/apps/v1alpha1/postgresql/types.go +++ b/api/apps/v1alpha1/postgresql/types.go @@ -112,15 +112,9 @@ type DatabaseRoles struct { } type PostgreSQL struct { - // PostgreSQL server parameters. - // +kubebuilder:default:={} - Parameters PostgreSQLParameters `json:"parameters,omitempty"` -} - -type PostgreSQLParameters struct { - // Maximum number of concurrent connections to the database server. - // +kubebuilder:default:=100 - MaxConnections int `json:"max_connections,omitempty"` + // PostgreSQL server parameters. All values must be strings (enclose numbers in quotes). See PostgreSQL documentation for available parameters. WARNING: Some parameters are managed by CloudNativePG and should NOT be overridden (archive_mode, archive_command, restore_command, primary_conninfo, etc.). Incorrect values may break backup/replication or require pod restart. + // +kubebuilder:default:={"max_connections":"100"} + Parameters map[string]string `json:"parameters,omitempty"` } type Quorum struct { diff --git a/api/apps/v1alpha1/postgresql/zz_generated.deepcopy.go b/api/apps/v1alpha1/postgresql/zz_generated.deepcopy.go index 5fba855e..89a42778 100644 --- a/api/apps/v1alpha1/postgresql/zz_generated.deepcopy.go +++ b/api/apps/v1alpha1/postgresql/zz_generated.deepcopy.go @@ -85,7 +85,7 @@ func (in *ConfigSpec) DeepCopyInto(out *ConfigSpec) { *out = *in in.Resources.DeepCopyInto(&out.Resources) out.Size = in.Size.DeepCopy() - out.Postgresql = in.Postgresql + in.Postgresql.DeepCopyInto(&out.Postgresql) out.Quorum = in.Quorum if in.Users != nil { in, out := &in.Users, &out.Users @@ -164,7 +164,13 @@ func (in *DatabaseRoles) DeepCopy() *DatabaseRoles { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PostgreSQL) DeepCopyInto(out *PostgreSQL) { *out = *in - out.Parameters = in.Parameters + if in.Parameters != nil { + in, out := &in.Parameters, &out.Parameters + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQL. @@ -177,21 +183,6 @@ func (in *PostgreSQL) DeepCopy() *PostgreSQL { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PostgreSQLParameters) DeepCopyInto(out *PostgreSQLParameters) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLParameters. -func (in *PostgreSQLParameters) DeepCopy() *PostgreSQLParameters { - if in == nil { - return nil - } - out := new(PostgreSQLParameters) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Quorum) DeepCopyInto(out *Quorum) { *out = *in diff --git a/hack/e2e-apps/postgres.bats b/hack/e2e-apps/postgres.bats index b86bc664..b1d3de51 100644 --- a/hack/e2e-apps/postgres.bats +++ b/hack/e2e-apps/postgres.bats @@ -16,7 +16,7 @@ spec: storageClass: "" postgresql: parameters: - max_connections: 100 + max_connections: "100" quorum: minSyncReplicas: 0 maxSyncReplicas: 0 diff --git a/packages/apps/postgres/README.md b/packages/apps/postgres/README.md index 4cda7284..a2fb76eb 100644 --- a/packages/apps/postgres/README.md +++ b/packages/apps/postgres/README.md @@ -81,11 +81,10 @@ See: ### Application-specific parameters -| Name | Description | Type | Value | -| --------------------------------------- | ---------------------------------------------------------------- | -------- | ----- | -| `postgresql` | PostgreSQL server configuration. | `object` | `{}` | -| `postgresql.parameters` | PostgreSQL server parameters. | `object` | `{}` | -| `postgresql.parameters.max_connections` | Maximum number of concurrent connections to the database server. | `int` | `100` | +| Name | Description | Type | Value | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ----- | +| `postgresql` | PostgreSQL server configuration. | `object` | `{}` | +| `postgresql.parameters` | PostgreSQL server parameters. All values must be strings (enclose numbers in quotes). See PostgreSQL documentation for available parameters. WARNING: Some parameters are managed by CloudNativePG and should NOT be overridden (archive_mode, archive_command, restore_command, primary_conninfo, etc.). Incorrect values may break backup/replication or require pod restart. | `map[string]string` | `{}` | ### Quorum-based synchronous replication diff --git a/packages/apps/postgres/templates/db.yaml b/packages/apps/postgres/templates/db.yaml index 2cdaec1d..6c1fda12 100644 --- a/packages/apps/postgres/templates/db.yaml +++ b/packages/apps/postgres/templates/db.yaml @@ -60,9 +60,16 @@ spec: {{- end }} postgresql: parameters: - max_wal_senders: "30" - {{- with .Values.postgresql.parameters.max_connections }} - max_connections: "{{ . }}" + {{- $defaults := dict "max_wal_senders" "30" }} + {{- $params := merge (dict) .Values.postgresql.parameters $defaults }} + {{- /* Security: Block parameters that could allow shell command execution */ -}} + {{- $dangerousParams := list "archive_command" "restore_command" "ssl_passphrase_command" "log_destination" "event_source" }} + {{- range $key, $value := $params }} + {{- $normalizedKey := lower $key }} + {{- if has $normalizedKey $dangerousParams }} + {{- fail (printf "SECURITY: Parameter '%s' is blocked for security reasons. This parameter could allow arbitrary shell command execution and is managed by CloudNativePG operator." $key) }} + {{- end }} + {{ $key }}: {{ $value | quote }} {{- end }} minSyncReplicas: {{ .Values.quorum.minSyncReplicas }} diff --git a/packages/apps/postgres/values.schema.json b/packages/apps/postgres/values.schema.json index 98e29822..470fca2f 100644 --- a/packages/apps/postgres/values.schema.json +++ b/packages/apps/postgres/values.schema.json @@ -97,15 +97,13 @@ "default": {}, "properties": { "parameters": { - "description": "PostgreSQL server parameters.", + "description": "PostgreSQL server parameters. All values must be strings (enclose numbers in quotes). See PostgreSQL documentation for available parameters. WARNING: Some parameters are managed by CloudNativePG and should NOT be overridden (archive_mode, archive_command, restore_command, primary_conninfo, etc.). Incorrect values may break backup/replication or require pod restart.", "type": "object", - "default": {}, - "properties": { - "max_connections": { - "description": "Maximum number of concurrent connections to the database server.", - "type": "integer", - "default": 100 - } + "default": { + "max_connections": "100" + }, + "additionalProperties": { + "type": "string" } } } diff --git a/packages/apps/postgres/values.yaml b/packages/apps/postgres/values.yaml index 2ceaa9cf..07824e80 100644 --- a/packages/apps/postgres/values.yaml +++ b/packages/apps/postgres/values.yaml @@ -48,16 +48,32 @@ version: v18 ## @section Application-specific parameters ## -## @typedef {struct} PostgreSQLParameters - PostgreSQL server parameters. -## @field {int} [max_connections] - Maximum number of concurrent connections to the database server. - ## @typedef {struct} PostgreSQL - PostgreSQL server configuration. -## @field {PostgreSQLParameters} [parameters] - PostgreSQL server parameters. +## @field {map[string]string} [parameters] - PostgreSQL server parameters. All values must be strings (enclose numbers in quotes). See PostgreSQL documentation for available parameters. WARNING: Some parameters are managed by CloudNativePG and should NOT be overridden (archive_mode, archive_command, restore_command, primary_conninfo, etc.). Incorrect values may break backup/replication or require pod restart. ## @param {PostgreSQL} postgresql - PostgreSQL server configuration. postgresql: parameters: - max_connections: 100 + max_connections: "100" +## Additional parameters examples: +## shared_buffers: "1GB" +## effective_cache_size: "4GB" +## bgwriter_delay: "200ms" +## commit_delay: "100" +## commit_siblings: "5" +## work_mem: "256MB" +## +## SECURITY: The following parameters are BLOCKED for security reasons +## (could allow arbitrary shell command execution): +## archive_command, restore_command, ssl_passphrase_command, +## log_destination, event_source +## +## IMPORTANT: Do NOT override CloudNativePG-managed parameters: +## archive_mode, primary_conninfo, wal_level, max_replication_slots +## +## NOTE: Some parameters require pod restart (not just reload): +## shared_buffers, max_connections, max_prepared_transactions, +## wal_buffers, max_wal_senders ## ## @section Quorum-based synchronous replication diff --git a/packages/system/kubevirt-operator/alerts/PrometheusRule.yaml b/packages/system/kubevirt-operator/alerts/PrometheusRule.yaml deleted file mode 100644 index 72fabfa0..00000000 --- a/packages/system/kubevirt-operator/alerts/PrometheusRule.yaml +++ /dev/null @@ -1,42 +0,0 @@ -apiVersion: monitoring.coreos.com/v1 -kind: PrometheusRule -metadata: - name: vm-not-running-alert -spec: - groups: - - name: kubevirt-alerts - rules: - - alert: VMNotRunningFor10Minutes - expr: | - max_over_time( - kubevirt_vm_info{ - status!="running", - exported_namespace=~".+", - name=~".+" - }[10m] - ) == 1 - labels: - severity: critical - namespace: "{{ $labels.exported_namespace }}" - annotations: - summary: "VM '{{ $labels.name }}' in namespace '{{ $labels.exported_namespace }}' is not Running for more than 10 minutes" - description: > - The VM '{{ $labels.name }}' in namespace '{{ $labels.exported_namespace }}' has been in a non-Running state - for more than 10 minutes. Please investigate the issue. - - alert: VMINotRunningFor10Minutes - expr: | - max_over_time( - kubevirt_vmi_info{ - phase!="running", - exported_namespace=~".+", - name=~".+" - }[10m] - ) == 1 - labels: - severity: critical - namespace: "{{ $labels.exported_namespace }}" - annotations: - summary: "VMI '{{ $labels.name }}' in namespace '{{ $labels.exported_namespace }}' is not Running for more than 10 minutes" - description: > - The VM '{{ $labels.name }}' in namespace '{{ $labels.exported_namespace }}' has been in a non-Running state - for more than 10 minutes. Please investigate the issue. diff --git a/packages/system/kubevirt-operator/templates/prometheusrule.yaml b/packages/system/kubevirt-operator/templates/prometheusrule.yaml new file mode 100644 index 00000000..5984ce43 --- /dev/null +++ b/packages/system/kubevirt-operator/templates/prometheusrule.yaml @@ -0,0 +1,38 @@ +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: vm-not-running-alert +spec: + groups: + - name: kubevirt-alerts + rules: + - alert: VMNotRunningFor10Minutes + expr: | + max_over_time( + kubevirt_vm_info{ + status!="running", + exported_namespace=~".+", + name=~".+" + }[10m] + ) == 1 + labels: + severity: critical + namespace: '{{ "{{" }} $labels.exported_namespace {{ "}}" }}' + annotations: + summary: 'VM {{ "{{" }} $labels.name {{ "}}" }} in namespace {{ "{{" }} $labels.exported_namespace {{ "}}" }} is not Running for more than 10 minutes' + description: 'The VM {{ "{{" }} $labels.name {{ "}}" }} in namespace {{ "{{" }} $labels.exported_namespace {{ "}}" }} has been in a non-Running state for more than 10 minutes. Please investigate the issue.' + - alert: VMINotRunningFor10Minutes + expr: | + max_over_time( + kubevirt_vmi_info{ + phase!="running", + exported_namespace=~".+", + name=~".+" + }[10m] + ) == 1 + labels: + severity: critical + namespace: '{{ "{{" }} $labels.exported_namespace {{ "}}" }}' + annotations: + summary: 'VMI {{ "{{" }} $labels.name {{ "}}" }} in namespace {{ "{{" }} $labels.exported_namespace {{ "}}" }} is not Running for more than 10 minutes' + description: 'The VMI {{ "{{" }} $labels.name {{ "}}" }} in namespace {{ "{{" }} $labels.exported_namespace {{ "}}" }} has been in a non-Running state for more than 10 minutes. Please investigate the issue.' diff --git a/packages/system/postgres-rd/cozyrds/postgres.yaml b/packages/system/postgres-rd/cozyrds/postgres.yaml index 72d013f1..29a0056d 100644 --- a/packages/system/postgres-rd/cozyrds/postgres.yaml +++ b/packages/system/postgres-rd/cozyrds/postgres.yaml @@ -8,7 +8,7 @@ spec: singular: postgres plural: postgreses openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"replicas":{"description":"Number of Postgres replicas.","type":"integer","default":2},"resources":{"description":"Explicit CPU and memory configuration for each PostgreSQL replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available to each replica.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume Claim size available for application data.","default":"10Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"version":{"description":"PostgreSQL major version to deploy","type":"string","default":"v18","enum":["v18","v17","v16","v15","v14","v13"]},"postgresql":{"description":"PostgreSQL server configuration.","type":"object","default":{},"properties":{"parameters":{"description":"PostgreSQL server parameters.","type":"object","default":{},"properties":{"max_connections":{"description":"Maximum number of concurrent connections to the database server.","type":"integer","default":100}}}}},"quorum":{"description":"Quorum configuration for synchronous replication.","type":"object","default":{},"required":["maxSyncReplicas","minSyncReplicas"],"properties":{"maxSyncReplicas":{"description":"Maximum number of synchronous replicas allowed (must be less than total replicas).","type":"integer","default":0},"minSyncReplicas":{"description":"Minimum number of synchronous replicas required for commit.","type":"integer","default":0}}},"users":{"description":"Users configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"password":{"description":"Password for the user.","type":"string"},"replication":{"description":"Whether the user has replication privileges.","type":"boolean"}}}},"databases":{"description":"Databases configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"extensions":{"description":"List of enabled PostgreSQL extensions.","type":"array","items":{"type":"string"}},"roles":{"description":"Roles assigned to users.","type":"object","properties":{"admin":{"description":"List of users with admin privileges.","type":"array","items":{"type":"string"}},"readonly":{"description":"List of users with read-only privileges.","type":"array","items":{"type":"string"}}}}}}},"backup":{"description":"Backup configuration.","type":"object","default":{},"required":["enabled"],"properties":{"destinationPath":{"description":"Destination path for backups (e.g. s3://bucket/path/).","type":"string","default":"s3://bucket/path/to/folder/"},"enabled":{"description":"Enable regular backups.","type":"boolean","default":false},"endpointURL":{"description":"S3 endpoint URL for uploads.","type":"string","default":"http://minio-gateway-service:9000"},"retentionPolicy":{"description":"Retention policy (e.g. \"30d\").","type":"string","default":"30d"},"s3AccessKey":{"description":"Access key for S3 authentication.","type":"string","default":""},"s3SecretKey":{"description":"Secret key for S3 authentication.","type":"string","default":""},"schedule":{"description":"Cron schedule for automated backups.","type":"string","default":"0 2 * * * *"}}},"bootstrap":{"description":"Bootstrap configuration.","type":"object","default":{},"required":["enabled","oldName"],"properties":{"enabled":{"description":"Whether to restore from a backup.","type":"boolean","default":false},"oldName":{"description":"Previous cluster name before deletion.","type":"string","default":""},"recoveryTime":{"description":"Timestamp (RFC3339) for point-in-time recovery; empty means latest.","type":"string","default":""},"serverName":{"description":"Barman server name (S3 path prefix) used by the original cluster when writing backups. Set this only when the original cluster had an explicit barmanObjectStore.serverName that differed from its Kubernetes resource name.","type":"string","default":""}}}}} + {"title":"Chart Values","type":"object","properties":{"replicas":{"description":"Number of Postgres replicas.","type":"integer","default":2},"resources":{"description":"Explicit CPU and memory configuration for each PostgreSQL replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available to each replica.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume Claim size available for application data.","default":"10Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"version":{"description":"PostgreSQL major version to deploy","type":"string","default":"v18","enum":["v18","v17","v16","v15","v14","v13"]},"postgresql":{"description":"PostgreSQL server configuration.","type":"object","default":{},"properties":{"parameters":{"description":"PostgreSQL server parameters. All values must be strings (enclose numbers in quotes). See PostgreSQL documentation for available parameters. WARNING: Some parameters are managed by CloudNativePG and should NOT be overridden (archive_mode, archive_command, restore_command, primary_conninfo, etc.). Incorrect values may break backup/replication or require pod restart.","type":"object","default":{"max_connections":"100"},"additionalProperties":{"type":"string"}}}},"quorum":{"description":"Quorum configuration for synchronous replication.","type":"object","default":{},"required":["maxSyncReplicas","minSyncReplicas"],"properties":{"maxSyncReplicas":{"description":"Maximum number of synchronous replicas allowed (must be less than total replicas).","type":"integer","default":0},"minSyncReplicas":{"description":"Minimum number of synchronous replicas required for commit.","type":"integer","default":0}}},"users":{"description":"Users configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"password":{"description":"Password for the user.","type":"string"},"replication":{"description":"Whether the user has replication privileges.","type":"boolean"}}}},"databases":{"description":"Databases configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"extensions":{"description":"List of enabled PostgreSQL extensions.","type":"array","items":{"type":"string"}},"roles":{"description":"Roles assigned to users.","type":"object","properties":{"admin":{"description":"List of users with admin privileges.","type":"array","items":{"type":"string"}},"readonly":{"description":"List of users with read-only privileges.","type":"array","items":{"type":"string"}}}}}}},"backup":{"description":"Backup configuration.","type":"object","default":{},"required":["enabled"],"properties":{"destinationPath":{"description":"Destination path for backups (e.g. s3://bucket/path/).","type":"string","default":"s3://bucket/path/to/folder/"},"enabled":{"description":"Enable regular backups.","type":"boolean","default":false},"endpointURL":{"description":"S3 endpoint URL for uploads.","type":"string","default":"http://minio-gateway-service:9000"},"retentionPolicy":{"description":"Retention policy (e.g. \"30d\").","type":"string","default":"30d"},"s3AccessKey":{"description":"Access key for S3 authentication.","type":"string","default":""},"s3SecretKey":{"description":"Secret key for S3 authentication.","type":"string","default":""},"schedule":{"description":"Cron schedule for automated backups.","type":"string","default":"0 2 * * * *"}}},"bootstrap":{"description":"Bootstrap configuration.","type":"object","default":{},"required":["enabled","oldName"],"properties":{"enabled":{"description":"Whether to restore from a backup.","type":"boolean","default":false},"oldName":{"description":"Previous cluster name before deletion.","type":"string","default":""},"recoveryTime":{"description":"Timestamp (RFC3339) for point-in-time recovery; empty means latest.","type":"string","default":""},"serverName":{"description":"Barman server name (S3 path prefix) used by the original cluster when writing backups. Set this only when the original cluster had an explicit barmanObjectStore.serverName that differed from its Kubernetes resource name.","type":"string","default":""}}}}} release: prefix: postgres- labels: