feat(redis,postgres): certify the per-engine external hostname (#4627)

<!-- Thank you for making a contribution! Here are some tips for you:
- Use Conventional Commits for the PR title: `type(scope): description`
- Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
- Scopes are not an exhaustive list — pick the most specific scope for
the change and extend the list when a genuinely new area appears.
Examples:
- System components: dashboard, platform, operator, cilium, kube-ovn,
linstor, fluxcd, cluster-api
- Managed apps: postgres, mariadb, redis, kafka, clickhouse,
virtual-machine, kubernetes
- Development and maintenance: api, hack, tests, ci, docs, maintenance
- Breaking changes: append `!` after type/scope (`feat(api)!: ...`) or
add a `BREAKING CHANGE:` footer
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `kind/backport` if it's a bugfix that needs to be
backported to a previous version.
-->

## What this PR does

Redis and postgres server certificates now carry the per-engine external
name `<release>.<engine>.<apex>` (for example
`redis-foo.redis.example.org`) next to the flat `<release>.<apex>` they
already had. Both names appear under the same condition as before: TLS
on and `external: true`, with a tenant apex present.

A TLS-passthrough listener on the tenant Gateway forwards the handshake
untouched, so the certificate the client checks is the engine's own. The
design in #2815 routes databases by SNI under `*.redis.<apex>` and
`*.postgres.<apex>`, and without the per-engine name in the SAN list a
verifying client would fail the hostname check. Issuing both names now
means a later move between the two hostname schemes reissues no
certificate and keeps clients on the same trust anchor. This is the
certificate step of #2815. Nothing renders a route yet.

The SAN list changes, so an existing external TLS release gets its
server certificate reissued once on upgrade, from the same CA. For redis
the operator then rolls the Redis and Sentinel pods one at a time, the
same way it handles a renewal.

The READMEs say who can reach the new name through a passthrough
listener. For postgres that is only clients using direct TLS negotiation
(server 17+, `sslnegotiation=direct`), because by default a plaintext
SSLRequest comes before the handshake. `redis-cli` needs `--sni`.

MongoDB is not in this PR. PSMDB issues the certificate itself and its
CRD has no field for extra SANs, so adding the mongos name means the
chart taking over the certificate. That needs its own change.

Covered by helm-unittest: the name is present with TLS and external on,
and absent with external off, with TLS off, and in a tenant with no
apex.

### Screenshots

<!-- REQUIRED for UI changes: attach screenshots or screen recordings
demonstrating
the visual impact of your changes. PRs with UI changes without
screenshots will not be merged. -->

### Downstream repositories

<!--
Cozystack is upstream for repositories that are NOT kept in sync with it
automatically, and nothing in CI compares the two sides, so a change
here can break them silently.

Which change forces what, and which file to touch, is the trigger map in
docs/agents/contributing.md under "Downstream Repositories". Walk it
before you tick anything.

IF YOU ARE AN AI AGENT OR AN LLM, THIS IS PART OF THE TASK, NOT A FORM
TO FILL IN:

1. Walk the trigger map against the actual diff of this PR, file by
file. Do not decide from the PR title or from your memory of what you
changed.
2. For every repository you tick, open the follow-up PR there and link
it on the same line. A ticked box with no link claims work that does not
exist, which is worse than an empty box. Search that repository for an
open PR or issue covering it first, and link that instead of filing a
duplicate.
3. When the follow-up is out of scope, or needs a decision that is not
yours to make, open an issue in that repository instead, link that, and
say so in the PR body. Do not push a speculative PR into another
repository just to fill a line here.
4. Do not tick "No downstream repository is affected" to make the
checklist go away. If you are unsure, leave every box empty and say so
in the PR body, so a human decides.
-->

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same `type(scope):` prefix as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
feat(redis,postgres): server certificates of external redis and postgres releases also carry the `<release>.<engine>.<apex>` name that the tenant Gateway's TLS-passthrough listeners are designed to route by. Existing external TLS releases get their server certificate reissued once on upgrade, from the same CA.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* PostgreSQL TLS certificates now cover both the tenant hostname and the
per-engine `postgres` hostname for external connections.
* Redis TLS certificates now also cover the per-engine `redis` hostname
when external services are enabled.

* **Documentation**
* Clarified connection requirements for per-engine hostnames: PostgreSQL
routing requires direct TLS negotiation, PostgreSQL 17 or later, and a
compatible client; Redis clients must specify SNI.
* Clarified that the hostnames may need to be mapped manually to the
LoadBalancer address.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
IvanHunters authored and GitHub committed 2026-10-07 10:56:34 +03:00
commit 3cce629535
10 files changed
+46 -17

No files matched your search

+1 -1
View File
@@ -213,7 +213,7 @@ type S3CredentialsSecret struct {
}
type TLS struct {
// Tri-state switch controlling whether the chart injects the external hostname into the operator-managed CNPG cert via spec.certificates.serverAltDNSNames. When omitted, the chart injects the SAN if `external: true` and skips it otherwise. Set explicitly to `true` to inject regardless of `external` (no-op when `external: false` since there is no external hostname to add). Set to `false` to skip injection. Note that CNPG keeps its built-in TLS on the wire regardless of this flag — this toggle only controls the chart-side SAN injection; to disable PostgreSQL TLS entirely set `postgresql.parameters.ssl = "off"` at the CNPG layer.
// Tri-state switch controlling whether the chart injects the external hostnames into the operator-managed CNPG cert via spec.certificates.serverAltDNSNames. When omitted, the chart injects them if `external: true` and skips them otherwise. Set explicitly to `true` to inject regardless of `external` (no-op when `external: false` since there is no external hostname to add). Set to `false` to skip injection. Note that CNPG keeps its built-in TLS on the wire regardless of this flag — this toggle only controls the chart-side SAN injection; to disable PostgreSQL TLS entirely set `postgresql.parameters.ssl = "off"` at the CNPG layer.
Enabled *bool `json:"enabled,omitempty"`
}
+4 -4
View File
@@ -166,14 +166,14 @@ See:
CNPG manages the cert chain end-to-end. The operator auto-generates a self-signed CA, signs server, client, and replication leaf certs from it, and rotates them as needed. The chart does not render any cert-manager `Issuer`/`Certificate` objects — that path is mutually exclusive with the operator-managed chain on the CNPG admission webhook.
What the chart contributes: when TLS is on and `external: true`, the chart sets `spec.certificates.serverAltDNSNames` on the CNPG Cluster CR to inject the external hostname `<release>.<_namespace.host>` into the auto-generated server certificate's SAN list. CNPG's default SAN coverage already includes the three built-in ClusterIP services (`-rw`, `-r`, `-ro`) across the four DNS forms (`<svc>`, `<svc>.<ns>`, `<svc>.<ns>.svc`, `<svc>.<ns>.svc.<cluster-domain>`); only the external hostname needs to be added.
What the chart contributes: when TLS is on and `external: true`, the chart sets `spec.certificates.serverAltDNSNames` on the CNPG Cluster CR to inject the external hostnames `<release>.<_namespace.host>` and `<release>.postgres.<_namespace.host>` into the auto-generated server certificate's SAN list. The second is the per-engine name meant for SNI routing through a TLS-passthrough listener on the tenant Gateway. The certificate covers that name for any client, but routing through such a listener works only when the TLS handshake is the first thing on the wire. With the default negotiation a PostgreSQL client first sends a plaintext SSLRequest, so the listener can route only clients using direct TLS negotiation: PostgreSQL 17 or later on the server and a client connecting with `sslnegotiation=direct`, such as libpq 17 or pgx 5.7.5. CNPG's default SAN coverage already includes the three built-in ClusterIP services (`-rw`, `-r`, `-ro`) across the four DNS forms (`<svc>`, `<svc>.<ns>`, `<svc>.<ns>.svc`, `<svc>.<ns>.svc.<cluster-domain>`); only the external hostnames need to be added.
The tri-state `tls.enabled` controls whether the chart injects `serverAltDNSNames`:
- `tls.enabled: null` (the default) — TLS posture inherits from `external`. When `external: true`, the chart injects the external hostname into the operator-managed cert.
- `tls.enabled: null` (the default) — TLS posture inherits from `external`. When `external: true`, the chart injects the external hostnames into the operator-managed cert.
- `tls.enabled: true` with `external: true` — same effect as the default.
- `tls.enabled: true` with `external: false` — no `serverAltDNSNames` injection is needed (there is no external hostname to add); CNPG's auto-generated cert covers internal services.
- `tls.enabled: false` — the chart skips `serverAltDNSNames` injection. **Note:** CNPG keeps its built-in TLS on the wire regardless of this flag; this toggle only controls whether the external hostname is added to the cert. To force PostgreSQL to drop TLS entirely you would need to set `postgresql.parameters.ssl = "off"` at the CNPG layer, which is out of scope for this flag.
- `tls.enabled: false` — the chart skips `serverAltDNSNames` injection. **Note:** CNPG keeps its built-in TLS on the wire regardless of this flag; this toggle only controls whether the external hostnames are added to the cert. To force PostgreSQL to drop TLS entirely you would need to set `postgresql.parameters.ssl = "off"` at the CNPG layer, which is out of scope for this flag.
**Retrieving the CA bundle** for client verification:
@@ -221,7 +221,7 @@ For `sslmode=verify-full` to work, the CA bundle retrieved above must be saved t
| Name | Description | Type | Value |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------ |
| `tls` | TLS configuration for server connections. | `object` | `{}` |
| `tls.enabled` | Tri-state switch controlling whether the chart injects the external hostname into the operator-managed CNPG cert via spec.certificates.serverAltDNSNames. When omitted, the chart injects the SAN if `external: true` and skips it otherwise. Set explicitly to `true` to inject regardless of `external` (no-op when `external: false` since there is no external hostname to add). Set to `false` to skip injection. Note that CNPG keeps its built-in TLS on the wire regardless of this flag — this toggle only controls the chart-side SAN injection; to disable PostgreSQL TLS entirely set `postgresql.parameters.ssl = "off"` at the CNPG layer. | `*bool` | `null` |
| `tls.enabled` | Tri-state switch controlling whether the chart injects the external hostnames into the operator-managed CNPG cert via spec.certificates.serverAltDNSNames. When omitted, the chart injects them if `external: true` and skips them otherwise. Set explicitly to `true` to inject regardless of `external` (no-op when `external: false` since there is no external hostname to add). Set to `false` to skip injection. Note that CNPG keeps its built-in TLS on the wire regardless of this flag — this toggle only controls the chart-side SAN injection; to disable PostgreSQL TLS entirely set `postgresql.parameters.ssl = "off"` at the CNPG layer. | `*bool` | `null` |
### Application-specific parameters
+11 -3
View File
@@ -56,8 +56,8 @@ spec:
# Operator-managed TLS. CNPG auto-generates its own self-signed CA and signs
# server + client + replication certs from it. The operator's default SAN list
# already covers the three built-in services (rw/r/ro) in four DNS forms each
# (bare, .ns, .ns.svc, .ns.svc.<cluster-domain>); only the external hostname
# needs to be appended via serverAltDNSNames.
# (bare, .ns, .ns.svc, .ns.svc.<cluster-domain>); only the external hostnames
# need to be appended via serverAltDNSNames.
#
# serverAltDNSNames is mutually exclusive with serverTLSSecret per the CNPG
# admission webhook, which is why the chart no longer renders a cert-manager
@@ -69,9 +69,17 @@ spec:
# core.cozystack.io tenantsecrets API. It is neither <release>-credentials,
# which is chart-rendered and holds user passwords only, nor CNPG's
# <release>-ca, which holds the CA private key next to ca.crt.
#
# The second name is the per-engine one, meant for SNI routing through a
# TLS-passthrough listener on the tenant Gateway (*.postgres.<apex>). Both
# are issued so that a later move between the two hostname schemes reissues
# no certificate and changes no client's trust anchor; clients still change
# the host they dial.
{{- $apex := include "cozy-lib.ns-host-required" . }}
certificates:
serverAltDNSNames:
- {{ printf "%s.%s" .Release.Name (include "cozy-lib.ns-host-required" .) }}
- {{ printf "%s.%s" .Release.Name $apex }}
- {{ printf "%s.postgres.%s" .Release.Name $apex }}
{{- end }}
{{/*
s3Credentials points at the operator-supplied Secret when
+11 -5
View File
@@ -12,7 +12,7 @@ suite: postgres TLS configuration
# In TLS-on mode the chart sets spec.certificates with serverAltDNSNames so the
# operator's own CA signs server certs. CNPG auto-derives SANs for the three
# built-in services (rw/r/ro) in four DNS forms each; the chart only appends
# the external hostname when external=true.
# the external hostnames when external=true.
release:
name: pg-test
@@ -64,7 +64,7 @@ tests:
- isNull:
path: spec.certificates
- it: Cluster has spec.certificates.serverAltDNSNames with external hostname when tls auto-on via external true
- it: Cluster has spec.certificates.serverAltDNSNames with external hostnames when tls auto-on via external true
set:
external: true
_namespace:
@@ -82,11 +82,14 @@ tests:
- contains:
path: spec.certificates.serverAltDNSNames
content: pg-test.example.cozy.local
- contains:
path: spec.certificates.serverAltDNSNames
content: pg-test.postgres.example.cozy.local
- lengthEqual:
path: spec.certificates.serverAltDNSNames
count: 1
count: 2
- it: Cluster has spec.certificates.serverAltDNSNames with external hostname when tls explicitly enabled and external true
- it: Cluster has spec.certificates.serverAltDNSNames with external hostnames when tls explicitly enabled and external true
set:
tls:
enabled: true
@@ -100,7 +103,10 @@ tests:
- contains:
path: spec.certificates.serverAltDNSNames
content: pg-test.example.cozy.local
- contains:
path: spec.certificates.serverAltDNSNames
content: pg-test.postgres.example.cozy.local
- lengthEqual:
path: spec.certificates.serverAltDNSNames
count: 1
count: 2
+1 -1
View File
@@ -155,7 +155,7 @@
"default": {},
"properties": {
"enabled": {
"description": "Tri-state switch controlling whether the chart injects the external hostname into the operator-managed CNPG cert via spec.certificates.serverAltDNSNames. When omitted, the chart injects the SAN if `external: true` and skips it otherwise. Set explicitly to `true` to inject regardless of `external` (no-op when `external: false` since there is no external hostname to add). Set to `false` to skip injection. Note that CNPG keeps its built-in TLS on the wire regardless of this flag — this toggle only controls the chart-side SAN injection; to disable PostgreSQL TLS entirely set `postgresql.parameters.ssl = \"off\"` at the CNPG layer.",
"description": "Tri-state switch controlling whether the chart injects the external hostnames into the operator-managed CNPG cert via spec.certificates.serverAltDNSNames. When omitted, the chart injects them if `external: true` and skips them otherwise. Set explicitly to `true` to inject regardless of `external` (no-op when `external: false` since there is no external hostname to add). Set to `false` to skip injection. Note that CNPG keeps its built-in TLS on the wire regardless of this flag — this toggle only controls the chart-side SAN injection; to disable PostgreSQL TLS entirely set `postgresql.parameters.ssl = \"off\"` at the CNPG layer.",
"type": "boolean"
}
}
+1 -1
View File
@@ -100,7 +100,7 @@ flavor: postgresql
##
## @typedef {struct} TLS - TLS configuration for PostgreSQL server connections.
## @field {*bool} enabled - Tri-state switch controlling whether the chart injects the external hostname into the operator-managed CNPG cert via spec.certificates.serverAltDNSNames. When omitted, the chart injects the SAN if `external: true` and skips it otherwise. Set explicitly to `true` to inject regardless of `external` (no-op when `external: false` since there is no external hostname to add). Set to `false` to skip injection. Note that CNPG keeps its built-in TLS on the wire regardless of this flag — this toggle only controls the chart-side SAN injection; to disable PostgreSQL TLS entirely set `postgresql.parameters.ssl = "off"` at the CNPG layer.
## @field {*bool} enabled - Tri-state switch controlling whether the chart injects the external hostnames into the operator-managed CNPG cert via spec.certificates.serverAltDNSNames. When omitted, the chart injects them if `external: true` and skips them otherwise. Set explicitly to `true` to inject regardless of `external` (no-op when `external: false` since there is no external hostname to add). Set to `false` to skip injection. Note that CNPG keeps its built-in TLS on the wire regardless of this flag — this toggle only controls the chart-side SAN injection; to disable PostgreSQL TLS entirely set `postgresql.parameters.ssl = "off"` at the CNPG layer.
## @param {TLS} tls - TLS configuration for server connections.
tls: {}
+1 -1
View File
@@ -90,7 +90,7 @@ It is reached through `tenantsecrets` rather than by reading a Secret directly,
`<host>` has to be a name the certificate covers. In-cluster that is any of the `rfr-`, `rfrm-`, `rfrs-` and `rfs-` service names, and `<release>-external-lb` when `external` is on; all of them resolve normally.
From outside the cluster the only covered name is `<release>.<tenant-host>`, and the chart does not publish DNS for it: the external Service is a plain LoadBalancer with no `external-dns` annotation, so nothing points that name at the LoadBalancer address. Connecting to the LoadBalancer IP instead fails for any client that verifies the hostname, because the only IP addresses in the certificate are the loopback ones the in-pod probes and the metrics sidecar use; `redis-cli` is not such a client, it checks the chain and not the name, so it connects to the IP and hides the mismatch. Until the name is published, an external client has to be pointed at it manually — a DNS record or a hosts entry mapping `<release>.<tenant-host>` to the LoadBalancer address.
From outside the cluster the covered names are `<release>.<tenant-host>` and `<release>.redis.<tenant-host>`, the second being the per-engine name meant for SNI routing through a TLS-passthrough listener on the tenant Gateway, which `redis-cli` reaches only when given `--sni`, since it sends no SNI by default. The chart publishes DNS for neither: the external Service is a plain LoadBalancer with no `external-dns` annotation, so nothing points either name at the LoadBalancer address. Connecting to the LoadBalancer IP instead fails for any client that verifies the hostname, because the only IP addresses in the certificate are the loopback ones the in-pod probes and the metrics sidecar use; `redis-cli` is not such a client, it checks the chain and not the name, so it connects to the IP and hides the mismatch. Until a name is published, an external client has to be pointed at it manually — a DNS record or a hosts entry mapping either name to the LoadBalancer address.
Neither the CA and its private key (`<release>.ca-tls`) nor the server leaf and its private key (`<release>.tls`) is readable by the tenant. The first would allow minting certificates that any client trusting this release accepts; the second would allow impersonating this release's Redis endpoints. `<release>.tenant-ca` exists precisely so the certificate can be handed over without either key going with it.
@@ -111,6 +111,12 @@ spec:
{{- $extHost := index (.Values._namespace | default dict) "host" | default "" }}
{{- if $extHost }}
- {{ printf "%s.%s" .Release.Name $extHost | quote }}
{{- /* The per-engine name, meant for SNI routing through a
TLS-passthrough listener on the tenant Gateway (*.redis.<apex>).
Issued next to the flat name so that a later move between the two
hostname schemes reissues no certificate and changes no client's
trust anchor; clients still change the host they dial. */}}
- {{ printf "%s.redis.%s" .Release.Name $extHost | quote }}
{{- end }}
{{- end }}
ipAddresses:
+9
View File
@@ -338,6 +338,9 @@ tests:
- contains:
path: spec.dnsNames
content: "my-redis.example.com"
- contains:
path: spec.dnsNames
content: "my-redis.redis.example.com"
- it: "redisfailover.yaml passes TLS settings to the operator when TLS is enabled"
templates:
@@ -559,6 +562,9 @@ tests:
- notContains:
path: spec.dnsNames
content: "my-redis.example.com"
- notContains:
path: spec.dnsNames
content: "my-redis.redis.example.com"
# ------------------------------------------------------------------
# authClients propagation
@@ -688,6 +694,9 @@ tests:
- notContains:
path: spec.dnsNames
content: "my-redis."
- notContains:
path: spec.dnsNames
content: "my-redis.redis."
- it: "leaf Certificate still renders with no external SAN when no namespace map is injected"
templates:
+1 -1
View File
@@ -30,7 +30,7 @@ spec:
singular: postgres
plural: postgreses
openAPISchema: |-
{"title":"Chart Values","type":"object","properties":{"replicas":{"description":"Number of Postgres replicas. Under active autoscaling this is the resting/seed count, not the live count — KEDA drives the live count via `/scale`. Disabling autoscaling does NOT automatically bring the live count back to `replicas`: the client-side merge writes `instances` only when the rendered value changes, so a cluster KEDA grew past `replicas` stays there with no autoscaler left to reduce it — lower `replicas` to the count you want and it rebases down (shedding the surplus replicas + PVCs). Lowering it below the live count, or disabling/dry-running while it sits below the live count, sheds in one step past the scaleDown pacing; stage it to the live count first (suspend the HelmRelease and `kubectl scale` if needed) to avoid a transient. Must stay greater than `quorum.maxSyncReplicas`.","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":"t1.micro","enum":["t1.nano","t1.micro","t1.small","t1.medium","t1.large","t1.xlarge","t1.2xlarge","t1.4xlarge","c1.nano","c1.micro","c1.small","c1.medium","c1.large","c1.xlarge","c1.2xlarge","c1.4xlarge","s1.nano","s1.micro","s1.small","s1.medium","s1.large","s1.xlarge","s1.2xlarge","s1.4xlarge","u1.nano","u1.micro","u1.small","u1.medium","u1.large","u1.xlarge","u1.2xlarge","u1.4xlarge","m1.nano","m1.micro","m1.small","m1.medium","m1.large","m1.xlarge","m1.2xlarge","m1.4xlarge","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":"","x-kubernetes-validations":[{"rule":"self == oldSelf","message":"storageClass is immutable"}],"x-cozystack-options":{"source":"storageclass"}},"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"]},"flavor":{"description":"PostgreSQL image flavor, fixed when the cluster is created. `postgresql` uses the CloudNativePG PostgreSQL image. `postgis` uses the CloudNativePG PostGIS image, which adds postgis, postgis_raster, postgis_topology, postgis_sfcgal, postgis_tiger_geocoder and address_standardizer; enable them per database in `databases.<name>.extensions`. Changing the flavor of an existing cluster is refused, and a backup restores only into a cluster of the flavor it was taken from: to change flavor, create a new cluster and move the data with a logical dump (pg_dump / pg_restore).","type":"string","default":"postgresql","enum":["postgresql","postgis"]},"tls":{"description":"TLS configuration for server connections.","type":"object","default":{},"properties":{"enabled":{"description":"Tri-state switch controlling whether the chart injects the external hostname into the operator-managed CNPG cert via spec.certificates.serverAltDNSNames. When omitted, the chart injects the SAN if `external: true` and skips it otherwise. Set explicitly to `true` to inject regardless of `external` (no-op when `external: false` since there is no external hostname to add). Set to `false` to skip injection. Note that CNPG keeps its built-in TLS on the wire regardless of this flag — this toggle only controls the chart-side SAN injection; to disable PostgreSQL TLS entirely set `postgresql.parameters.ssl = \"off\"` at the CNPG layer.","type":"boolean"}}},"postgresql":{"description":"PostgreSQL server configuration.","type":"object","default":{},"properties":{"parameters":{"description":"PostgreSQL server parameters. Values may be strings or integers; integers are coerced to strings by the template (e.g. both `max_connections: 100` and `max_connections: \"100\"` are accepted). BLOCKED (enable arbitrary code execution): archive_command, rLine truncated
{"title":"Chart Values","type":"object","properties":{"replicas":{"description":"Number of Postgres replicas. Under active autoscaling this is the resting/seed count, not the live count — KEDA drives the live count via `/scale`. Disabling autoscaling does NOT automatically bring the live count back to `replicas`: the client-side merge writes `instances` only when the rendered value changes, so a cluster KEDA grew past `replicas` stays there with no autoscaler left to reduce it — lower `replicas` to the count you want and it rebases down (shedding the surplus replicas + PVCs). Lowering it below the live count, or disabling/dry-running while it sits below the live count, sheds in one step past the scaleDown pacing; stage it to the live count first (suspend the HelmRelease and `kubectl scale` if needed) to avoid a transient. Must stay greater than `quorum.maxSyncReplicas`.","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":"t1.micro","enum":["t1.nano","t1.micro","t1.small","t1.medium","t1.large","t1.xlarge","t1.2xlarge","t1.4xlarge","c1.nano","c1.micro","c1.small","c1.medium","c1.large","c1.xlarge","c1.2xlarge","c1.4xlarge","s1.nano","s1.micro","s1.small","s1.medium","s1.large","s1.xlarge","s1.2xlarge","s1.4xlarge","u1.nano","u1.micro","u1.small","u1.medium","u1.large","u1.xlarge","u1.2xlarge","u1.4xlarge","m1.nano","m1.micro","m1.small","m1.medium","m1.large","m1.xlarge","m1.2xlarge","m1.4xlarge","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":"","x-kubernetes-validations":[{"rule":"self == oldSelf","message":"storageClass is immutable"}],"x-cozystack-options":{"source":"storageclass"}},"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"]},"flavor":{"description":"PostgreSQL image flavor, fixed when the cluster is created. `postgresql` uses the CloudNativePG PostgreSQL image. `postgis` uses the CloudNativePG PostGIS image, which adds postgis, postgis_raster, postgis_topology, postgis_sfcgal, postgis_tiger_geocoder and address_standardizer; enable them per database in `databases.<name>.extensions`. Changing the flavor of an existing cluster is refused, and a backup restores only into a cluster of the flavor it was taken from: to change flavor, create a new cluster and move the data with a logical dump (pg_dump / pg_restore).","type":"string","default":"postgresql","enum":["postgresql","postgis"]},"tls":{"description":"TLS configuration for server connections.","type":"object","default":{},"properties":{"enabled":{"description":"Tri-state switch controlling whether the chart injects the external hostnames into the operator-managed CNPG cert via spec.certificates.serverAltDNSNames. When omitted, the chart injects them if `external: true` and skips them otherwise. Set explicitly to `true` to inject regardless of `external` (no-op when `external: false` since there is no external hostname to add). Set to `false` to skip injection. Note that CNPG keeps its built-in TLS on the wire regardless of this flag — this toggle only controls the chart-side SAN injection; to disable PostgreSQL TLS entirely set `postgresql.parameters.ssl = \"off\"` at the CNPG layer.","type":"boolean"}}},"postgresql":{"description":"PostgreSQL server configuration.","type":"object","default":{},"properties":{"parameters":{"description":"PostgreSQL server parameters. Values may be strings or integers; integers are coerced to strings by the template (e.g. both `max_connections: 100` and `max_connections: \"100\"` are accepted). BLOCKED (enable arbitrary code execution): archive_command, rLine truncated
release:
prefix: postgres-
labels: