Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ $(GENERATED_CRDS): $(GENERATED)
@$(SED) -i -e 's/listKind: PostgresqlList/listKind: postgresqlList/' manifests/postgresql.crd.yaml
@hack/adjust_postgresql_crd.sh
@mv manifests/acid.zalan.do_operatorconfigurations.yaml manifests/operatorconfiguration.crd.yaml
@hack/adjust_operatorconfiguration_crd.sh
@mv manifests/acid.zalan.do_postgresteams.yaml manifests/postgresteam.crd.yaml
@cp manifests/postgresql.crd.yaml pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml
@cp manifests/postgresql.crd.yaml charts/postgres-operator/crds/postgresqls.yaml
Expand Down
9 changes: 7 additions & 2 deletions charts/postgres-operator/crds/operatorconfigurations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,15 @@ spec:
format: int32
minimum: 1
type: integer
connection_pooler_pod_security_context:
type: object
x-kubernetes-preserve-unknown-fields: true
connection_pooler_schema:
default: pooler
type: string
connection_pooler_security_context:
type: object
x-kubernetes-preserve-unknown-fields: true
connection_pooler_user:
default: pooler
type: string
Expand Down Expand Up @@ -707,8 +713,7 @@ spec:
type: object
maintenance_windows:
items:
pattern: ^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\
*$
pattern: '^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ *$'
type: string
type: array
major_version_upgrade:
Expand Down
18 changes: 18 additions & 0 deletions charts/postgres-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,24 @@ configConnectionPooler:
connection_pooler_default_memory_request: 100Mi
connection_pooler_default_cpu_limit: "1"
connection_pooler_default_memory_limit: 100Mi
# Override the pooler pod- and container-level securityContext. Useful for hardened
# images whose pgbouncer user/UID differs from the operator default (100/101) and which
# own /etc/pgbouncer, or to satisfy restricted Pod Security Standards. When unset the
# operator keeps its defaults (pod RunAsUser/RunAsGroup 100/101, container
# allowPrivilegeEscalation=false).
# connection_pooler_pod_security_context:
# runAsUser: 100
# runAsGroup: 101
# runAsNonRoot: true
# fsGroup: 101
# seccompProfile:
# type: RuntimeDefault
# connection_pooler_security_context:
# allowPrivilegeEscalation: false
# readOnlyRootFilesystem: true
# capabilities:
# drop:
# - ALL

configPatroni:
# enable Patroni DCS failsafe_mode feature
Expand Down
45 changes: 45 additions & 0 deletions docs/reference/operator_parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -1105,3 +1105,48 @@ operator being able to provide some reasonable defaults.
**connection_pooler_default_cpu_limit**
**connection_pooler_default_memory_limit**
Default resource configuration for connection pooler deployment.

* **connection_pooler_pod_security_context**
Pod-level `securityContext` applied to the connection pooler deployment. The
value is a standard Kubernetes
[PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#podsecuritycontext-v1-core)
object. Only fields you set are overridden; unset fields keep the operator
defaults (`runAsUser: 100`, `runAsGroup: 101`, and the cluster's `fsGroup`).
Settable **only** via the `OperatorConfiguration` CRD (not the ConfigMap).
Defaults to not set.

* **connection_pooler_security_context**
Container-level `securityContext` applied to the pgbouncer container. The
value is a standard Kubernetes
[SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#securitycontext-v1-core)
object. Only fields you set are overridden; when `allowPrivilegeEscalation`
is unset it defaults to `false`. Settable **only** via the
`OperatorConfiguration` CRD (not the ConfigMap). Defaults to not set.

These two parameters are useful when running a hardened pooler image whose
pgbouncer user/UID differs from the operator default (100/101), or to satisfy
restricted [Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/).
Because they are object-valued they cannot be expressed in the flat operator
ConfigMap; configure them through the `OperatorConfiguration` CRD:

```yaml
apiVersion: "acid.zalan.do/v1"
kind: OperatorConfiguration
metadata:
name: postgresql-operator-default-configuration
configuration:
connection_pooler:
connection_pooler_pod_security_context:
runAsUser: 100
runAsGroup: 101
runAsNonRoot: true
fsGroup: 101
seccompProfile:
type: RuntimeDefault
connection_pooler_security_context:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
```
22 changes: 22 additions & 0 deletions hack/adjust_operatorconfiguration_crd.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash

# Hack to adjust the generated operatorconfiguration CRD YAML file and add
# missing field settings which can not be expressed via kubebuilder markers.
#
# Injections:
#
# * type: string and pattern for the maintenance_windows items.
# The MaintenanceWindow type marshals to/from a string (see marshal.go) but
# the field is declared `+kubebuilder:validation:Schemaless` /
# `+kubebuilder:validation:Type=array`, so controller-gen emits a bare
# `type: array` without the required `items` schema. A structural CRD must
# specify `items` for every array, so the generated CRD is rejected with:
# spec.validation.openAPIV3Schema.properties[configuration]
# .properties[maintenance_windows].items: Required value: must be specified

file="${1:-"manifests/operatorconfiguration.crd.yaml"}"

sed -i '/^[[:space:]]*maintenance_windows:$/{
# Capture the indentation
s/^\([[:space:]]*\)maintenance_windows:$/\1maintenance_windows:\n\1 items:\n\1 pattern: '\''^\\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))-((2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))\\ *$'\''\n\1 type: string/
}' "$file"
3 changes: 1 addition & 2 deletions manifests/operatorconfiguration.crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -707,8 +707,7 @@ spec:
type: object
maintenance_windows:
items:
pattern: ^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\
*$
pattern: '^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ *$'
type: string
type: array
major_version_upgrade:
Expand Down
8 changes: 8 additions & 0 deletions pkg/apis/acid.zalan.do/v1/operator_configuration_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,14 @@ type ConnectionPoolerConfiguration struct {
DefaultCPULimit string `json:"connection_pooler_default_cpu_limit,omitempty"`
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
DefaultMemoryLimit string `json:"connection_pooler_default_memory_limit,omitempty"`
// +kubebuilder:validation:XPreserveUnknownFields
// +kubebuilder:validation:Type=object
// +kubebuilder:validation:Schemaless
PodSecurityContext *v1.PodSecurityContext `json:"connection_pooler_pod_security_context,omitempty"`
// +kubebuilder:validation:XPreserveUnknownFields
// +kubebuilder:validation:Type=object
// +kubebuilder:validation:Schemaless
SecurityContext *v1.SecurityContext `json:"connection_pooler_security_context,omitempty"`
}

// OperatorLogicalBackupConfiguration defines configuration for logical backup
Expand Down
3 changes: 1 addition & 2 deletions pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -707,8 +707,7 @@ spec:
type: object
maintenance_windows:
items:
pattern: ^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\
*$
pattern: '^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ *$'
type: string
type: array
major_version_upgrade:
Expand Down
10 changes: 10 additions & 0 deletions pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 48 additions & 17 deletions pkg/cluster/connection_pooler.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,51 @@ import (
var poolerRunAsUser = int64(100)
var poolerRunAsGroup = int64(101)

// generateConnectionPoolerPodSecurityContext returns the pod-level securityContext for the
// connection pooler. When the operator configuration provides one it is used as the base
// (deep-copied); otherwise an empty context is used. The historical defaults (RunAsUser/RunAsGroup
// 100/101) and the spilo FSGroup are applied only for fields the configuration leaves unset, so
// behavior is unchanged when no override is configured.
func (c *Cluster) generateConnectionPoolerPodSecurityContext(spec *acidv1.PostgresSpec) *v1.PodSecurityContext {
var securityContext v1.PodSecurityContext
if c.OpConfig.ConnectionPooler.PodSecurityContext != nil {
securityContext = *c.OpConfig.ConnectionPooler.PodSecurityContext.DeepCopy()
}

if securityContext.RunAsUser == nil {
securityContext.RunAsUser = &poolerRunAsUser
}
if securityContext.RunAsGroup == nil {
securityContext.RunAsGroup = &poolerRunAsGroup
}

if securityContext.FSGroup == nil {
effectiveFSGroup := c.OpConfig.Resources.SpiloFSGroup
if spec.SpiloFSGroup != nil {
effectiveFSGroup = spec.SpiloFSGroup
}
if effectiveFSGroup != nil {
securityContext.FSGroup = effectiveFSGroup
}
}

return &securityContext
}

// generateConnectionPoolerContainerSecurityContext returns the container-level securityContext for
// the pooler. A configured context is used as the base (deep-copied); AllowPrivilegeEscalation
// defaults to false when left unset, preserving prior behavior.
func (c *Cluster) generateConnectionPoolerContainerSecurityContext() *v1.SecurityContext {
var securityContext v1.SecurityContext
if c.OpConfig.ConnectionPooler.SecurityContext != nil {
securityContext = *c.OpConfig.ConnectionPooler.SecurityContext.DeepCopy()
}
if securityContext.AllowPrivilegeEscalation == nil {
securityContext.AllowPrivilegeEscalation = util.False()
}
return &securityContext
}

// ConnectionPoolerObjects K8s objects that are belong to connection pooler
type ConnectionPoolerObjects struct {
AuthSecret *v1.Secret
Expand Down Expand Up @@ -383,9 +428,7 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) (
},
},
},
SecurityContext: &v1.SecurityContext{
AllowPrivilegeEscalation: util.False(),
},
SecurityContext: c.generateConnectionPoolerContainerSecurityContext(),
}

var poolerVolumes []v1.Volume
Expand Down Expand Up @@ -444,19 +487,7 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) (
poolerContainer.Env = envVars
poolerContainer.VolumeMounts = volumeMounts
tolerationsSpec := tolerations(&spec.Tolerations, c.OpConfig.PodToleration)
securityContext := v1.PodSecurityContext{}

// determine the User, Group and FSGroup for the pooler pod
securityContext.RunAsUser = &poolerRunAsUser
securityContext.RunAsGroup = &poolerRunAsGroup

effectiveFSGroup := c.OpConfig.Resources.SpiloFSGroup
if spec.SpiloFSGroup != nil {
effectiveFSGroup = spec.SpiloFSGroup
}
if effectiveFSGroup != nil {
securityContext.FSGroup = effectiveFSGroup
}
securityContext := c.generateConnectionPoolerPodSecurityContext(spec)

podTemplate := &v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -469,7 +500,7 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) (
Containers: []v1.Container{poolerContainer},
Tolerations: tolerationsSpec,
Volumes: poolerVolumes,
SecurityContext: &securityContext,
SecurityContext: securityContext,
ServiceAccountName: c.OpConfig.PodServiceAccountName,
},
}
Expand Down
Loading