Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/resource-handler/controller/shard/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ func buildPgctldSidecar(
Resources: pool.Postgres.Resources,
RestartPolicy: &sidecarRestartPolicy,
Env: env,
SecurityContext: buildPgctldSecurityContext(image, pool.Postgres),
SecurityContext: buildPgctldSecurityContext(pool.Postgres),
VolumeMounts: volumeMounts,
StartupProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
Expand Down Expand Up @@ -591,7 +591,7 @@ func buildMultipoolerContainer(
Args: args,
Ports: buildMultipoolerContainerPorts(),
Resources: pool.Multipooler.Resources,
SecurityContext: buildMultipoolerSecurityContext(shard, pool),
SecurityContext: buildMultipoolerSecurityContext(pool),
StartupProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Expand Down
9 changes: 7 additions & 2 deletions pkg/resource-handler/controller/shard/containers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ func TestBuildMultipoolerContainer(t *testing.T) {
Resources: corev1.ResourceRequirements{},
SecurityContext: &corev1.SecurityContext{
RunAsNonRoot: ptr.To(true),
RunAsUser: ptr.To(DefaultMultipoolerUID),
RunAsGroup: ptr.To(DefaultMultipoolerGID),
},
StartupProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
Expand Down Expand Up @@ -852,8 +854,11 @@ func TestBuildPgctldSidecar(t *testing.T) {
if c.Image != "custom/pgctld:v1" {
t.Errorf("Image = %q, want %q", c.Image, "custom/pgctld:v1")
}
assert.Nil(t, c.SecurityContext.RunAsUser)
assert.Nil(t, c.SecurityContext.RunAsGroup)
// The numeric identity must not depend on the image reference: pgctld
// declares USER postgres by name, so without it RunAsNonRoot makes the
// kubelet reject the container regardless of which tag is in use.
assert.Equal(t, ptr.To(DefaultPostgresUID), c.SecurityContext.RunAsUser)
assert.Equal(t, ptr.To(DefaultPostgresGID), c.SecurityContext.RunAsGroup)
})

t.Run("with observability", func(t *testing.T) {
Expand Down
45 changes: 16 additions & 29 deletions pkg/resource-handler/controller/shard/pool_pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,10 @@ func buildPoolPodSecurityContext(poolSpec multigresv1alpha1.PoolSpec) *corev1.Po

const (
// DefaultPostgresUID and DefaultPostgresGID identify the postgres user in
// the default pgctld image. That image declares USER postgres by name, so
// Kubernetes needs the numeric identity to enforce RunAsNonRoot.
// the pgctld image. That image declares USER postgres by name, so
// Kubernetes needs the numeric identity to enforce RunAsNonRoot. This is a
// property of every pgctld build, not of any one tag, so it is applied
// whenever the spec leaves the identity unset — see effectivePgctldIdentity.
DefaultPostgresUID int64 = 999
DefaultPostgresGID int64 = 999

Expand Down Expand Up @@ -187,55 +189,47 @@ func buildContainerSecurityContext(runAsUser, runAsGroup *int64) *corev1.Securit
}

func buildPgctldSecurityContext(
image string,
config multigresv1alpha1.ContainerConfig,
) *corev1.SecurityContext {
runAsUser, runAsGroup := effectivePgctldIdentity(image, config)
runAsUser, runAsGroup := effectivePgctldIdentity(config)
return buildContainerSecurityContext(runAsUser, runAsGroup)
}

func effectivePgctldIdentity(
image string,
config multigresv1alpha1.ContainerConfig,
) (runAsUser, runAsGroup *int64) {
runAsUser = config.RunAsUser
runAsGroup = config.RunAsGroup
if image == multigresv1alpha1.DefaultPostgresImage {
if runAsUser == nil {
runAsUser = ptr.To(DefaultPostgresUID)
}
if runAsGroup == nil {
runAsGroup = ptr.To(DefaultPostgresGID)
}
if runAsUser == nil {
runAsUser = ptr.To(DefaultPostgresUID)
}
if runAsGroup == nil {
runAsGroup = ptr.To(DefaultPostgresGID)
}
return runAsUser, runAsGroup
}

func effectiveMultipoolerIdentity(
shard *multigresv1alpha1.Shard,
pool multigresv1alpha1.PoolSpec,
) (runAsUser, runAsGroup *int64) {
runAsUser = pool.Multipooler.RunAsUser
runAsGroup = pool.Multipooler.RunAsGroup
if runAsUser == nil && pool.Postgres.RunAsUser != nil {
runAsUser = ptr.To(*pool.Postgres.RunAsUser)
}
if resolvedMultipoolerImage(shard) == multigresv1alpha1.DefaultMultipoolerImage {
if runAsUser == nil {
runAsUser = ptr.To(DefaultMultipoolerUID)
}
if runAsGroup == nil {
runAsGroup = ptr.To(DefaultMultipoolerGID)
}
if runAsUser == nil {
runAsUser = ptr.To(DefaultMultipoolerUID)
}
if runAsGroup == nil {
runAsGroup = ptr.To(DefaultMultipoolerGID)
}
return runAsUser, runAsGroup
}

func buildMultipoolerSecurityContext(
shard *multigresv1alpha1.Shard,
pool multigresv1alpha1.PoolSpec,
) *corev1.SecurityContext {
runAsUser, runAsGroup := effectiveMultipoolerIdentity(shard, pool)
runAsUser, runAsGroup := effectiveMultipoolerIdentity(pool)
return buildContainerSecurityContext(runAsUser, runAsGroup)
}

Expand All @@ -259,13 +253,6 @@ func validatePoolRuntimeIdentity(pool multigresv1alpha1.PoolSpec) error {
return nil
}

func resolvedMultipoolerImage(shard *multigresv1alpha1.Shard) string {
if shard.Spec.Images.Multipooler != "" {
return string(shard.Spec.Images.Multipooler)
}
return multigresv1alpha1.DefaultMultipoolerImage
}

// buildHeadlessServiceName constructs the headless service name for DNS
// resolution. Matches the naming used by pool_service.go.
func buildHeadlessServiceName(shard *multigresv1alpha1.Shard, poolName, cellName string) string {
Expand Down
25 changes: 20 additions & 5 deletions pkg/resource-handler/controller/shard/pool_pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,10 +468,10 @@ func TestBuildPoolPod_SecurityContext(t *testing.T) {
}

func TestBuildPoolPod_FSGroupDoesNotOverrideContainerRuntimeIdentity(t *testing.T) {
t.Run("custom images retain their identity", func(t *testing.T) {
t.Run("custom images get the default identity", func(t *testing.T) {
shard := newTestShard()
shard.Spec.Images.Postgres = "example/pgctld:user-1000-group-1001"
shard.Spec.Images.Multipooler = "example/multipooler:user-1000-group-1001"
shard.Spec.Images.Postgres = "example/pgctld:custom"
shard.Spec.Images.Multipooler = "example/multipooler:custom"
pool := newTestPoolSpec()
pool.FSGroup = ptr.To(int64(2000))

Expand All @@ -480,9 +480,24 @@ func TestBuildPoolPod_FSGroupDoesNotOverrideContainerRuntimeIdentity(t *testing.
t.Fatalf("unexpected error: %v", err)
}

// Overriding the image must not drop the numeric identity. pgctld
// declares USER postgres by name, so leaving RunAsUser unset pairs
// RunAsNonRoot with a username the kubelet cannot resolve, and every
// pod fails CreateContainerConfigError. An image that genuinely runs
// as something else sets RunAsUser explicitly — see the case below.
assertPoolPodFSGroup(t, pod, 2000)
assertContainerIdentity(t, pod.Spec.InitContainers[0], nil, nil)
assertContainerIdentity(t, pod.Spec.Containers[0], nil, nil)
assertContainerIdentity(
t,
pod.Spec.InitContainers[0],
ptr.To(DefaultPostgresUID),
ptr.To(DefaultPostgresGID),
)
assertContainerIdentity(
t,
pod.Spec.Containers[0],
ptr.To(DefaultMultipoolerUID),
ptr.To(DefaultMultipoolerGID),
)
assertContainerIdentity(
t,
pod.Spec.Containers[1],
Expand Down
Loading