diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index 6ad0f095..d97c320f 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -13,12 +13,12 @@ jobs: uses: actions/checkout@v2 - name: Ensure Go Version - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: - go-version: '^1.18' + go-version-file: 'go.mod' - name: Lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v3 with: args: -p bugs -p unused --timeout=5m diff --git a/Makefile b/Makefile index 836bedc9..3d24b0de 100644 --- a/Makefile +++ b/Makefile @@ -203,8 +203,7 @@ ifeq (,$(wildcard ~/.kubebuilder/${KUBEBUILDER_VERSION})) { \ os=$$(go env GOOS) ;\ arch=$$(go env GOARCH) ;\ - curl -L https://go.kubebuilder.io/dl/${KUBEBUILDER_VERSION}/$${os}/$${arch} | tar -xz -C /tmp/ ;\ - mv /tmp/kubebuilder_${KUBEBUILDER_VERSION}_$${os}_$${arch}/bin/* ${GOBIN} ;\ + curl -L https://github.com/kubernetes-sigs/kubebuilder/releases/download/v${KUBEBUILDER_VERSION}/kubebuilder_$${os}_$${arch} -o ${GOBIN}/kubebuilder ;\ mkdir -p ~/.kubebuilder ;\ touch ~/.kubebuilder/${KUBEBUILDER_VERSION} ;\ } diff --git a/api/v1/postgres_types.go b/api/v1/postgres_types.go index 307df462..fc8fedf1 100644 --- a/api/v1/postgres_types.go +++ b/api/v1/postgres_types.go @@ -78,6 +78,11 @@ const ( defaultPostgresParamValueSSLMinProtocolVersion = "TLSv1.2" defaultPostgresParamValueSSLPreferServerCiphers = "on" defaultPostgresParamValueSSLCiphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384" + + // spiloRunAsUser the uid to use when running the spilo (postgres) image + spiloRunAsUser int64 = 101 + // spiloRunAsGroup the gid to use when running the spilo (postgres) image + spiloRunAsGroup int64 = 101 ) var ( @@ -495,7 +500,7 @@ func (p *Postgres) ToPeripheralResourceLookupKey() types.NamespacedName { } } -func (p *Postgres) ToUnstructuredZalandoPostgresql(z *zalando.Postgresql, c *corev1.ConfigMap, sc string, pgParamBlockList map[string]bool, rbs *BackupConfig, srcDB *Postgres, patroniTTL, patroniLoopWait, patroniRetryTimeout uint32) (*unstructured.Unstructured, error) { +func (p *Postgres) ToUnstructuredZalandoPostgresql(z *zalando.Postgresql, c *corev1.ConfigMap, sc string, pgParamBlockList map[string]bool, rbs *BackupConfig, srcDB *Postgres, runAsNonRoot bool, patroniTTL, patroniLoopWait, patroniRetryTimeout uint32) (*unstructured.Unstructured, error) { if z == nil { z = &zalando.Postgresql{} } @@ -612,6 +617,16 @@ func (p *Postgres) ToUnstructuredZalandoPostgresql(z *zalando.Postgresql, c *cor } } + if runAsNonRoot { + // Fix uid/gid for the spilo user + z.Spec.SpiloRunAsUser = pointer.Int64(spiloRunAsUser) + z.Spec.SpiloRunAsGroup = pointer.Int64(spiloRunAsGroup) + } else { + // Unset + z.Spec.SpiloRunAsUser = nil + z.Spec.SpiloRunAsGroup = nil + } + jsonZ, err := runtime.DefaultUnstructuredConverter.ToUnstructured(z) if err != nil { return nil, fmt.Errorf("failed to convert to unstructured zalando postgresql: %w", err) diff --git a/api/v1/postgres_types_test.go b/api/v1/postgres_types_test.go index d5b352c5..afc71aa0 100644 --- a/api/v1/postgres_types_test.go +++ b/api/v1/postgres_types_test.go @@ -229,6 +229,7 @@ func TestPostgresRestoreTimestamp_ToUnstructuredZalandoPostgresql(t *testing.T) pgParamBlockList map[string]bool rbs *BackupConfig srcDB *Postgres + runAsNonRoot bool want string wantErr bool }{ @@ -257,8 +258,9 @@ func TestPostgresRestoreTimestamp_ToUnstructuredZalandoPostgresql(t *testing.T) Description: "description", }, }, - want: time.Now().Format(time.RFC3339), // I know this is not perfect, let's just hope we always finish within the same second... - wantErr: false, + runAsNonRoot: false, + want: time.Now().Format(time.RFC3339), // I know this is not perfect, let's just hope we always finish within the same second... + wantErr: false, }, { name: "undefined timestamp initialized with current time", @@ -283,8 +285,9 @@ func TestPostgresRestoreTimestamp_ToUnstructuredZalandoPostgresql(t *testing.T) Description: "description", }, }, - want: time.Now().Format(time.RFC3339), // I know this is not perfect, let's just hope we always finish within the same second... - wantErr: false, + runAsNonRoot: false, + want: time.Now().Format(time.RFC3339), // I know this is not perfect, let's just hope we always finish within the same second... + wantErr: false, }, { name: "given timestamp is passed along", @@ -311,8 +314,9 @@ func TestPostgresRestoreTimestamp_ToUnstructuredZalandoPostgresql(t *testing.T) Description: "description", }, }, - want: "invalid but whatever", - wantErr: false, + runAsNonRoot: false, + want: "invalid but whatever", + wantErr: false, }, { name: "fail on purpose", @@ -339,8 +343,9 @@ func TestPostgresRestoreTimestamp_ToUnstructuredZalandoPostgresql(t *testing.T) Description: "description", }, }, - want: "oranges", - wantErr: true, + runAsNonRoot: false, + want: "oranges", + wantErr: true, }, } for _, tt := range tests { @@ -349,7 +354,7 @@ func TestPostgresRestoreTimestamp_ToUnstructuredZalandoPostgresql(t *testing.T) p := &Postgres{ Spec: tt.spec, } - got, _ := p.ToUnstructuredZalandoPostgresql(nil, tt.c, tt.sc, tt.pgParamBlockList, tt.rbs, tt.srcDB, 130, 10, 60) + got, _ := p.ToUnstructuredZalandoPostgresql(nil, tt.c, tt.sc, tt.pgParamBlockList, tt.rbs, tt.srcDB, tt.runAsNonRoot, 130, 10, 60) jsonZ, err := runtime.DefaultUnstructuredConverter.ToUnstructured(got) if err != nil { diff --git a/controllers/postgres_controller.go b/controllers/postgres_controller.go index 9a30704c..cca60027 100644 --- a/controllers/postgres_controller.go +++ b/controllers/postgres_controller.go @@ -71,6 +71,7 @@ type PostgresReconciler struct { SidecarsConfigMapName string EnableNetPol bool EtcdHost string + RunAsNonRoot bool PatroniTTL uint32 PatroniLoopWait uint32 PatroniRetryTimeout uint32 @@ -326,7 +327,7 @@ func (r *PostgresReconciler) createOrUpdateZalandoPostgresql(ctx context.Context return fmt.Errorf("failed to fetch zalando postgresql: %w", err) } - u, err := instance.ToUnstructuredZalandoPostgresql(nil, sidecarsCM, r.StorageClass, r.PgParamBlockList, restoreBackupConfig, restoreSouceInstance, patroniTTL, patroniLoopWait, patroniRetryTimout) + u, err := instance.ToUnstructuredZalandoPostgresql(nil, sidecarsCM, r.StorageClass, r.PgParamBlockList, restoreBackupConfig, restoreSouceInstance, r.RunAsNonRoot, patroniTTL, patroniLoopWait, patroniRetryTimout) if err != nil { return fmt.Errorf("failed to convert to unstructured zalando postgresql: %w", err) } @@ -342,7 +343,7 @@ func (r *PostgresReconciler) createOrUpdateZalandoPostgresql(ctx context.Context // Update zalando postgresql mergeFrom := client.MergeFrom(rawZ.DeepCopy()) - u, err := instance.ToUnstructuredZalandoPostgresql(rawZ, sidecarsCM, r.StorageClass, r.PgParamBlockList, restoreBackupConfig, restoreSouceInstance, patroniTTL, patroniLoopWait, patroniRetryTimout) + u, err := instance.ToUnstructuredZalandoPostgresql(rawZ, sidecarsCM, r.StorageClass, r.PgParamBlockList, restoreBackupConfig, restoreSouceInstance, r.RunAsNonRoot, patroniTTL, patroniLoopWait, patroniRetryTimout) if err != nil { return fmt.Errorf("failed to convert to unstructured zalando postgresql: %w", err) } diff --git a/main.go b/main.go index 459bc5f9..3916c0d1 100644 --- a/main.go +++ b/main.go @@ -56,6 +56,7 @@ const ( postgresletNamespaceFlg = "postgreslet-namespace" sidecarsCMNameFlg = "sidecars-configmap-name" enableNetPolFlg = "enable-netpol" + runAsNonRootFlg = "run-as-non-root" enablePodAntiaffinityFlg = "enable-pod-antiaffinity" patroniRetryTimeoutFlg = "patroni-retry-timeout" enableStandbyLeaderSelectorFlg = "enable-standby-leader-selector" @@ -78,7 +79,7 @@ func init() { func main() { var metricsAddrCtrlMgr, metricsAddrSvcMgr, partitionID, tenant, ctrlClusterKubeconfig, pspName, lbIP, storageClass, postgresImage, etcdHost, operatorImage, majorVersionUpgradeMode, postgresletNamespace, sidecarsCMName string - var enableLeaderElection, enableCRDValidation, enableNetPol, enablePodAntiaffinity, enableStandbyLeaderSelector bool + var enableLeaderElection, enableCRDValidation, enableNetPol, runAsNonRoot, enablePodAntiaffinity, enableStandbyLeaderSelector bool var portRangeStart, portRangeSize int var patroniTTL, patroniLoopWait, patroniRetryTimeout uint32 var pgParamBlockList map[string]bool @@ -159,6 +160,9 @@ func main() { viper.SetDefault(enableNetPolFlg, false) enableNetPol = viper.GetBool(enableNetPolFlg) + viper.SetDefault(runAsNonRootFlg, false) + runAsNonRoot = viper.GetBool(runAsNonRootFlg) + viper.SetDefault(enablePodAntiaffinityFlg, false) enablePodAntiaffinity = viper.GetBool(enablePodAntiaffinityFlg) @@ -199,6 +203,7 @@ func main() { postgresletNamespaceFlg, postgresletNamespace, sidecarsCMNameFlg, sidecarsCMName, enableNetPolFlg, enableNetPol, + runAsNonRootFlg, runAsNonRoot, enablePodAntiaffinityFlg, enablePodAntiaffinity, patroniRetryTimeoutFlg, patroniRetryTimeout, enableStandbyLeaderSelectorFlg, enableStandbyLeaderSelector, @@ -243,6 +248,7 @@ func main() { MajorVersionUpgradeMode: majorVersionUpgradeMode, PostgresletNamespace: postgresletNamespace, SidecarsConfigMapName: sidecarsCMName, + RunAsNonRoot: runAsNonRoot, PodAntiaffinity: enablePodAntiaffinity, } opMgr, err := operatormanager.New(svcClusterConf, "external/svc-postgres-operator.yaml", scheme, ctrl.Log.WithName("OperatorManager"), opMgrOpts) @@ -273,6 +279,7 @@ func main() { SidecarsConfigMapName: sidecarsCMName, EnableNetPol: enableNetPol, EtcdHost: etcdHost, + RunAsNonRoot: runAsNonRoot, PatroniTTL: patroniTTL, PatroniLoopWait: patroniLoopWait, PatroniRetryTimeout: patroniRetryTimeout, diff --git a/pkg/operatormanager/operatormanager.go b/pkg/operatormanager/operatormanager.go index 65de66bf..004162a8 100644 --- a/pkg/operatormanager/operatormanager.go +++ b/pkg/operatormanager/operatormanager.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/rest" + "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -47,6 +48,9 @@ const ( SidecarsCMExporterQueriesKey string = "queries.yaml" localSidecarsCMName = "postgres-sidecars-configmap" + + // operatorRunAsUser the uid to use when running the operator image + operatorRunAsUser int64 = 1000 ) // operatorPodMatchingLabels is for listing operator pods @@ -62,6 +66,7 @@ type Options struct { MajorVersionUpgradeMode string PostgresletNamespace string SidecarsConfigMapName string + RunAsNonRoot bool PodAntiaffinity bool } @@ -356,9 +361,23 @@ func (m *OperatorManager) createNewClientObject(ctx context.Context, obj client. m.log.Info("handling Deployment") if len(v.Spec.Template.Spec.Containers) != 1 { m.log.Info("Unexpected number of containers in deployment, ignoring.") - } else if m.options.OperatorImage != "" { - m.log.Info("Patching operator image", "image", m.options.OperatorImage) - v.Spec.Template.Spec.Containers[0].Image = m.options.OperatorImage + } else { + if m.options.OperatorImage != "" { + m.log.Info("Patching operator image", "image", m.options.OperatorImage) + v.Spec.Template.Spec.Containers[0].Image = m.options.OperatorImage + } + + if m.options.RunAsNonRoot { + v.Spec.Template.Spec.Containers[0].SecurityContext = &corev1.SecurityContext{ + RunAsUser: pointer.Int64(operatorRunAsUser), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), + } + } else { + // Unset + v.Spec.Template.Spec.Containers[0].SecurityContext = nil + } } err = m.Get(ctx, key, &appsv1.Deployment{}) default: @@ -414,6 +433,11 @@ func (m *OperatorManager) editConfigMap(cm *corev1.ConfigMap, namespace string, cm.Data["super_username"] = "postgres" cm.Data["replication_username"] = "standby" + // From the docs (https://postgres-operator.readthedocs.io/en/latest/reference/operator_parameters/): + // Required by cron which needs setuid. Without this parameter, certification rotation & backups will not be done + cm.Data["spilo_allow_privilege_escalation"] = "true" + cm.Data["spilo_privileged"] = "false" + cm.Data["enable_pod_antiaffinity"] = strconv.FormatBool(options.PodAntiaffinity) }