From a8feccc677d8f5a687d30dd3255fe6033c7c8348 Mon Sep 17 00:00:00 2001 From: ermakov-oleg Date: Fri, 13 Feb 2026 15:36:19 +0100 Subject: [PATCH] feat: set hardened default SecurityContext for sidecar containers Signed-off-by: ermakov-oleg --- internal/cnpgi/operator/lifecycle.go | 11 ++++++++-- internal/cnpgi/operator/lifecycle_test.go | 20 +++++++++++++++---- internal/pgbackrest/archiver/command.go | 2 ++ internal/pgbackrest/archiver/command_test.go | 2 +- internal/pgbackrest/backup/backup_test.go | 2 +- internal/pgbackrest/command/commandbuilder.go | 9 +++++++-- .../pgbackrest/command/commandbuilder_test.go | 4 ++-- internal/pgbackrest/restorer/backup_test.go | 1 + 8 files changed, 39 insertions(+), 12 deletions(-) diff --git a/internal/cnpgi/operator/lifecycle.go b/internal/cnpgi/operator/lifecycle.go index 6b27e3d4..44979c6c 100644 --- a/internal/cnpgi/operator/lifecycle.go +++ b/internal/cnpgi/operator/lifecycle.go @@ -148,8 +148,15 @@ func (impl LifecycleImplementation) calculateSidecarSecurityContext( return archive.Spec.InstanceSidecarConfiguration.SecurityContext } - contextLogger.Info("Security context definition not found in the archive object, using default (no restrictions).") - return nil + contextLogger.Info("Security context definition not found in the archive object, using hardened default.") + return &corev1.SecurityContext{ + AllowPrivilegeEscalation: ptr.To(false), + RunAsNonRoot: ptr.To(true), + Privileged: ptr.To(false), + ReadOnlyRootFilesystem: ptr.To(true), + SeccompProfile: &corev1.SeccompProfile{Type: corev1.SeccompProfileTypeRuntimeDefault}, + Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, + } } func (impl LifecycleImplementation) getArchives( diff --git a/internal/cnpgi/operator/lifecycle_test.go b/internal/cnpgi/operator/lifecycle_test.go index 6e5ef2fc..a04467d4 100644 --- a/internal/cnpgi/operator/lifecycle_test.go +++ b/internal/cnpgi/operator/lifecycle_test.go @@ -236,12 +236,18 @@ var _ = Describe("LifecycleImplementation", func() { ctx = context.Background() }) - It("returns nil when archive is nil", func() { + It("returns hardened default when archive is nil", func() { result := lifecycleImpl.calculateSidecarSecurityContext(ctx, nil) - Expect(result).To(BeNil()) + Expect(result).ToNot(BeNil()) + Expect(result.AllowPrivilegeEscalation).To(Equal(ptr.To(false))) + Expect(result.RunAsNonRoot).To(Equal(ptr.To(true))) + Expect(result.ReadOnlyRootFilesystem).To(Equal(ptr.To(true))) + Expect(result.Privileged).To(Equal(ptr.To(false))) + Expect(result.SeccompProfile.Type).To(Equal(corev1.SeccompProfileTypeRuntimeDefault)) + Expect(result.Capabilities.Drop).To(Equal([]corev1.Capability{"ALL"})) }) - It("returns nil when archive has no security context", func() { + It("returns hardened default when archive has no security context", func() { archive := &pgbackrestv1.Archive{ Spec: pgbackrestv1.ArchiveSpec{ InstanceSidecarConfiguration: pgbackrestv1.InstanceSidecarConfiguration{ @@ -251,7 +257,13 @@ var _ = Describe("LifecycleImplementation", func() { } result := lifecycleImpl.calculateSidecarSecurityContext(ctx, archive) - Expect(result).To(BeNil()) + Expect(result).ToNot(BeNil()) + Expect(result.AllowPrivilegeEscalation).To(Equal(ptr.To(false))) + Expect(result.RunAsNonRoot).To(Equal(ptr.To(true))) + Expect(result.ReadOnlyRootFilesystem).To(Equal(ptr.To(true))) + Expect(result.Privileged).To(Equal(ptr.To(false))) + Expect(result.SeccompProfile.Type).To(Equal(corev1.SeccompProfileTypeRuntimeDefault)) + Expect(result.Capabilities.Drop).To(Equal([]corev1.Capability{"ALL"})) }) It("returns configured security context when present", func() { diff --git a/internal/pgbackrest/archiver/command.go b/internal/pgbackrest/archiver/command.go index cc51a163..b70e4db5 100644 --- a/internal/pgbackrest/archiver/command.go +++ b/internal/pgbackrest/archiver/command.go @@ -143,6 +143,8 @@ func (archiver *WALArchiver) PgbackrestWalArchiveOptions( if err != nil { return nil, err } + // Disable file logging because sidecar containers use readOnlyRootFilesystem by default. + options = append(options, "--log-level-file", "off") serverName := clusterName if len(configuration.Stanza) != 0 { diff --git a/internal/pgbackrest/archiver/command_test.go b/internal/pgbackrest/archiver/command_test.go index 50b22cb8..4c4b9696 100644 --- a/internal/pgbackrest/archiver/command_test.go +++ b/internal/pgbackrest/archiver/command_test.go @@ -72,7 +72,7 @@ var _ = Describe("pgbackrestWalArchiveOptions", func() { Expect(strings.Join(options, " ")). To( Equal( - "--compress-type gzip --buffer-size=5MB --io-timeout=60 --repo1-type s3 --repo1-s3-bucket bucket-name --repo1-path / --stanza test-cluster", + "--compress-type gzip --buffer-size=5MB --io-timeout=60 --repo1-type s3 --repo1-s3-bucket bucket-name --repo1-path / --log-level-file off --stanza test-cluster", )) }) }) diff --git a/internal/pgbackrest/backup/backup_test.go b/internal/pgbackrest/backup/backup_test.go index 2df9921b..727bb398 100644 --- a/internal/pgbackrest/backup/backup_test.go +++ b/internal/pgbackrest/backup/backup_test.go @@ -56,7 +56,7 @@ var _ = Describe("GetPgbackrestBackupOptions", func() { Expect(strings.Join(options, " ")). To( Equal( - fmt.Sprintf("backup --annotation %s=%s --repo1-type s3 --repo1-s3-bucket bucket-name --repo1-path / --pg1-path %s --pg1-user postgres --pg1-socket-path /controller/run/ --log-level-stderr warn --log-level-console off --stanza %s --lock-path /controller/tmp/pgbackrest --no-archive-check", pgbackrestCatalog.BackupNameAnnotation, backupName, pgDataDir, stanza), + fmt.Sprintf("backup --annotation %s=%s --repo1-type s3 --repo1-s3-bucket bucket-name --repo1-path / --pg1-path %s --pg1-user postgres --pg1-socket-path /controller/run/ --log-level-stderr warn --log-level-console off --log-level-file off --stanza %s --lock-path /controller/tmp/pgbackrest --no-archive-check", pgbackrestCatalog.BackupNameAnnotation, backupName, pgDataDir, stanza), )) }) diff --git a/internal/pgbackrest/command/commandbuilder.go b/internal/pgbackrest/command/commandbuilder.go index 23f1a084..3348c603 100644 --- a/internal/pgbackrest/command/commandbuilder.go +++ b/internal/pgbackrest/command/commandbuilder.go @@ -42,6 +42,8 @@ func CloudWalRestoreOptions( if err != nil { return nil, err } + // Disable file logging because sidecar containers use readOnlyRootFilesystem by default. + options = append(options, "--log-level-file", "off") stanza := clusterName if len(configuration.Stanza) != 0 { @@ -241,19 +243,22 @@ func AppendLogOptionsFromConfiguration( } // appendLogOptions takes an options array and adds the stanza-specific pgbackrest -// options required for all operations connecting to the database +// options required for all operations connecting to the database. func appendLogOptions( _ context.Context, options []string, ) ([]string, error) { // TODO: Those options likely shouldn't be hardcoded. - // TODO: Maybe configure log path to a writable directory? options = append( options, "--log-level-stderr", "warn", "--log-level-console", "off", + // Disable file logging because the default path (/var/log/pgbackrest/) is not + // writable when readOnlyRootFilesystem is enabled in the SecurityContext. + "--log-level-file", + "off", ) return options, nil diff --git a/internal/pgbackrest/command/commandbuilder_test.go b/internal/pgbackrest/command/commandbuilder_test.go index 0f6ce4bd..919e3270 100644 --- a/internal/pgbackrest/command/commandbuilder_test.go +++ b/internal/pgbackrest/command/commandbuilder_test.go @@ -45,7 +45,7 @@ var _ = Describe("pgbackrestWalRestoreOptions", func() { Expect(strings.Join(options, " ")). To( Equal( - "--repo1-type s3 --repo1-s3-bucket bucket-name --repo1-path / --pg1-path /var/lib/postgres/pgdata --stanza test-cluster", + "--repo1-type s3 --repo1-s3-bucket bucket-name --repo1-path / --pg1-path /var/lib/postgres/pgdata --log-level-file off --stanza test-cluster", )) }) @@ -59,7 +59,7 @@ var _ = Describe("pgbackrestWalRestoreOptions", func() { Expect(strings.Join(options, " ")). To( Equal( - "--repo1-type s3 --repo1-s3-bucket bucket-name --repo1-path / --pg1-path /var/lib/postgres/pgdata --stanza test-cluster --protocol-timeout=60", + "--repo1-type s3 --repo1-s3-bucket bucket-name --repo1-path / --pg1-path /var/lib/postgres/pgdata --log-level-file off --stanza test-cluster --protocol-timeout=60", )) }) }) diff --git a/internal/pgbackrest/restorer/backup_test.go b/internal/pgbackrest/restorer/backup_test.go index 807680fd..1a6dda47 100644 --- a/internal/pgbackrest/restorer/backup_test.go +++ b/internal/pgbackrest/restorer/backup_test.go @@ -57,6 +57,7 @@ var _ = Describe("GetPgbackrestRestoreOptions", func() { ContainSubstring("--pg1-path %s", pgDataDir), ContainSubstring("--log-level-stderr warn"), ContainSubstring("--log-level-console off"), + ContainSubstring("--log-level-file off"), ContainSubstring("--stanza %s", stanza), ContainSubstring("restore --set %s", backupName), ),