Security

CloudNativePG is designed to be secure by default, following the “Least Privilege” principle across all layers of the stack. This document outlines our security governance, supply chain integrity, and the technical controls analyzed at three distinct layers: Code, Container, and Cluster.

Warning

The information contained in this page must not exonerate you from performing regular InfoSec duties on your Kubernetes cluster. Please familiarize yourself with the

Overview of Cloud Native Security

page from the Kubernetes documentation. !!! Note About the 4C’s Security Model] Please refer to The 4C’s Security Model in Kubernetes

blog article to get a better understanding and context of the approach EDB has taken with security in CloudNativePG. —

Governance and Trust

Vulnerability Reporting

Important

Please do not report security vulnerabilities through public GitHub issues. CloudNativePG follows a coordinated disclosure model. For instructions on how to report a vulnerability privately, please refer to our Security Policy on GitHub .

Release Integrity and Supply Chain

CloudNativePG follows the OpenSSF Software Producer Security (OSPS) Baseline . All official release assets, including Kubernetes manifests and container images, are cryptographically signed and verifiable.

For quick-start instructions on verifying these assets during installation, see the Installation Guide . Detailed information on our Image Signatures and Attestations

can be found below.


Code

CloudNativePG’s source code undergoes systematic static analysis, including checks for security vulnerabilities, using the popular open-source linter for Go, GolangCI-Lint , directly integrated into the CI/CD pipeline. GolangCI-Lint can run multiple linters on the same source code.

The following tools are used to identify security issues:

  • ** Golang Security Checker (gosec ):** A linter that scans the abstract syntax tree of the source code against a set of rules designed to detect known vulnerabilities, threats, and weaknesses, such as hard-coded credentials, integer overflows, and SQL injections. GolangCI-Lint runs gosec as part of its suite.

  • ** govulncheck :** This tool runs in the CI/CD pipeline and reports known vulnerabilities affecting Go code or the compiler. If the operator is built with a version of the Go compiler containing a known vulnerability, govulncheck will detect it.

  • ** CodeQL :** Provided by GitHub, this tool scans for security issues and blocks any pull request with detected vulnerabilities. CodeQL is configured to review only Go code, excluding other languages in the repository such as Python or Bash.

  • ** Snyk :** Conducts nightly code scans in a scheduled job and generates weekly reports highlighting any new findings related to code security and licensing issues.

The CloudNativePG repository has the “Private vulnerability reporting” option enabled in the Security section . This feature allows users to safely report security issues that require careful handling before being publicly disclosed. If you discover any security bug, please use this medium to report it.

Container

Every container image in CloudNativePG is automatically built via CI/CD pipelines after every commit. These images include not only the operator’s image but also the operands’ images, specifically for every supported PostgreSQL version.

  • ** Dockle :** Ensures best practices in the container build process.

  • ** Snyk :** Detects security issues within the container and reports findings via the GitHub interface.

Image Signatures

The operator and operandimages are cryptographically signed using cosign , a signature tool from sigstore . This process is automated via GitHub Actions and leverages short-lived tokens issued through OpenID Connect .

The token issuer is https://token.actions.githubusercontent.com , and the signing identity corresponds to a GitHub workflow executed under the cloudnative-pg repository. This workflow uses the cosign-installer action

to streamline the signing process.

To verify the authenticity of an operator image, use the following cosign command with the image digest:

cosign verify ghcr.io/cloudnative-pg/cloudnative-pg@sha256:<DIGEST> \
  --certificate-identity-regexp="^https://github.com/cloudnative-pg/cloudnative-pg/.github/workflows/release-publish.yml@refs/tags/v" \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com"

Attestations

Container images include the following attestations for transparency and traceability:

that the artifact was built on a trusted, isolated GitHub Actions runner directly from the project’s source.

You can retrieve the SBOM for a specific image and platform using the following command:

docker buildx imagetools inspect <IMAGE> \
  --format {{ json (index .SBOM "<PLATFORM>").SPDX }}

This command outputs the SBOM in JSON format, providing a detailed view of the software components and build dependencies.

For the build-level provenance, use:

docker buildx imagetools inspect <IMAGE> \
  --format {{ json (index .Provenance "<PLATFORM>").SLSA }}

Verifying SLSA provenance

You can verify SLSA Level 3 provenance using slsa-verifier .

To verify a container image, pass its digest reference:

slsa-verifier verify-image \
  ghcr.io/cloudnative-pg/cloudnative-pg@sha256:<DIGEST> \
  --source-uri github.com/cloudnative-pg/cloudnative-pg

Guidelines and Frameworks for Container Security

The following guidelines and frameworks have been considered for ensuring container-level security:

CIS Benchmark for Docker :** Developed by the Center for Internet

Security (CIS).

Security and Containers in CloudNativePG .

Cluster

Security at the cluster level takes into account all Kubernetes components that form both the control plane and the nodes, as well as the applications that run in the cluster (PostgreSQL included).

Role Based Access Control (RBAC)

The operator interacts with the Kubernetes API server using a dedicated service account named cnpg-manager . This service account is typically installed in the operator namespace, commonly cnpg-system . However, the namespace may vary based on the deployment method (see the subsection below).

In the same namespace, there is a binding between the cnpg-manager service account and a role. The specific name and type of this role (either Role or ClusterRole ) also depend on the deployment method. This role defines the necessary permissions required by the operator to function correctly. To learn more about these roles, you can use the kubectl describe clusterrole or kubectl describe role commands, depending on the deployment method.

configmaps

The operator needs to create and manage default config maps for the Prometheus exporter monitoring metrics.

deployments

The operator needs to manage a PgBouncer connection pooler using a standard Kubernetes Deployment resource.

jobs

The operator needs to handle jobs to manage different Cluster ’s phases.

persistentvolumeclaims

The volume where the PGDATA resides is the central element of a PostgreSQL Cluster resource; the operator needs to interact with the selected storage class to dynamically provision the requested volumes, based on the defined scheduling policies.

pods

The operator needs to manage Cluster ’s instances.

secrets

Unless you provide certificates and passwords to your Cluster objects, the operator adopts the “convention over configuration” paradigm by self-provisioning random generated passwords and TLS certificates, and by storing them in secrets.

serviceaccounts

The operator needs to create a service account that enables the instance manager (which is the PID 1 process of the container that controls the PostgreSQL server) to safely communicate with the Kubernetes API server to coordinate actions and continuously provide a reliable status of the Cluster .

services

The operator needs to control network access to the PostgreSQL cluster (or the connection pooler) from applications, and properly manage failover/switchover operations in an automated way (by assigning, for example, the correct end-point of a service to the proper primary PostgreSQL instance).

validatingwebhookconfigurations and mutatingwebhookconfigurations

The operator injects its self-signed webhook CA into both webhook configurations, which are needed to validate and mutate all the resources it manages. For more details, please see the Kubernetes documentation .

volumesnapshots

The operator needs to generate VolumeSnapshots objects in order to take backups of a PostgreSQL server. VolumeSnapshots are read too in order to validate them before starting the restore process.

nodes

The operator needs to get the labels for Affinity and AntiAffinity so it can decide in which nodes a pod can be scheduled. This is useful, for example, to prevent the replicas from being scheduled in the same node - especially important if nodes are in different availability zones. This permission is also used to determine whether a node is scheduled, preventing the creation of pods on unscheduled nodes, or triggering a switchover if the primary lives in an unscheduled node.

Deployments and ClusterRole Resources

As mentioned above, each deployment method may have variations in the namespace location of the service account, as well as the names and types of role bindings and respective roles.

Via Kubernetes Manifest

When installing CloudNativePG using the Kubernetes manifest, permissions are set to ClusterRoleBinding by default. You can inspect the permissions required by the operator by running:

kubectl describe clusterrole cnpg-manager

Via OLM

From a security perspective, the Operator Lifecycle Manager (OLM) provides a more flexible deployment method. It allows you to configure the operator to watch either all namespaces or specific namespaces, enabling more granular permission management.

Note

OLM allows you to deploy the operator in its own namespace and configure it to watch specific namespaces used for CloudNativePG clusters. This setup helps to contain permissions and restrict access more effectively.

Why Are ClusterRole Permissions Needed?

The operator currently requires ClusterRole permissions to read nodes and ClusterImageCatalog objects. All other permissions can be namespace-scoped (i.e., Role ) or cluster-wide (i.e., ClusterRole ).

Even with these permissions, if someone gains access to the ServiceAccount , they will only have get , list , and watch permissions, which are limited to viewing resources. However, if an unauthorized user gains access to the ServiceAccount , it indicates a more significant security issue.

Therefore, it’s crucial to prevent users from accessing the operator’s ServiceAccount and any other ServiceAccount with elevated permissions.

Calls to the API server made by the instance manager

The instance manager, which is the entry point of the operand container, needs to make some calls to the Kubernetes API server to ensure that the status of some resources is correctly updated and to access the config maps and secrets that are associated with that Postgres cluster. Such calls are performed through a dedicated ServiceAccount created by the operator that shares the same PostgreSQL Cluster resource name.

file. For example, to retrieve the permissions of a generic mypg cluster in the myns namespace, you can type the following command:

kubectl get role -n myns mypg -o yaml

Then verify that the role is bound to the service account:

kubectl get rolebinding -n myns mypg -o yaml

configmaps

The instance manager can only read config maps that are related to the same cluster, such as custom monitoring queries

secrets

The instance manager can only read secrets that are related to the same cluster, namely: streaming replication user, application user, super user, LDAP authentication user, client CA, server CA, server certificate, backup credentials, custom monitoring queries

events

The instance manager can create an event for the cluster, informing the API server about a particular aspect of the PostgreSQL instance lifecycle

Here instead, we provide the same summary for resources specific to CloudNativePG.

clusters

The instance manager requires read-only permissions, namely get , list and watch , just for its own Cluster resource

clusters/status

The instance manager requires to update and patch the status of just its own Cluster resource

backups

The instance manager requires get and list permissions to read any Backup resource in the namespace. Additionally, it requires the delete permission to clean up the Kubernetes cluster by removing the Backup objects that do not have a counterpart in the object store - typically because of retention policies

backups/status

The instance manager requires to update and patch the status of any Backup resource in the namespace

Pod and Container Security Contexts

A Security Context

defines privilege and access control settings for a pod or container.

CloudNativePG does not require privileged mode for container execution. The PostgreSQL containers run as the postgres system user. No component whatsoever requires running as root .

Likewise, Volume access does not require privileged mode nor root privileges. Proper permissions must be assigned by the Kubernetes platform and/or administrators. The PostgreSQL containers run with a read-only root filesystem (i.e. no writable layer).

The operator manages the setting of security contexts for all pods and containers of a PostgreSQL cluster. The Seccomp Profile

to be used for the PostgreSQL containers can be configured with the spec.seccompProfile section of the Cluster resource. If this section is left blank, the containers will use a seccompProfile Type of RuntimeDefault , that is, the container runtime default.

The security context of PostgreSQL containers using the default seccompProfile will look like this:

securityContext:
  allowPrivilegeEscalation: false
  capabilities:
    drop:
    - ALL
  privileged: false
  readOnlyRootFilesystem: true
  runAsNonRoot: true
  seccompProfile:
    type: RuntimeDefault

Customizing Security Contexts

CloudNativePG provides fine-grained control over security contexts for both pods and containers through the spec.podSecurityContext and spec.securityContext fields respectively.

Example:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: cluster-example
spec:
  instances: 3
  podSecurityContext:
    runAsUser: 26
    runAsGroup: 26
    fsGroup: 26
    supplementalGroups: [2000, 3000]
    fsGroupChangePolicy: "OnRootMismatch"

Container Security Context (spec.securityContext ): This allows you to override the default SecurityContext applied to all containers within the PostgreSQL cluster pods. Like podSecurityContext , it merges with the operator’s defaults.

Example:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: cluster-example
spec:
  instances: 3
  securityContext:
    allowPrivilegeEscalation: false
    # Note: capabilities are not merged with operator defaults.
    # If specified, they fully replace any defaults.
    capabilities:
      drop:
      - ALL
      add:
      - NET_BIND_SERVICE
    readOnlyRootFilesystem: true
    runAsNonRoot: true

restricted profile, which has strict requirements for pod and container security contexts.

Security Context Constraints

When running in an environment that is utilizing Security Context Constraints (SCC)

the operator does not explicitly set the security context of the PostgreSQL cluster pods, but rather allows the pods to inherit the restricted Security Context Constraints that are already defined.

Restricting Pod access using AppArmor

You can assign an AppArmor profile to the postgres , initdb , join , full-recovery and bootstrap-controller containers inside every Cluster pod through the container.apparmor.security.beta.kubernetes.io annotation. For example:

kind: Cluster
metadata:
  name: cluster-apparmor
  annotations:
    container.apparmor.security.beta.kubernetes.io/postgres: runtime/default
    container.apparmor.security.beta.kubernetes.io/initdb: runtime/default
    container.apparmor.security.beta.kubernetes.io/join: runtime/default

Warning

Using this kind of annotations can result in your cluster to stop working. If this is the case, the annotation can be safely removed from the Cluster . The AppArmor configuration must be at Kubernetes node level, meaning that the underlying operating system must have this option enable and properly configured.

In case this is not the situation, and the annotations were added at the Cluster creation time, pods will not be created. On the other hand, if you add the annotations after the Cluster was created the pods in the cluster will be unable to start and you will get an error like this:

metadata.annotations[container.apparmor.security.beta.kubernetes.io/postgres]: Forbidden: may not add AppArmor annotations]

In such cases, please refer to your Kubernetes administrators and ask for the proper AppArmor profile to use.

Network Policies

The pods created by the Cluster resource can be controlled by Kubernetes network policies

to enable/disable inbound and outbound network access at IP and TCP level. You can find more information in the networking document .

section of the Kubernetes documentation for further information.

Exposed Ports

CloudNativePG exposes ports at operator, instance manager and operand levels, as listed in the table below:

System

Port number

Exposing

Name

TLS

Authentication

operator

9443

webhook server

webhook-server

Yes

Yes

operator

8080

metrics

metrics

No

No

instance manager

9187

metrics

metrics

Optional

No

instance manager

8000

status

status

Yes

No

operand

5432

PostgreSQL instance

postgresql

Optional

Yes

PostgreSQL

The current implementation of CloudNativePG automatically creates passwords and .pgpass files for the database owner and, only if requested by setting enableSuperuserAccess to true , for the postgres superuser.

Warning

enableSuperuserAccess is set to false by default to improve the security-by-default posture of the operator, fostering a microservice approach where changes to PostgreSQL are performed in a declarative way through the spec of the Cluster resource, while providing developers with full powers inside the database through the database owner user. As far as password encryption is concerned, CloudNativePG follows the default behavior of PostgreSQL: starting from PostgreSQL 14, password_encryption is by default set to scram-sha-256 , while on earlier versions it is set to md5 .

section in the PostgreSQL documentation for details. !!! Note The operator supports toggling the enableSuperuserAccess option. When you disable it on a running cluster, the operator will ignore the content of the secret, remove it (if previously generated by the operator) and set the password of the postgres user to NULL (de facto disabling remote access through password authentication). !!! Note Important] PostgreSQL may include cleartext role passwords in its logs for some role operations, as mentioned in the PostgreSQL documentation . CloudNativePG ensures that postgres logging (both statement logging and error statement logging) is temporarily suppressed for CREATE/ALTER ROLE operations with passwords, thus preventing any password leakage. See the Secrets for more information.

You can use those files to configure application access to the database.

By default, every replica is automatically configured to connect in physical async streaming replication with the current primary instance, with a special user called streaming_replica . The connection between nodes is encrypted and authentication is via TLS client certificates (please refer to the Client TLS/SSL connections page for details). By default, the operator requires TLS v1.3 connections.

Currently, the operator allows administrators to add pg_hba.conf lines directly in the manifest as part of the pg_hba section of the postgresql configuration. The lines defined in the manifest are added to a default pg_hba.conf .

For further detail on how pg_hba.conf is managed by the operator, see the Mounting and PostgreSQL Configuration of the documentation.

The administrator can also customize the content of the pg_ident.conf file that by default only maps the local postgres user to the postgres user in the database.

For further detail on how pg_ident.conf is managed by the operator, see the Mounting and PostgreSQL Configuration of the documentation.

Storage

CloudNativePG delegates encryption at rest to the underlying storage class. For data protection in production environments, we highly recommend that you choose a storage class that supports encryption at rest.