From 1569311a506e6c8ca6a306096cd75e20291490c2 Mon Sep 17 00:00:00 2001 From: yerkennz Date: Mon, 8 Jun 2026 16:49:17 +0500 Subject: [PATCH 1/4] feat: [CPCAP-9519] integrate s3 bucket aliases --- operator/api/apps/v1/postgresservice_types.go | 1 + .../crds/netcracker.com_patroniservices.yaml | 2 + .../patroni-services/templates/_helpers.tpl | 16 +++++ .../charts/patroni-services/templates/cr.yaml | 3 + .../templates/secrets/s3-aliases-secret.yaml | 37 ++++++++++++ operator/charts/patroni-services/values.yaml | 1 + operator/pkg/deployment/backup.go | 30 ++++++++++ operator/pkg/reconciler/backup_daemon.go | 12 +++- .../docker/granular/storage_s3.py | 39 +++++++++--- .../docker/postgres/storage_s3.py | 60 +++++++++++++------ 10 files changed, 173 insertions(+), 28 deletions(-) create mode 100644 operator/charts/patroni-services/templates/secrets/s3-aliases-secret.yaml diff --git a/operator/api/apps/v1/postgresservice_types.go b/operator/api/apps/v1/postgresservice_types.go index d961aecf..ff0a6ea3 100644 --- a/operator/api/apps/v1/postgresservice_types.go +++ b/operator/api/apps/v1/postgresservice_types.go @@ -300,6 +300,7 @@ type BackupDaemon struct { SecurityContext v1.PodSecurityContext `json:"securityContext,omitempty"` PriorityClassName string `json:"priorityClassName,omitempty"` S3Storage *S3Storage `json:"s3Storage,omitempty"` + S3AliasesUsed bool `json:"s3AliasesUsed,omitempty"` PodLabels map[string]string `json:"podLabels,omitempty"` ExternalPv *ExternalPv `json:"externalPv,omitempty"` SslMode string `json:"sslMode,omitempty"` diff --git a/operator/charts/patroni-services/crds/netcracker.com_patroniservices.yaml b/operator/charts/patroni-services/crds/netcracker.com_patroniservices.yaml index e2143f0a..b631ca90 100644 --- a/operator/charts/patroni-services/crds/netcracker.com_patroniservices.yaml +++ b/operator/charts/patroni-services/crds/netcracker.com_patroniservices.yaml @@ -1073,6 +1073,8 @@ spec: type: object retainArchiveSettings: type: boolean + s3AliasesUsed: + type: boolean s3Storage: properties: accessKeyId: diff --git a/operator/charts/patroni-services/templates/_helpers.tpl b/operator/charts/patroni-services/templates/_helpers.tpl index 9ab3fbba..d194d98e 100644 --- a/operator/charts/patroni-services/templates/_helpers.tpl +++ b/operator/charts/patroni-services/templates/_helpers.tpl @@ -265,6 +265,22 @@ pg-{{ default "patroni" .Values.patroni.clusterName }}-direct {{- end }} {{- end -}} +{{/* +Effective backup daemon S3 aliases wrapped in a map: { items: [...] }. +When CLOUD_BACKUP_STORAGE_LOCATION is set and global.cloudIntegrationEnabled is true, +use cloud payload; otherwise use backupDaemon.s3Aliases from values. +Usage: (fromYaml (include "backupDaemon.s3Aliases" .)).items +*/}} +{{- define "backupDaemon.s3Aliases" -}} +{{- if and .Values.CLOUD_BACKUP_STORAGE_LOCATION .Values.global.cloudIntegrationEnabled -}} +items: {{ toYaml .Values.CLOUD_BACKUP_STORAGE_LOCATION | nindent 2 }} +{{- else if .Values.backupDaemon.s3Aliases -}} +items: {{ toYaml .Values.backupDaemon.s3Aliases | nindent 2 }} +{{- else -}} +items: [] +{{- end -}} +{{- end -}} + {{/* Postgres host for DBaaS adapter */}} diff --git a/operator/charts/patroni-services/templates/cr.yaml b/operator/charts/patroni-services/templates/cr.yaml index 89f9e5d7..7e46716d 100644 --- a/operator/charts/patroni-services/templates/cr.yaml +++ b/operator/charts/patroni-services/templates/cr.yaml @@ -120,6 +120,9 @@ spec: untrustedCert: {{ default "true" .Values.backupDaemon.s3Storage.untrustedCert }} region: {{ default "us-east-1" .Values.backupDaemon.s3Storage.region }} {{ end }} + {{- if (fromYaml (include "backupDaemon.s3Aliases" .)).items }} + s3AliasesUsed: true + {{- end }} {{ if .Values.backupDaemon.externalPv }} externalPv: {{ toYaml .Values.backupDaemon.externalPv | nindent 6 }} {{ end }} diff --git a/operator/charts/patroni-services/templates/secrets/s3-aliases-secret.yaml b/operator/charts/patroni-services/templates/secrets/s3-aliases-secret.yaml new file mode 100644 index 00000000..b2dbc011 --- /dev/null +++ b/operator/charts/patroni-services/templates/secrets/s3-aliases-secret.yaml @@ -0,0 +1,37 @@ +{{- if .Values.backupDaemon.install }} +{{- $s3Data := fromYaml (include "backupDaemon.s3Aliases" .) }} +{{- if $s3Data.items }} +{{- $aliases := dict }} +{{- range $s3Data.items }} +{{- $out := dict }} + +{{- if .spec }} +{{- $out = merge $out (omit .spec "storageBucket" "storageUsername" "storageRegion" "storageServerUrl") }} +{{- if .spec.storageBucket }}{{- $out = set $out "bucketName" .spec.storageBucket }}{{- end }} +{{- if .spec.storageUsername }}{{- $out = set $out "accessKeyId" .spec.storageUsername }}{{- end }} +{{- $out = set $out "region" (default "us-east-1" .spec.storageRegion) }} +{{- if .spec.storageServerUrl }}{{- $out = set $out "s3Url" .spec.storageServerUrl }}{{- end }} +{{- end }} + +{{- if .secretContent }} +{{- $out = merge $out (omit .secretContent "storagePassword") }} +{{- if .secretContent.storagePassword }}{{- $out = set $out "accessKeySecret" .secretContent.storagePassword }}{{- end }} +{{- end }} + +{{- $aliases = set $aliases .name $out }} +{{- end }} + +apiVersion: v1 +kind: Secret +metadata: + name: s3-aliases + labels: + app: postgres-backup-daemon + name: postgres-backup-daemon + {{- include "kubernetes.labels" . | nindent 4 }} +type: Opaque +stringData: + s3_aliases.json: | +{{ $aliases | toPrettyJson | indent 4 }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/operator/charts/patroni-services/values.yaml b/operator/charts/patroni-services/values.yaml index ad9d11af..5153b41a 100644 --- a/operator/charts/patroni-services/values.yaml +++ b/operator/charts/patroni-services/values.yaml @@ -224,6 +224,7 @@ backupDaemon: # - postgresql-backup-pv-1 # The array of node-selectors that will be used for deployment. # storage.nodes can be used only if storage.type is set to PV + s3Aliases: [] # nodes: # - db-backup-node1 diff --git a/operator/pkg/deployment/backup.go b/operator/pkg/deployment/backup.go index 72271f47..93e5c3e4 100644 --- a/operator/pkg/deployment/backup.go +++ b/operator/pkg/deployment/backup.go @@ -296,6 +296,36 @@ func NewBackupDaemonDeployment(backupDaemon *netcrackerv1.BackupDaemon, pgCluste }, } } + if backupDaemon.S3AliasesUsed { + deployment.Spec.Template.Spec.Containers[0].Env = append( + deployment.Spec.Template.Spec.Containers[0].Env, + corev1.EnvVar{ + Name: "S3_ALIASES_USED", + Value: "true", + }, + ) + + deployment.Spec.Template.Spec.Volumes = append( + deployment.Spec.Template.Spec.Volumes, + corev1.Volume{ + Name: "s3-aliases", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "s3-aliases", + }, + }, + }, + ) + + deployment.Spec.Template.Spec.Containers[0].VolumeMounts = append( + deployment.Spec.Template.Spec.Containers[0].VolumeMounts, + corev1.VolumeMount{ + Name: "s3-aliases", + MountPath: "/aliases/", + ReadOnly: true, + }, + ) + } if backupDaemon.ExternalPv != nil { deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, getExternalBackupVolume()) diff --git a/operator/pkg/reconciler/backup_daemon.go b/operator/pkg/reconciler/backup_daemon.go index 9e4a231f..440e0079 100644 --- a/operator/pkg/reconciler/backup_daemon.go +++ b/operator/pkg/reconciler/backup_daemon.go @@ -16,6 +16,9 @@ package reconciler import ( "fmt" + "strconv" + "strings" + qubershipv1 "github.com/Netcracker/pgskipper-operator/api/apps/v1" commonv1 "github.com/Netcracker/pgskipper-operator/api/common/v1" patroniv1 "github.com/Netcracker/pgskipper-operator/api/patroni/v1" @@ -30,8 +33,6 @@ import ( "go.uber.org/zap" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/intstr" - "strconv" - "strings" ) const ( @@ -91,7 +92,12 @@ func (r *BackupDaemonReconciler) Reconcile() error { } // Add Secret Hash - err := manager.AddCredHashToPodTemplate(credentials.PostgresSecretNames, &backupDaemonDeployment.Spec.Template) + secretNames := append([]string{}, credentials.PostgresSecretNames...) + + if bdSpec.S3AliasesUsed { + secretNames = append(secretNames, "s3-aliases") + } + err := manager.AddCredHashToPodTemplate(secretNames, &backupDaemonDeployment.Spec.Template) if err != nil { logger.Error(fmt.Sprintf("can't add secret HASH to annotations for %s", backupDaemonDeployment.Name), zap.Error(err)) return err diff --git a/services/backup-daemon/docker/granular/storage_s3.py b/services/backup-daemon/docker/granular/storage_s3.py index 4876949c..d2630b47 100644 --- a/services/backup-daemon/docker/granular/storage_s3.py +++ b/services/backup-daemon/docker/granular/storage_s3.py @@ -20,6 +20,7 @@ import os import logging import configs +import json from retrying import retry try: @@ -38,10 +39,31 @@ class AwsS3Vault: __log = logging.getLogger("AwsS3Granular") + @staticmethod + def get_s3_alias_config(): + if os.getenv("S3_ALIASES_USED", "false").lower() != "true": + return None + + with open("/aliases/s3_aliases.json", "r") as f: + aliases = json.load(f) + + if not aliases: + raise Exception("S3 aliases are enabled, but /aliases/s3_aliases.json is empty") + + alias_name = next(iter(aliases)) + return aliases[alias_name] + + @staticmethod + def get_s3_bucket_name(): + alias = AwsS3Vault.get_s3_alias_config() + if alias: + return alias.get("bucketName") + return os.getenv("CONTAINER") or os.getenv("AWS_S3_BUCKET") or os.getenv("S3_BUCKET") + def __init__(self, cluster_name=None, cache_enabled=False, aws_s3_bucket_listing=None, prefix=None): - self.bucket = bucket or os.getenv("CONTAINER") or os.getenv("AWS_S3_BUCKET") or os.getenv("S3_BUCKET") + self.bucket = AwsS3Vault.get_s3_bucket_name() self.console = None self.cluster_name = cluster_name self.cache_enabled = cache_enabled @@ -56,12 +78,15 @@ def __init__(self, cluster_name=None, cache_enabled=False, raise ValueError("S3 bucket is not configured. Set one of CONTAINER, AWS_S3_BUCKET, or S3_BUCKET.") def get_s3_client(self): - return boto3.client("s3", - region_name=os.getenv("AWS_DEFAULT_REGION") if os.getenv("AWS_DEFAULT_REGION") else None, - endpoint_url=os.getenv("AWS_S3_ENDPOINT_URL"), - aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"), - aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"), - verify=(False if os.getenv("AWS_S3_UNTRUSTED_CERT", "false").lower() == "true" else None)) + alias = AwsS3Vault.get_s3_alias_config() + return boto3.client( + "s3", + region_name=alias.get("region") if alias else (os.getenv("AWS_DEFAULT_REGION") if os.getenv("AWS_DEFAULT_REGION") else None), + endpoint_url=alias.get("s3Url") if alias else os.getenv("AWS_S3_ENDPOINT_URL"), + aws_access_key_id=alias.get("accessKeyId") if alias else os.getenv("AWS_ACCESS_KEY_ID"), + aws_secret_access_key=alias.get("accessKeySecret") if alias else os.getenv("AWS_SECRET_ACCESS_KEY"), + verify=(False if os.getenv("AWS_S3_UNTRUSTED_CERT", "false").lower() == "true" else None), + ) @retry(stop_max_attempt_number=RETRY_COUNT, wait_fixed=RETRY_WAIT) def upload_file(self, file_path, blob_path=None, backup_id=None): diff --git a/services/backup-daemon/docker/postgres/storage_s3.py b/services/backup-daemon/docker/postgres/storage_s3.py index 8451173f..25020ee7 100644 --- a/services/backup-daemon/docker/postgres/storage_s3.py +++ b/services/backup-daemon/docker/postgres/storage_s3.py @@ -114,21 +114,21 @@ def prot_put_as_stream(self, filename, stream): self.__log.info("Start uploading: %s" % filename) # todo[anin] replace implementation # AwsS3Vault.get_s3_client().upload_fileobj(data, CONTAINER, filename) - AwsS3Vault.get_s3_client().upload_file(fs_filename, CONTAINER, filename) + AwsS3Vault.get_s3_client().upload_file(fs_filename, AwsS3Vault.get_s3_bucket_name(), filename) os.remove(fs_filename) return sha256.hexdigest() @retry(stop_max_attempt_number=RETRY_COUNT, wait_fixed=RETRY_WAIT) def prot_get_as_stream(self, filename): self.__log.info("Get stream request for file: %s" % self.aws_prefix + filename) - object_body = AwsS3Vault.get_s3_resource().Bucket(CONTAINER).Object(self.aws_prefix + filename).get()['Body'] + object_body = AwsS3Vault.get_s3_resource().Bucket(AwsS3Vault.get_s3_bucket_name()).Object(self.aws_prefix + filename).get()['Body'] return StreamWrapper(object_body) @retry(stop_max_attempt_number=RETRY_COUNT, wait_fixed=RETRY_WAIT) def prot_delete_bundle(self, filename): - objects_to_delete = AwsS3Vault.get_s3_client().list_objects(Bucket=CONTAINER, Prefix=self.aws_prefix + filename) + objects_to_delete = AwsS3Vault.get_s3_client().list_objects(Bucket=AwsS3Vault.get_s3_bucket_name(), Prefix=self.aws_prefix + filename) for obj in objects_to_delete.get('Contents', []): - AwsS3Vault.get_s3_client().delete_object(Bucket=CONTAINER, Key=obj['Key']) + AwsS3Vault.get_s3_client().delete_object(Bucket=AwsS3Vault.get_s3_bucket_name(), Key=obj['Key']) def prot_delete(self, filename): self.prot_delete_bundle(filename) @@ -136,7 +136,7 @@ def prot_delete(self, filename): def prot_is_file_exists(self, filename): exists = True try: - AwsS3Vault.get_s3_resource().Object(CONTAINER, self.aws_prefix + filename).get() + AwsS3Vault.get_s3_resource().Object(AwsS3Vault.get_s3_bucket_name(), self.aws_prefix + filename).get() except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == 'NoSuchKey': exists = False @@ -147,7 +147,7 @@ def prot_is_file_exists(self, filename): def prot_get_file_size(self, filename): if self.prot_is_file_exists(filename): - return int(AwsS3Vault.get_s3_resource().Object(CONTAINER, filename).get()['Size']) + return int(AwsS3Vault.get_s3_resource().Object(AwsS3Vault.get_s3_bucket_name(), filename).get()['Size']) return 0 def is_valid_backup_id(self, backup_id): @@ -159,7 +159,7 @@ def is_valid_backup_id(self, backup_id): @retry(stop_max_attempt_number=RETRY_COUNT, wait_fixed=RETRY_WAIT) def list(self): - bucket = AwsS3Vault.get_s3_client().list_objects(Bucket=CONTAINER) + bucket = AwsS3Vault.get_s3_client().list_objects(Bucket=AwsS3Vault.get_s3_bucket_name()) aws_s3_vault_listing = [] if 'Contents' in bucket: # Collect backups ids only @@ -169,7 +169,7 @@ def list(self): vaults = [ AwsS3Vault(backup_id - , bucket=CONTAINER + , bucket=AwsS3Vault.get_s3_bucket_name() , cluster_name=PG_CLUSTER_NAME , cache_enabled=True , aws_s3_bucket_listing=(bucket['Contents'] if 'Contents' in bucket else None)) @@ -180,7 +180,7 @@ def list(self): def size(self): """ Returns whole storage size in bytes """ total_size = 0 - bucket = AwsS3Vault.get_s3_client().list_objects(Bucket=CONTAINER) + bucket = AwsS3Vault.get_s3_client().list_objects(Bucket=AwsS3Vault.get_s3_bucket_name()) if 'Contents' not in bucket: return 0 @@ -192,7 +192,7 @@ def size(self): def archive_size(self): """ Returns whole storage size in bytes """ total_size = 0 - bucket = AwsS3Vault.get_s3_client().list_objects(Bucket=CONTAINER, Prefix="archive/") + bucket = AwsS3Vault.get_s3_client().list_objects(Bucket=AwsS3Vault.get_s3_bucket_name(), Prefix="archive/") if 'Contents' not in bucket: return 0 @@ -211,7 +211,7 @@ def open_vault(self, backup_id): :return: :rtype: (str, dict, StringIO) """ - return AwsS3Vault("%s" % (datetime.now().strftime(VAULT_NAME_FORMAT)), CONTAINER, cluster_name=PG_CLUSTER_NAME) + return AwsS3Vault("%s" % (datetime.now().strftime(VAULT_NAME_FORMAT)), AwsS3Vault.get_s3_bucket_name(), cluster_name=PG_CLUSTER_NAME) def evict_vault(self, vault): self.__log.info("Evict vault: %s" % vault) @@ -224,7 +224,7 @@ def evict_vault(self, vault): return "Not Found" def prot_list_archive(self): - bucket = AwsS3Vault.get_s3_client().list_objects(Bucket=CONTAINER, Prefix="archive/", Delimiter="/") + bucket = AwsS3Vault.get_s3_client().list_objects(Bucket=AwsS3Vault.get_s3_bucket_name(), Prefix="archive/", Delimiter="/") aws_s3_archive_listing = [] if 'Contents' in bucket: # Collect archive ids only @@ -256,14 +256,38 @@ class AwsS3VaultCreationException(Exception): class AwsS3Vault(storage.Vault): __log = logging.getLogger("AwsS3Vault") + @staticmethod + def get_s3_alias_config(): + if os.getenv("S3_ALIASES_USED", "false").lower() != "true": + return None + + with open("/aliases/s3_aliases.json", "r") as f: + aliases = json.load(f) + + if not aliases: + raise Exception("S3 aliases are enabled, but /aliases/s3_aliases.json is empty") + + alias_name = next(iter(aliases)) + return aliases[alias_name] + + @staticmethod + def get_s3_bucket_name(): + alias = AwsS3Vault.get_s3_alias_config() + if alias: + return alias.get("bucketName") + return CONTAINER + @staticmethod def get_s3_resource(): - return boto3.resource("s3", - region_name=os.getenv("AWS_DEFAULT_REGION") if os.getenv("AWS_DEFAULT_REGION") else None, - endpoint_url=os.getenv("AWS_S3_ENDPOINT_URL"), - aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"), - aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"), - verify=(False if os.getenv("AWS_S3_UNTRUSTED_CERT", "false").lower() == "true" else None)) + alias = AwsS3Vault.get_s3_alias_config() + return boto3.resource( + "s3", + region_name=alias.get("region") if alias else (os.getenv("AWS_DEFAULT_REGION") if os.getenv("AWS_DEFAULT_REGION") else None), + endpoint_url=alias.get("s3Url") if alias else os.getenv("AWS_S3_ENDPOINT_URL"), + aws_access_key_id=alias.get("accessKeyId") if alias else os.getenv("AWS_ACCESS_KEY_ID"), + aws_secret_access_key=alias.get("accessKeySecret") if alias else os.getenv("AWS_SECRET_ACCESS_KEY"), + verify=(False if os.getenv("AWS_S3_UNTRUSTED_CERT", "false").lower() == "true" else None), + ) @staticmethod def get_s3_client(): From 73f419b62e1d7f0d01da5e7dee150722408337ed Mon Sep 17 00:00:00 2001 From: yerkennz Date: Tue, 9 Jun 2026 20:09:21 +0500 Subject: [PATCH 2/4] feat: add s3Aliases docs --- docs/public/installation.md | 10 +++++++++- operator/charts/patroni-services/values.yaml | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/public/installation.md b/docs/public/installation.md index 98d3296b..e2f24fff 100644 --- a/docs/public/installation.md +++ b/docs/public/installation.md @@ -418,7 +418,15 @@ This sections describes all possible deploy parameters for PostgreSQL Backup Dae | backupDaemon.externalPv.storageClass | string | no | n/a | Specifies StorageClass of External PV. | | backupDaemon.priorityClassName | string | no | n/a | Specifies [Priority Class](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/#priorityclass). | | backupDaemon.affinity | json | no | n/a | Specifies the affinity scheduling rules. | -| backupDaemon.podLabels | yaml | no | n/a | Specifies custom pod labels. | +| backupDaemon.podLabels | yaml | no | n/a | Specifies custom pod labels. | +| backupDaemon.s3Aliases | list | no | [] | Array of S3 storage alias configurations. All entries are stored in a single Kubernetes Secret named s3-aliases, where each alias name is a separate data key with a JSON payload describing the S3 connection. Automatically filled from CLOUD_BACKUP_STORAGE_LOCATION if global.cloudIntegrationEnabled is enabled. | +| backupDaemon.s3Aliases[].name | string | yes | n/a | Unique alias name. Used as a top-level key inside `s3_aliases.json`. | +| backupDaemon.s3Aliases[].spec.storageBucket | string | yes | n/a | Specifies the name of the S3 bucket. | +| backupDaemon.s3Aliases[].spec.storageProvider | string | no | n/a | Specifies the storage provider type, for example `aws` or `minio`. | +| backupDaemon.s3Aliases[].spec.storageRegion | string | no | us-east-1 | Specifies the name of the region associated with the client. | +| backupDaemon.s3Aliases[].spec.storageServerUrl | string | yes | n/a | Specifies the URL address to S3 storage. | +| backupDaemon.s3Aliases[].spec.storageUsername | string | yes | n/a | Specifies S3 accessKeyId credential. | +| backupDaemon.s3Aliases[].secretContent.storagePassword | string | yes | n/a | Specifies S3 secretAccessKey credential. | ## metricCollector diff --git a/operator/charts/patroni-services/values.yaml b/operator/charts/patroni-services/values.yaml index 5153b41a..889b5101 100644 --- a/operator/charts/patroni-services/values.yaml +++ b/operator/charts/patroni-services/values.yaml @@ -224,7 +224,7 @@ backupDaemon: # - postgresql-backup-pv-1 # The array of node-selectors that will be used for deployment. # storage.nodes can be used only if storage.type is set to PV - s3Aliases: [] + s3Aliases: [] # nodes: # - db-backup-node1 From cb63b99c05a782ee0eafc3462572b48eadd69516 Mon Sep 17 00:00:00 2001 From: Mikhail Chekalov Date: Mon, 8 Jun 2026 19:44:28 +0500 Subject: [PATCH 3/4] fix: [CPCAP-9409] return readOnlyRootFilesystem (#479) --- docs/public/features/cis-hardening.md | 8 +++--- docs/public/features/disaster-recovery.md | 6 ++--- docs/public/features/ldap_integration.md | 2 +- docs/public/features/pgBackRest.md | 2 +- docs/public/features/query-exporter.md | 8 +++--- docs/public/installation.md | 26 +++++++++---------- docs/public/quickstart.md | 3 --- operator/build/Dockerfile | 5 ++-- operator/build/bin/entrypoint | 12 --------- .../patroni-core/templates/_helpers.tpl | 2 +- .../patroni-core/templates/deployment.yaml | 6 +---- .../templates/deployment.yaml | 6 +---- operator/pkg/deployment/patroni.go | 2 +- operator/pkg/reconciler/backup_daemon.go | 2 +- operator/pkg/reconciler/patroni.go | 14 +++++----- operator/pkg/upgrade/upgrade.go | 12 ++++----- operator/pkg/util/util.go | 2 +- .../check_installation.robot | 2 +- 18 files changed, 48 insertions(+), 72 deletions(-) delete mode 100755 operator/build/bin/entrypoint diff --git a/docs/public/features/cis-hardening.md b/docs/public/features/cis-hardening.md index 5bf1713a..95db088d 100644 --- a/docs/public/features/cis-hardening.md +++ b/docs/public/features/cis-hardening.md @@ -36,13 +36,13 @@ The following table describes the custom values for such parameters: |3.1.4 Ensure the log file destination directory is set correctly. | `log` | `/proc/1/fd` | In case of deployment to Kubernetes or OpenShift, we should forward all the logs to `1` process. So the `fluentd` agent should be able to forward logs to Graylog. | |3.1.5 Ensure the filename pattern for log files is set correctly. | `postgresql-%a.log` | `1` | In case of deployment to Kubernetes or OpenShift, we should forward all the logs to `stdout` of `1` process. So the `fluentd` agent should be able to forward logs to Graylog. | |3.1.8 Ensure the maximum log file lifetime is set correctly. | `1d` | `0` | In case of deployment to Kubernetes or OpenShift, we should forward all the logs to Graylog. The logs' rotation policy is configured system-wide for Kubernetes or OpenShift and for Graylog. | -|3.1.18 Ensure 'log_connections' is enabled. | `on` | `off` | By default, we are not enabling this parameter, but it is possible to enable it. For more information, see the [Configuring PostgreSQL Parameters](/docs/public/features/cis-hardening.md#configuring-postgresql-parameters) section. | -|3.1.18 Ensure 'log_disconnections' is enabled. | `on` | `off` | By default, we are not enabling this parameter, but it is possible to enable it. For more information, see the [Configuring PostgreSQL Parameters](/docs/public/features/cis-hardening.md#configuring-postgresql-parameters) section. | +|3.1.18 Ensure 'log_connections' is enabled. | `on` | `off` | By default, we are not enabling this parameter, but it is possible to enable it. For more information, see the [Configuring PostgreSQL Parameters](#configuring-postgresql-parameters) section. | +|3.1.18 Ensure 'log_disconnections' is enabled. | `on` | `off` | By default, we are not enabling this parameter, but it is possible to enable it. For more information, see the [Configuring PostgreSQL Parameters](#configuring-postgresql-parameters) section. | |3.1.22 Ensure 'log_line_prefix' is set correctly. | `%m` | `[%m][source=postgresql]` | In case of deployment to Kubernetes or OpenShift, we should forward all the logs to Graylog. Such `log_line_prefix` allows to filter all the PostgreSQL logs through the `source=postgresql` prefix. | |3.1.23 Ensure 'log_hostname' is set correctly | `off` | `on` | Enabling the log_hostname setting causes the hostname of the connecting host to be logged in addition to the host's IP address for connection log messages. | |3.1.24 Ensure 'log_timezone' is set correctly. | `us/eastern` | `UTC` | We are setting `UTC` timezone for our PostgreSQL deployment. But it is possible to change this value. | -|3.2 Ensure the PostgreSQL Audit Extension (pgAudit) is enabled - pgaudit installed. | `pgaudit` | `pg_stat_statements, pg_hint_plan, pg_cron, pgaudit, set_user` | By default, we are not enabling this parameter, but it is possible to enable it. For more information, see the [Configuring PostgreSQL Parameters](/docs/public/features/cis-hardening.md#configuring-postgresql-parameters) section. | -|4.7 Ensure the set_user extension is installed. | `set_user` | `pg_stat_statements, pg_hint_plan, pg_cron, pgaudit, set_user` | By default, we are not enabling this parameter, but it is possible to enable it. For more information, see the [Configuring PostgreSQL Parameters](/docs/public/features/cis-hardening.md#configuring-postgresql-parameters) section. | +|3.2 Ensure the PostgreSQL Audit Extension (pgAudit) is enabled - pgaudit installed. | `pgaudit` | `pg_stat_statements, pg_hint_plan, pg_cron, pgaudit, set_user` | By default, we are not enabling this parameter, but it is possible to enable it. For more information, see the [Configuring PostgreSQL Parameters](#configuring-postgresql-parameters) section. | +|4.7 Ensure the set_user extension is installed. | `set_user` | `pg_stat_statements, pg_hint_plan, pg_cron, pgaudit, set_user` | By default, we are not enabling this parameter, but it is possible to enable it. For more information, see the [Configuring PostgreSQL Parameters](#configuring-postgresql-parameters) section. | |6.8 Ensure SSL is enabled and configured correctly. | `on` | `off` | PostgreSQL is not exposed outside of OpenShift or Kubernetes, so enabling of SSL is needless. If necessary, it is better to configure SSL on the PaaS Level (OpenShift or Kubernetes). | |6.9 Ensure that pgcrypto extension is installed and configured correctly. | `"pgcrypto", regex:".*", regex:".*", regex:".*"` | `"pgcrypto", "1.3", NULL, "cryptographic functions"` | Pgcrypto extension is installed in PostgreSQL Docker images by default, but the extension should be activated on Logical Database level by applications. | diff --git a/docs/public/features/disaster-recovery.md b/docs/public/features/disaster-recovery.md index 7892a434..19d7b6d1 100644 --- a/docs/public/features/disaster-recovery.md +++ b/docs/public/features/disaster-recovery.md @@ -4,9 +4,9 @@ This chapter describes how to deploy and use PostgreSQL in Disaster Recovery sch Postgres Service can be deployed in the Disaster Recovery (DR) scheme with clusters in `active` and `standby` modes using the configuration described in the section Active-Standby PostgreSQL Cluster Deployment Scheme in the _Postgres Operator Maintenance_ chapter. -For more information about the DR scheme, refer to the [PostgreSQL Service Installation Procedure](/docs/public/installation.md#active-standby-deployment-in-two-kubernetes-clusters-prerequisites). +For more information about the DR scheme, refer to the [PostgreSQL Service Installation Procedure](../installation.md#active-standby-deployment-in-two-kubernetes-clusters-prerequisites). -![Postgres Service DR Scheme](/docs/public/images/arch/pg-arch-on-prem-dr.png) +![Postgres Service DR Scheme](../images/arch/pg-arch-on-prem-dr.png) In case of maintenance, switchover, or failover, promote the standby cluster to active by changing the configuration. @@ -21,7 +21,7 @@ Previously, this action was fully manual. Now there is a high level Site Manager # Prerequisites * In case of two separate Postgres services already installed on two Kubernetes or OpenShift clusters (also can be deployed on different namespaces of the one cloud) -* Configuration below can be considered as additional part for [Installation Guide](/docs/public/installation.md) +* Configuration below can be considered as additional part for [Installation Guide](../installation.md) * **Openshift 4.X** Postgres Operator limits should be set to `limits.cpu=100m` and `limits.memory=100Mi`. * In case if `siteManager.httpAuth.enabled` is set to `true`, TokenReview rights should be granted to `postgres-sa` ServiceAccount in PostgreSQL Operator namespace. * `siteManager.httpAuth.smNamespace` should be specified if custom name for site-manager NS is used. diff --git a/docs/public/features/ldap_integration.md b/docs/public/features/ldap_integration.md index 2683f64c..e07d2b06 100644 --- a/docs/public/features/ldap_integration.md +++ b/docs/public/features/ldap_integration.md @@ -18,7 +18,7 @@ It is widely used in enterprise environments for authentication, authorization, # Input Parameters -The required parameters to integrate LDAP with postgres is described in the [LDAP Configuration section](/docs/public/installation.md#ldap). +The required parameters to integrate LDAP with postgres is described in the [LDAP Configuration section](../installation.md#ldap). # AD/LDAP side configuration We have created below 2 user’s i.e. diff --git a/docs/public/features/pgBackRest.md b/docs/public/features/pgBackRest.md index 08e1cd18..8e8da7e3 100644 --- a/docs/public/features/pgBackRest.md +++ b/docs/public/features/pgBackRest.md @@ -13,7 +13,7 @@ About pgBackRest [Official docs](https://pgbackrest.org/). In our case pgBackRest included into `Sidecar` container with web server onboard and provides REST API. -![pgbackrest](/docs/public/images/features/pgbackrest.png) +![pgbackrest](../images/features/pgbackrest.png) # How to deploy diff --git a/docs/public/features/query-exporter.md b/docs/public/features/query-exporter.md index 05242317..1d0c2d61 100644 --- a/docs/public/features/query-exporter.md +++ b/docs/public/features/query-exporter.md @@ -29,7 +29,7 @@ However for managed databases these extensions must be enabled for database inst ## Migration from postgres-exporter -Please check [new queries format](/charts/patroni-services/query-exporter/query-exporter-queries.yaml) for query-exporter. +Please check [new queries format](../../../operator/charts/patroni-services/query-exporter/query-exporter-queries.yaml) for query-exporter. For custom queries two sections must be used in config: `metrics` and `queries`. Queries section includes map of queries. Each query now include next mandatory fields: @@ -58,7 +58,7 @@ This feature is used for dynamically update of queries for Query Exporter by con ## How to enable this feature -Custom queries' watcher for Query Exporter should be enabled in [deployment parameters](/docs/public/installation.md#query-exporter). +Custom queries' watcher for Query Exporter should be enabled in [deployment parameters](../installation.md#query-exporter). Namespaces list also should be defined in the deployment parameters. There are several ways of configure necessary roles: @@ -174,7 +174,7 @@ In this case query `pg_example` will be executed for all databases matching at l In postgres-operator new watchers are created for namespaces, listed in deployment parameters. These watchers react to Create, Update, Delete events for config maps with labels from `queryExporter.customQueries.labels` parameter and mandatory label -```query-exporter: custom-queries```. Config maps should contain metrics with custom queries for Query Exporter. Metrics must correspond to the [query exporter format](/charts/patroni-services/query-exporter/query-exporter-queries.yaml) and must meet [metric naming rules](https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels)). +```query-exporter: custom-queries```. Config maps should contain metrics with custom queries for Query Exporter. Metrics must correspond to the [query exporter format](../../../operator/charts/patroni-services/query-exporter/query-exporter-queries.yaml) and must meet [metric naming rules](https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels)). After the Create event, changes from created config map will be appended to `query-exporter-queries` config map. After the Modify event, changes from config map will be replaced in `query-exporter-queries` config map. After the Delete event, changes from config map will be deleted from `query-exporter-queries` config map. @@ -202,7 +202,7 @@ queryExporter: - "pg_lock_tree_query" - "connection_by_role_with_limit_query" ``` -Names of the queries can be found in [query-exporter-queries](/charts/patroni-services/query-exporter/query-exporter-queries.yaml) configmap. All metrics for excluded query will be automatically excluded. +Names of the queries can be found in [query-exporter-queries](../../../operator/charts/patroni-services/query-exporter/query-exporter-queries.yaml) configmap. All metrics for excluded query will be automatically excluded. # Self monitoring diff --git a/docs/public/installation.md b/docs/public/installation.md index e2f24fff..380f7817 100644 --- a/docs/public/installation.md +++ b/docs/public/installation.md @@ -47,7 +47,7 @@ In case of Prometheus Monitoring stack deployment, deploy user should have the r ### Disaster Recovery For more information about prerequisites for PostgreSQL in Disaster Recovery Scheme, please, follow: -[Disaster Recovery](/docs/public/features/disaster-recovery.md#prerequisites) section. +[Disaster Recovery](features/disaster-recovery.md#prerequisites) section. ## Kubernetes @@ -297,8 +297,8 @@ Patroni Core Operator allows configuration of TLS for PostgreSQL. By default, re | Parameter | Type | Mandatory | Default value | Description | |----------------------------------------------------------------|----------|-----------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | tls.enabled | bool | no | false | Indicates that TLS should be enabled or not. | -| tls.certificateSecretName | string | no | pg-cert | Specifies the name of secret with certificate in PostgreSQL namespace. See [TLS Configuration](/docs/public/features/tls-configuration.md) | -| tls.generateCerts.enabled | bool | yes | false | Specifies whether to generate SSL certificates by cert-manager or not. If `false` specified, follow [manual certificate configuration guid](/docs/public/features/tls-configuration.md#manual). | +| tls.certificateSecretName | string | no | pg-cert | Specifies the name of secret with certificate in PostgreSQL namespace. See [TLS Configuration](features/tls-configuration.md) | +| tls.generateCerts.enabled | bool | yes | false | Specifies whether to generate SSL certificates by cert-manager or not. If `false` specified, follow [manual certificate configuration guid](features/tls-configuration.md#manual). | | tls.generateCerts.duration | int | no | 365 | Specifies SSL certificate validity duration in days. The default value is 365. | | tls.generateCerts.subjectAlternativeName.additionalDnsNames | []string | no | n/a | Specifies the list of additional DNS names to be added to the "Subject Alternative Name" field of SSL certificate. If access to Postgres Service for external clients is enabled, DNS names from externalHostNames parameter must be specified in here. | | tls.generateCerts.subjectAlternativeName.additionalIpAddresses | []string | no | n/a | Specifies the list of additional IP addresses to be added to the "Subject Alternative Name" field of SSL certificate. If access to Postgres Service for external clients is enabled, IP addresses from externalHostNames parameter must be specified in here. | @@ -502,7 +502,7 @@ This sections describes all possible deploy parameters for PostgreSQL DBaaS Adap dbaas: pgHost: pg-patroni-external. ``` -For more info, please visit the following article regarding disaster recovery - [Disaster Recovery](/docs/public/features/disaster-recovery.md) +For more info, please visit the following article regarding disaster recovery - [Disaster Recovery](features/disaster-recovery.md) ## siteManager @@ -535,7 +535,7 @@ This sections describes all possible deploy parameters to run Integration Tests ## Integration tests and ATP storage -Integration test settings live under `tests` in the Helm values for **patroni-core** and **patroni-services** (see [`operator/charts/patroni-core/values.yaml`](operator/charts/patroni-core/values.yaml) and [`operator/charts/patroni-services/values.yaml`](operator/charts/patroni-services/values.yaml)). The test image is based on [qubership-docker-integration-tests](https://github.com/Netcracker/qubership-docker-integration-tests). +Integration test settings live under `tests` in the Helm values for **patroni-core** and **patroni-services** (see [`operator/charts/patroni-core/values.yaml`](../../operator/charts/patroni-core/values.yaml) and [`operator/charts/patroni-services/values.yaml`](../../operator/charts/patroni-services/values.yaml)). The test image is based on [qubership-docker-integration-tests](https://github.com/Netcracker/qubership-docker-integration-tests). ATP-related Helm values are `atpReport` (with nested `atpReport.atpStorage`), `atpReportViewUiUrl`, and `environmentName`. The chart maps them into the Custom Resource and the operator sets the usual `ATP_*` and `ENVIRONMENT_NAME` environment variables on the integration test pod. @@ -656,7 +656,7 @@ This sections describes all possible deploy parameters for Query Exporter compon | queryExporter.pgPassword | string | no | PaSsw0rDfoRExporT3r | Specifies password for postgres exporter user. | | queryExporter.maxMasterConnections | int | no | 10 | Specifies the number of simultaneous connections for master database. | | queryExporter.maxLogicalConnections | int | no | 1 | Specifies the number of simultaneous connections for non-master databases. | -| queryExporter.selfMonitorDisabled | bool | no | false | Specifies if self monitor metrics is disabled. | queryExporter.customQueries.enabled | bool | no | false | Specifies the Query Exporter custom queries feature. [Custom queries watcher](/docs/public/features/query-exporter.md#custom-queries). | +| queryExporter.selfMonitorDisabled | bool | no | false | Specifies if self monitor metrics is disabled. | queryExporter.customQueries.enabled | bool | no | false | Specifies the Query Exporter custom queries feature. [Custom queries watcher](features/query-exporter.md#custom-queries). | | queryExporter.customQueries.namespacesList | []string | no | n/a | Specifies the list of Namespaces for Query Exporter query watcher. | | queryExporter.customQueries.labels | map[string]string | no | n/a | Specifies the map of labels for config maps for watching. | | queryExporter.excludeQueries | []string | no | n/a | Specifies query list for exclusion from queries list. | queryExporter.collectionInterval | int | no | 60 | Specifies default interval in seconds to execute queries. | @@ -736,8 +736,8 @@ Postgres Operator allows configuration of TLS for supplementary and other compon | Parameter | Type | Mandatory | Default value | Description | |----------------------------------------------------------------|----------|-----------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | tls.enabled | bool | no | false | Indicates that TLS should be enabled or not. | -| tls.certificateSecretName | string | no | pg-cert | Specifies the name of secret with certificate in PostgreSQL namespace. See [TLS Configuration](/docs/features/tls-configuration.md) | -| tls.generateCerts.enabled | bool | yes | false | Specifies whether to generate SSL certificates by cert-manager or not. If `false` specified, follow [manual certificate configuration guid](/docs/features/tls-configuration.md#manual). | +| tls.certificateSecretName | string | no | pg-cert | Specifies the name of secret with certificate in PostgreSQL namespace. See [TLS Configuration](features/tls-configuration.md) | +| tls.generateCerts.enabled | bool | yes | false | Specifies whether to generate SSL certificates by cert-manager or not. If `false` specified, follow [manual certificate configuration guid](features/tls-configuration.md#manual). | | tls.generateCerts.duration | int | no | 365 | Specifies SSL certificate validity duration in days. The default value is 365. | | tls.generateCerts.subjectAlternativeName.additionalDnsNames | []string | no | n/a | Specifies the list of additional DNS names to be added to the "Subject Alternative Name" field of SSL certificate. If access to Postgres Service for external clients is enabled, DNS names from externalHostNames parameter must be specified in here. | | tls.generateCerts.subjectAlternativeName.additionalIpAddresses | []string | no | n/a | Specifies the list of additional IP addresses to be added to the "Subject Alternative Name" field of SSL certificate. If access to Postgres Service for external clients is enabled, IP addresses from externalHostNames parameter must be specified in here. | @@ -773,7 +773,7 @@ Patroni Core Operator allows configuration of TLS for PostgreSQL. By default, re ### Helm -For pure installation, please, follow [Quick Start Guide](/docs/public/quickstart.md). +For pure installation, please, follow [Quick Start Guide](quickstart.md). ## On-prem @@ -832,11 +832,11 @@ dbaas: ### DR scheme -For more information about DR scheme, please, follow [Disaster Recovery](/docs/public/features/disaster-recovery.md) document. +For more information about DR scheme, please, follow [Disaster Recovery](features/disaster-recovery.md) document. -An example of parameters for [Active Cluster](/docs/public/features/disaster-recovery.md#active-postgres-service-on-cluster-1). +An example of parameters for [Active Cluster](features/disaster-recovery.md#active-postgres-service-on-cluster-1). -An example of parameters for [Standby Cluster](/docs/public/features/disaster-recovery.md#standby-postgres-service-on-cluster-2). +An example of parameters for [Standby Cluster](features/disaster-recovery.md#standby-postgres-service-on-cluster-2). ### Non-HA scheme @@ -848,7 +848,7 @@ Same as [HA Scheme](#ha-scheme), but `patroni.replicas` parameter should be set ## Major Upgrade of PostgreSQL -For more information on how to do the Major Upgrade of PostgreSQL, please, follow [Major Upgrade](/docs/public/features/major-upgrade.md) document. +For more information on how to do the Major Upgrade of PostgreSQL, please, follow [Major Upgrade](features/major-upgrade.md) document. # Appendix diff --git a/docs/public/quickstart.md b/docs/public/quickstart.md index 280914cc..c0cb7822 100644 --- a/docs/public/quickstart.md +++ b/docs/public/quickstart.md @@ -25,9 +25,6 @@ git clone git@github.com:Netcracker/pgskipper-operator.git cd postgres-operator ``` Note that in `operator/charts` folder you can fide two separate `Helm Charts` named `patroni-core` and `patroni-services` -So for each of `Helm Chart` you can find the sample.yaml -Information of the services separation you can find in [Architecture Guide](/docs/public/architecture.md#postgres-operator) - ## Storage configuration Before install, configure `patroni.storage` in ./operator/charts/patroni-core/patroni-core-quickstart-sample.yaml and `backupDaemon.storage` properties in ./operator/charts/patroni-services/patroni-services-quickstart-sample.yaml diff --git a/operator/build/Dockerfile b/operator/build/Dockerfile index 08d9dedd..8ca93009 100644 --- a/operator/build/Dockerfile +++ b/operator/build/Dockerfile @@ -22,8 +22,7 @@ RUN --mount=type=cache,id=gomodcache,target=/go/pkg/mod \ FROM alpine:3.23 ENV OPERATOR=/usr/local/bin/postgres-operator \ - USER_UID=1001 \ - USER_NAME=postgres-operator + USER_UID=1001 # Upgrade zlib to fix CVE-2026-22184 (CRITICAL) and CVE-2026-27171 (MEDIUM) # Temporal fix, will be removed when alpine will be fixed @@ -36,6 +35,6 @@ COPY build/configs/ /opt/operator/ RUN /usr/local/bin/user_setup -ENTRYPOINT ["/usr/local/bin/entrypoint"] +ENTRYPOINT ["/usr/local/bin/postgres-operator"] USER ${USER_UID} diff --git a/operator/build/bin/entrypoint b/operator/build/bin/entrypoint deleted file mode 100755 index b8dec799..00000000 --- a/operator/build/bin/entrypoint +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -e - -# This is documented here: -# https://docs.openshift.com/container-platform/3.11/creating_images/guidelines.html#openshift-specific-guidelines - -if ! whoami >/dev/null 2>&1; then - if [ -w /etc/passwd ]; then - echo "${USER_NAME:-postgres-operator}:x:$(id -u):$(id -g):${USER_NAME:-postgres-operator} user:${HOME}:/sbin/nologin" >>/etc/passwd - fi -fi - -exec ${OPERATOR} "$@" diff --git a/operator/charts/patroni-core/templates/_helpers.tpl b/operator/charts/patroni-core/templates/_helpers.tpl index 0edc72c2..ea71085d 100644 --- a/operator/charts/patroni-core/templates/_helpers.tpl +++ b/operator/charts/patroni-core/templates/_helpers.tpl @@ -90,9 +90,9 @@ fsGroup: {{ .Values.INFRA_POSTGRES_FS_GROUP }} {{- end -}} {{- define "restricted.globalContainerSecurityContext" -}} +readOnlyRootFilesystem: true {{- if .Values.GLOBAL_SECURITY_CONTEXT }} allowPrivilegeEscalation: false -readOnlyRootFilesystem: true capabilities: drop: ["ALL"] {{- end }} diff --git a/operator/charts/patroni-core/templates/deployment.yaml b/operator/charts/patroni-core/templates/deployment.yaml index ffff1baf..7f6c004d 100644 --- a/operator/charts/patroni-core/templates/deployment.yaml +++ b/operator/charts/patroni-core/templates/deployment.yaml @@ -56,11 +56,7 @@ spec: cpu: {{ default "50m" .Values.operator.resources.requests.cpu }} memory: {{ default "50Mi" .Values.operator.resources.requests.memory }} securityContext: - {{- if .Values.GLOBAL_SECURITY_CONTEXT }} - allowPrivilegeEscalation: false - capabilities: - drop: ["ALL"] - {{- end }} + {{- include "restricted.globalContainerSecurityContext" . | nindent 12 }} volumeMounts: - name: tmp mountPath: /tmp diff --git a/operator/charts/patroni-services/templates/deployment.yaml b/operator/charts/patroni-services/templates/deployment.yaml index be167dba..19c56c6b 100644 --- a/operator/charts/patroni-services/templates/deployment.yaml +++ b/operator/charts/patroni-services/templates/deployment.yaml @@ -57,11 +57,7 @@ spec: cpu: {{ default "50m" .Values.operator.resources.requests.cpu }} memory: {{ default "50Mi" .Values.operator.resources.requests.memory }} securityContext: - {{- if .Values.GLOBAL_SECURITY_CONTEXT }} - allowPrivilegeEscalation: false - capabilities: - drop: ["ALL"] - {{- end }} + {{- include "restricted.globalContainerSecurityContext" . | nindent 12 }} volumeMounts: - name: tmp mountPath: /tmp diff --git a/operator/pkg/deployment/patroni.go b/operator/pkg/deployment/patroni.go index adfb210a..980c538a 100644 --- a/operator/pkg/deployment/patroni.go +++ b/operator/pkg/deployment/patroni.go @@ -110,7 +110,7 @@ func NewPatroniStatefulset(cr *patroniv1.PatroniCore, deploymentIdx int, cluster }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: util.Merge(patroniLabels, patroniSpec.PodLabels), + Labels: util.Merge(patroniLabels, patroniSpec.PodLabels, map[string]string{"app.kubernetes.io/name": "pg-patroni-node"}), }, Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ diff --git a/operator/pkg/reconciler/backup_daemon.go b/operator/pkg/reconciler/backup_daemon.go index 440e0079..3fc23a63 100644 --- a/operator/pkg/reconciler/backup_daemon.go +++ b/operator/pkg/reconciler/backup_daemon.go @@ -104,7 +104,7 @@ func (r *BackupDaemonReconciler) Reconcile() error { } //Adding securityContexts - backupDaemonDeployment.Spec.Template.Spec.Containers[0].SecurityContext = util.GetDefaultSecurityContext() + backupDaemonDeployment.Spec.Template.Spec.Containers[0].SecurityContext = util.GetReadOnlyContainerSecurityContext() backupDaemonDeployment.Spec.Template.Spec.Volumes = append(backupDaemonDeployment.Spec.Template.Spec.Volumes, util.GetTmpVolume()) backupDaemonDeployment.Spec.Template.Spec.Containers[0].VolumeMounts = append(backupDaemonDeployment.Spec.Template.Spec.Containers[0].VolumeMounts, util.GetTmpVolumeMount()) diff --git a/operator/pkg/reconciler/patroni.go b/operator/pkg/reconciler/patroni.go index 851cd238..8cf06932 100644 --- a/operator/pkg/reconciler/patroni.go +++ b/operator/pkg/reconciler/patroni.go @@ -491,28 +491,28 @@ func (r *PatroniReconciler) processPatroniStatefulset(cr *v1.PatroniCore, deploy } // check deployments - patroniDeployment := deployment.NewPatroniStatefulset(cr, deploymentIdx, r.cluster.ClusterName, r.cluster.PatroniTemplate, r.cluster.PostgreSQLUserConf, r.cluster.PatroniLabels) + patroniSfs := deployment.NewPatroniStatefulset(cr, deploymentIdx, r.cluster.ClusterName, r.cluster.PatroniTemplate, r.cluster.PostgreSQLUserConf, r.cluster.PatroniLabels) if cr.Spec.PrivateRegistry.Enabled { for _, name := range cr.Spec.PrivateRegistry.Names { - patroniDeployment.Spec.Template.Spec.ImagePullSecrets = append(patroniDeployment.Spec.Template.Spec.ImagePullSecrets, corev1.LocalObjectReference{Name: name}) + patroniSfs.Spec.Template.Spec.ImagePullSecrets = append(patroniSfs.Spec.Template.Spec.ImagePullSecrets, corev1.LocalObjectReference{Name: name}) } } if cr.Spec.Policies != nil { logger.Info("Policies is not empty, setting them to Patroni Statefulset") - patroniDeployment.Spec.Template.Spec.Tolerations = cr.Spec.Policies.Tolerations + patroniSfs.Spec.Template.Spec.Tolerations = cr.Spec.Policies.Tolerations } // Add Secret Hash - err = manager.AddCredHashToPodTemplate(credentials.PostgresSecretNames, &patroniDeployment.Spec.Template) + err = manager.AddCredHashToPodTemplate(credentials.PostgresSecretNames, &patroniSfs.Spec.Template) if err != nil { - logger.Error(fmt.Sprintf("can't add secret HASH to annotations for %s", patroniDeployment.Name), zap.Error(err)) + logger.Error(fmt.Sprintf("can't add secret HASH to annotations for %s", patroniSfs.Name), zap.Error(err)) return err } - if err := r.helper.CreateOrUpdateStatefulset(patroniDeployment, true); err != nil { - logger.Error(fmt.Sprintf("Cannot create or update deployment %s", patroniDeployment.Name), zap.Error(err)) + if err := r.helper.CreateOrUpdateStatefulset(patroniSfs, true); err != nil { + logger.Error(fmt.Sprintf("Cannot create or update deployment %s", patroniSfs.Name), zap.Error(err)) return err } return nil diff --git a/operator/pkg/upgrade/upgrade.go b/operator/pkg/upgrade/upgrade.go index 1eb1f962..33b68f10 100644 --- a/operator/pkg/upgrade/upgrade.go +++ b/operator/pkg/upgrade/upgrade.go @@ -427,15 +427,15 @@ func (u *Upgrade) ProceedUpgrade(cr *v1.PatroniCore, cluster *v1.PatroniClusterS logger.Info(fmt.Sprintf("Leader name is %s", leaderName)) deploymentIdx, _ := strconv.Atoi(leaderName[len(leaderName)-1:]) - patroniDeployment := deployment.NewPatroniStatefulset(cr, deploymentIdx, cluster.ClusterName, + patroniSfs := deployment.NewPatroniStatefulset(cr, deploymentIdx, cluster.ClusterName, cluster.PatroniTemplate, cluster.PostgreSQLUserConf, cluster.PatroniLabels) upgradePod := u.getUpgradePod(cr, leaderName, initDbArgs, cr.Upgrade.DockerUpgradeImage) // copy nodeSelector, Volumes, SecurityContext from Deployment - upgradePod.Spec.NodeSelector = patroniDeployment.Spec.Template.Spec.NodeSelector - upgradePod.Spec.Volumes = patroniDeployment.Spec.Template.Spec.Volumes - upgradePod.Spec.Containers[0].VolumeMounts = patroniDeployment.Spec.Template.Spec.Containers[0].VolumeMounts - upgradePod.Spec.SecurityContext = patroniDeployment.Spec.Template.Spec.SecurityContext + upgradePod.Spec.NodeSelector = patroniSfs.Spec.Template.Spec.NodeSelector + upgradePod.Spec.Volumes = patroniSfs.Spec.Template.Spec.Volumes + upgradePod.Spec.Containers[0].VolumeMounts = patroniSfs.Spec.Template.Spec.Containers[0].VolumeMounts + upgradePod.Spec.SecurityContext = patroniSfs.Spec.Template.Spec.SecurityContext // create pod and wait till completed if err := u.helper.CreatePod(upgradePod); err != nil { @@ -456,7 +456,7 @@ func (u *Upgrade) ProceedUpgrade(cr *v1.PatroniCore, cluster *v1.PatroniClusterS } // upgrade completed, apply patroni deployment - if err := u.helper.CreateOrUpdateStatefulset(patroniDeployment, true); err != nil { + if err := u.helper.CreateOrUpdateStatefulset(patroniSfs, true); err != nil { logger.Error("Can't update Patroni deployment", zap.Error(err)) return err } diff --git a/operator/pkg/util/util.go b/operator/pkg/util/util.go index a97536f5..8b127812 100644 --- a/operator/pkg/util/util.go +++ b/operator/pkg/util/util.go @@ -38,8 +38,8 @@ import ( patroniv1 "github.com/Netcracker/pgskipper-operator/api/patroni/v1" "golang.org/x/crypto/ssh" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" diff --git a/tests/robot/check_installation/check_installation.robot b/tests/robot/check_installation/check_installation.robot index aa66b625..840567d2 100644 --- a/tests/robot/check_installation/check_installation.robot +++ b/tests/robot/check_installation/check_installation.robot @@ -30,5 +30,5 @@ Check Backup-daemon Installation Correctness Test Container Hardening [Tags] backup_basic - ${exclusions}= Create Dictionary _all=CH12 + ${exclusions}= Create Dictionary _all=CH12 pg-patroni-node=CH4 Check Container Hardening exclusions=${exclusions} From 230cc5625d8b23d8a5b028dfbd0c237c6a4c7bed Mon Sep 17 00:00:00 2001 From: KryukovaPolina Date: Tue, 9 Jun 2026 11:56:02 +0300 Subject: [PATCH 4/4] fix: update test pipeline version (#480) --- .github/workflows/run_nightly_tests.yaml | 4 ++-- .github/workflows/run_tests.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run_nightly_tests.yaml b/.github/workflows/run_nightly_tests.yaml index f368fc14..a6a5e57f 100644 --- a/.github/workflows/run_nightly_tests.yaml +++ b/.github/workflows/run_nightly_tests.yaml @@ -10,12 +10,12 @@ on: jobs: Nightly-Pgskipper-Pipeline: - uses: Netcracker/qubership-test-pipelines/.github/workflows/pgskipper.yaml@c46738acd2481dcea8b4cd0bee83e3f4f4539f2c #v1.11.0 + uses: Netcracker/qubership-test-pipelines/.github/workflows/pgskipper.yaml@3e4193378da5730d7b96d3a625d22a158d5e8372 #v1.12.0 with: repository_name: ${{ github.repository }} service_branch: '${{ github.head_ref || github.ref_name }}' - pipeline_branch: 'c46738acd2481dcea8b4cd0bee83e3f4f4539f2c' #this value must match the value after '@' in 'uses' + pipeline_branch: '3e4193378da5730d7b96d3a625d22a158d5e8372' #this value must match the value after '@' in 'uses' runner_type: 'ubuntu-latest' scope: 'nightly' secrets: diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 28d0fa39..d3514461 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -28,11 +28,11 @@ jobs: Pgskipper-Pipeline: if: ${{ github.actor != 'dependabot[bot]' && github.event.pull_request.user.login != 'dependabot[bot]' }} needs: Wait-for-images - uses: Netcracker/qubership-test-pipelines/.github/workflows/pgskipper.yaml@c46738acd2481dcea8b4cd0bee83e3f4f4539f2c #v1.11.0 + uses: Netcracker/qubership-test-pipelines/.github/workflows/pgskipper.yaml@3e4193378da5730d7b96d3a625d22a158d5e8372 #v1.12.0 with: repository_name: ${{ github.repository }} service_branch: '${{ github.head_ref || github.ref_name }}' - pipeline_branch: 'c46738acd2481dcea8b4cd0bee83e3f4f4539f2c' #this value must match the value after '@' in 'uses' + pipeline_branch: '3e4193378da5730d7b96d3a625d22a158d5e8372' #this value must match the value after '@' in 'uses' secrets: AWS_S3_ACCESS_KEY_ID: ${{secrets.AWS_S3_ACCESS_KEY_ID}} AWS_S3_ACCESS_KEY_SECRET: ${{secrets.AWS_S3_ACCESS_KEY_SECRET}}