From 3da117d560c64b8dbb61f68f5eb7d3098bab2184 Mon Sep 17 00:00:00 2001 From: karo Date: Sun, 3 May 2026 18:12:47 +0200 Subject: [PATCH] Add pg_ident.conf passthrough field to Patroni spec Mirrors the existing pg_hba passthrough (#361). Patroni supports pg_ident natively under postgresql.pg_ident; this exposes it through the Postgresql CRD so users can configure ident maps from the manifest. Useful for any auth method that uses pg_hba `map=...`: cert auth, GSS, PAM, peer. --- docs/reference/cluster_manifest.md | 6 +++++ manifests/complete-postgres-manifest.yaml | 3 +++ manifests/postgresql.crd.yaml | 4 +++ pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml | 4 +++ pkg/apis/acid.zalan.do/v1/postgresql_type.go | 1 + .../acid.zalan.do/v1/zz_generated.deepcopy.go | 5 ++++ pkg/cluster/k8sres.go | 27 +++++++++++-------- pkg/cluster/k8sres_test.go | 3 ++- pkg/cluster/sync.go | 3 +++ 9 files changed, 44 insertions(+), 12 deletions(-) diff --git a/docs/reference/cluster_manifest.md b/docs/reference/cluster_manifest.md index 7dddd5951..3f9aa28fa 100644 --- a/docs/reference/cluster_manifest.md +++ b/docs/reference/cluster_manifest.md @@ -371,6 +371,12 @@ explanation of `ttl` and `loop_wait` parameters. custom `pg_hba` should include the pam line to avoid breaking pam authentication. Optional. +* **pg_ident** + list of custom `pg_ident` lines defining user name maps for external + authentication methods (e.g. cert, gss, peer). Each line is of the form + `mapname system-username pg-username`. Patroni manages `pg_ident.conf` + and reloads PostgreSQL when this list changes. Optional. + * **ttl** Patroni `ttl` parameter value, optional. The default is set by the Spilo Docker image. Optional. diff --git a/manifests/complete-postgres-manifest.yaml b/manifests/complete-postgres-manifest.yaml index eff115cab..01afa8876 100644 --- a/manifests/complete-postgres-manifest.yaml +++ b/manifests/complete-postgres-manifest.yaml @@ -137,6 +137,9 @@ spec: # pg_hba: # - hostssl all all 0.0.0.0/0 md5 # - host all all 0.0.0.0/0 md5 +# pg_ident: +# - mymap /^(.*)@example\.com$ \1 +# - mymap admin@example.com postgres # slots: # permanent_physical_1: # type: physical diff --git a/manifests/postgresql.crd.yaml b/manifests/postgresql.crd.yaml index f3dcbab59..44cf30f32 100644 --- a/manifests/postgresql.crd.yaml +++ b/manifests/postgresql.crd.yaml @@ -2418,6 +2418,10 @@ spec: items: type: string type: array + pg_ident: + items: + type: string + type: array retry_timeout: format: int32 type: integer diff --git a/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml b/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml index f3dcbab59..44cf30f32 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml +++ b/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml @@ -2418,6 +2418,10 @@ spec: items: type: string type: array + pg_ident: + items: + type: string + type: array retry_timeout: format: int32 type: integer diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index 2ea8c6ba8..44e34d20b 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -241,6 +241,7 @@ type Resources struct { type Patroni struct { InitDB map[string]string `json:"initdb,omitempty"` PgHba []string `json:"pg_hba,omitempty"` + PgIdent []string `json:"pg_ident,omitempty"` TTL uint32 `json:"ttl,omitempty"` LoopWait uint32 `json:"loop_wait,omitempty"` RetryTimeout uint32 `json:"retry_timeout,omitempty"` diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 9f5e93976..d696fc370 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -675,6 +675,11 @@ func (in *Patroni) DeepCopyInto(out *Patroni) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.PgIdent != nil { + in, out := &in.PgIdent, &out.PgIdent + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.Slots != nil { in, out := &in.Slots, &out.Slots *out = make(map[string]map[string]string, len(*in)) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 7ed3cd4d6..430a0c807 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -35,15 +35,16 @@ import ( ) const ( - pgBinariesLocationTemplate = "/usr/lib/postgresql/%v/bin" - patroniPGBinariesParameterName = "bin_dir" - patroniPGHBAConfParameterName = "pg_hba" - localHost = "127.0.0.1/32" - scalyrSidecarName = "scalyr-sidecar" - logicalBackupContainerName = "logical-backup" - connectionPoolerContainer = "connection-pooler" - pgPort = 5432 - operatorPort = 8080 + pgBinariesLocationTemplate = "/usr/lib/postgresql/%v/bin" + patroniPGBinariesParameterName = "bin_dir" + patroniPGHBAConfParameterName = "pg_hba" + patroniPGIdentConfParameterName = "pg_ident" + localHost = "127.0.0.1/32" + scalyrSidecarName = "scalyr-sidecar" + logicalBackupContainerName = "logical-backup" + connectionPoolerContainer = "connection-pooler" + pgPort = 5432 + operatorPort = 8080 ) type patroniDCS struct { @@ -469,12 +470,16 @@ PatroniInitDBParams: config.Bootstrap.DCS.PGBootstrapConfiguration[constants.PatroniPGParametersParameterName] = bootstrap } } - // Patroni gives us a choice of writing pg_hba.conf to either the bootstrap section or to the local postgresql one. - // We choose the local one, because we need Patroni to change pg_hba.conf in PostgreSQL after the user changes the + + // Patroni gives us a choice of writing pg_hba.conf and pg_ident.conf to either the bootstrap section or to the local postgresql one. + // We choose the local one, because we need Patroni to change them in PostgreSQL after the user changes the // relevant section in the manifest. if len(patroni.PgHba) > 0 { config.PgLocalConfiguration[patroniPGHBAConfParameterName] = patroni.PgHba } + if len(patroni.PgIdent) > 0 { + config.PgLocalConfiguration[patroniPGIdentConfParameterName] = patroni.PgIdent + } res, err := json.Marshal(config) return string(res), err diff --git a/pkg/cluster/k8sres_test.go b/pkg/cluster/k8sres_test.go index 6cb0e085c..00da54458 100644 --- a/pkg/cluster/k8sres_test.go +++ b/pkg/cluster/k8sres_test.go @@ -90,6 +90,7 @@ func TestGenerateSpiloJSONConfiguration(t *testing.T) { "data-checksums": "true", }, PgHba: []string{"hostssl all all 0.0.0.0/0 scram-sha-256", "host all all 0.0.0.0/0 scram-sha-256"}, + PgIdent: []string{"mymap user1 dbuser1", "mymap user2 dbuser2"}, TTL: 30, LoopWait: 10, RetryTimeout: 10, @@ -101,7 +102,7 @@ func TestGenerateSpiloJSONConfiguration(t *testing.T) { FailsafeMode: util.True(), }, opConfig: &config.Config{}, - result: `{"postgresql":{"bin_dir":"/usr/lib/postgresql/18/bin","pg_hba":["hostssl all all 0.0.0.0/0 scram-sha-256","host all all 0.0.0.0/0 scram-sha-256"]},"bootstrap":{"initdb":[{"auth-host":"scram-sha-256"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"dcs":{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"synchronous_mode":true,"synchronous_mode_strict":true,"synchronous_node_count":1,"slots":{"permanent_logical_1":{"database":"foo","plugin":"pgoutput","type":"logical"}},"failsafe_mode":true}}}`, + result: `{"postgresql":{"bin_dir":"/usr/lib/postgresql/18/bin","pg_hba":["hostssl all all 0.0.0.0/0 scram-sha-256","host all all 0.0.0.0/0 scram-sha-256"],"pg_ident":["mymap user1 dbuser1","mymap user2 dbuser2"]},"bootstrap":{"initdb":[{"auth-host":"scram-sha-256"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"dcs":{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"synchronous_mode":true,"synchronous_mode_strict":true,"synchronous_node_count":1,"slots":{"permanent_logical_1":{"database":"foo","plugin":"pgoutput","type":"logical"}},"failsafe_mode":true}}}`, }, { subtest: "Patroni failsafe_mode configured globally", diff --git a/pkg/cluster/sync.go b/pkg/cluster/sync.go index 664c67b7e..8929d782e 100644 --- a/pkg/cluster/sync.go +++ b/pkg/cluster/sync.go @@ -921,6 +921,9 @@ func (c *Cluster) checkAndSetGlobalPostgreSQLConfiguration(pod *v1.Pod, effectiv if desiredPatroniConfig.PgHba != nil && !reflect.DeepEqual(desiredPatroniConfig.PgHba, effectivePatroniConfig.PgHba) { configToSet["pg_hba"] = desiredPatroniConfig.PgHba } + if desiredPatroniConfig.PgIdent != nil && !reflect.DeepEqual(desiredPatroniConfig.PgIdent, effectivePatroniConfig.PgIdent) { + configToSet["pg_ident"] = desiredPatroniConfig.PgIdent + } if desiredPatroniConfig.RetryTimeout > 0 && desiredPatroniConfig.RetryTimeout != effectivePatroniConfig.RetryTimeout { configToSet["retry_timeout"] = desiredPatroniConfig.RetryTimeout }