Skip to content

Commit

Permalink
Merge pull request kubernetes-sigs#1438 from everpeace/kep-3619-Suppl…
Browse files Browse the repository at this point in the history
…ementalGroupsPolicy

KEP-3619: Fine-grained SupplementalGroups control
  • Loading branch information
k8s-ci-robot authored Jul 23, 2024
2 parents ac17e9b + c9e3de6 commit ee58080
Show file tree
Hide file tree
Showing 6 changed files with 887 additions and 538 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ require (
k8s.io/api v0.31.0-beta.0
k8s.io/apimachinery v0.31.0-beta.0
k8s.io/client-go v0.31.0-beta.0
k8s.io/cri-api v0.31.0-beta.0
k8s.io/cri-api v0.31.0-beta.0.0.20240716205706-865479a3e1b3
k8s.io/cri-client v0.31.0-beta.0
k8s.io/klog/v2 v2.130.1
k8s.io/kubectl v0.30.3
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,8 @@ k8s.io/client-go v0.31.0-beta.0 h1:op39m8L2YX8cUVTj8Fj+SD09axcxwPbFlipM3x+11fc=
k8s.io/client-go v0.31.0-beta.0/go.mod h1:ZTCtLpZyZDJBji9GGwrzKjR60/lVXvm8WLdgTEqPRw4=
k8s.io/component-base v0.31.0-beta.0 h1:Pw4OEeykCxfs8fTCslHEWggbNe/24W4TYVIn1VEdkjE=
k8s.io/component-base v0.31.0-beta.0/go.mod h1:mjY7SWAP/NhFiwUkdT+gzhGDI/cBqB/Pi5tF443aikE=
k8s.io/cri-api v0.31.0-beta.0 h1:U+E5RaGMtR1TLsJvaxb0RCVRU9ZcDPrC5EiCxb1uN1s=
k8s.io/cri-api v0.31.0-beta.0/go.mod h1:Po3TMAYH/+KrZabi7QiwQI4a692oZcUOUThd/rqwxrI=
k8s.io/cri-api v0.31.0-beta.0.0.20240716205706-865479a3e1b3 h1:Ajq0xvhs7HDrZVLgqUenylY8i4BkaazJzfeM+fh63AU=
k8s.io/cri-api v0.31.0-beta.0.0.20240716205706-865479a3e1b3/go.mod h1:Po3TMAYH/+KrZabi7QiwQI4a692oZcUOUThd/rqwxrI=
k8s.io/cri-client v0.31.0-beta.0 h1:VIq3WTdpshrNkbM/zXmOedEValrOsSseqrLRXeK/s/k=
k8s.io/cri-client v0.31.0-beta.0/go.mod h1:qtduCKkjfx6kUepnUL6vyROfA43bVpmnIpD4pSw4JZo=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
Expand Down
198 changes: 147 additions & 51 deletions pkg/validate/security_context_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,57 +306,6 @@ var _ = framework.KubeDescribe("Security Context", func() {
Expect(groups).To(ContainElement("5678"))
})

It("if the container's primary UID belongs to some groups in the image, runtime should add SupplementalGroups to them", func() {
By("create pod")
podID, podConfig, podLogDir = createPodSandboxWithLogDirectory(rc)

By("create container for security context SupplementalGroups")
supplementalGroup := int64(1234)
containerName := "container-with-SupplementalGroups-and-predefined-group-image-test-" + framework.NewUUID()
logPath := containerName + ".log"
containerConfig := &runtimeapi.ContainerConfig{
Metadata: framework.BuildContainerMetadata(containerName, framework.DefaultAttempt),
Image: &runtimeapi.ImageSpec{Image: testImagePreDefinedGroup},
Command: []string{"sh", "-c", "id -G; while :; do sleep 1; done"},
Linux: &runtimeapi.LinuxContainerConfig{
SecurityContext: &runtimeapi.LinuxContainerSecurityContext{
RunAsUser: &runtimeapi.Int64Value{Value: imagePredefinedGroupUID},
SupplementalGroups: []int64{supplementalGroup},
},
},
LogPath: logPath,
}
containerID := framework.CreateContainer(rc, ic, containerConfig, podID, podConfig)

By("start container")
startContainer(rc, containerID)
Eventually(func(g Gomega) {
g.Expect(getContainerStatus(rc, containerID).State).To(Equal(runtimeapi.ContainerState_CONTAINER_RUNNING))
g.Expect(parseLogLine(podConfig, logPath)).NotTo(BeEmpty())
}, time.Minute, time.Second*4).Should(Succeed())

// In testImagePreDefinedGroup,
// - its default user is default-user(uid=1000)
// - default-user belongs to group-defined-in-image(gid=50000)
//
// thus, supplementary group of the container processes should be
// - 1000: self
// - 1234: SupplementalGroups
// - 50000: groups define in the container image
//
// $ id -G
// 1000 1234 5678 50000
expectedOutput := fmt.Sprintf("%d %d %d\n", imagePredefinedGroupUID, supplementalGroup, imagePredefinedGroupGID)

By("verify groups for the first process of the container")
verifyLogContents(podConfig, logPath, expectedOutput, stdoutType)

By("verify groups for 'exec'-ed process of container")
command := []string{"id", "-G"}
o := execSyncContainer(rc, containerID, command)
Expect(o).To(BeEquivalentTo(expectedOutput))
})

It("runtime should support RunAsUser", func() {
By("create pod")
podID, podConfig = framework.CreatePodSandboxForContainer(rc)
Expand Down Expand Up @@ -639,6 +588,153 @@ var _ = framework.KubeDescribe("Security Context", func() {
})
})

Context("SupplementalGroupsPolicy", func() {
BeforeEach(func(ctx context.Context) {
By("skip if the runtime does not support SupplementalGroupsPolicy")
statusResponse, err := rc.Status(ctx, false)
Expect(err).NotTo(HaveOccurred())
if !(statusResponse.Features != nil && statusResponse.Features.SupplementalGroupsPolicy) {
Skip("The runtime does not support SupplementalGroupsPolicy feature")
}
})

When("SupplementalGroupsPolicy=Merge (Default)", func() {
It("if the container's primary UID belongs to some groups in the image, runtime should add SupplementalGroups to them", func() {
By("create pod")
podID, podConfig, podLogDir = createPodSandboxWithLogDirectory(rc)

By("create container for security context SupplementalGroups")
supplementalGroup := int64(1234)
containerName := "container-with-SupplementalGroupsPolicyMerge-" + framework.NewUUID()
logPath := containerName + ".log"
containerConfig := &runtimeapi.ContainerConfig{
Metadata: framework.BuildContainerMetadata(containerName, framework.DefaultAttempt),
Image: &runtimeapi.ImageSpec{Image: testImagePreDefinedGroup},
Command: []string{"sh", "-c", "id -G; sleep infinity"},
Linux: &runtimeapi.LinuxContainerConfig{
SecurityContext: &runtimeapi.LinuxContainerSecurityContext{
RunAsUser: &runtimeapi.Int64Value{Value: imagePredefinedGroupUID},
SupplementalGroups: []int64{supplementalGroup},
},
},
LogPath: logPath,
}
containerID := framework.CreateContainer(rc, ic, containerConfig, podID, podConfig)

By("start container")
startContainer(rc, containerID)

Eventually(func(g Gomega) {
containerStatus := getContainerStatus(rc, containerID)
g.Expect(containerStatus.State).To(Equal(runtimeapi.ContainerState_CONTAINER_RUNNING))
// In testImagePreDefinedGroup,
// - its default user is default-user(uid=1000)
// - default-user belongs to group-defined-in-image(gid=50000) in /etc/group
// And, SupplementalGroupsPolicy is Merge(default)
//
// Thus, firstly attached process identity of the first container processes should be
// - uid: 1000 (RunAsUser)
// - gid: 1000 (default group for uid=1000)
// - supplementary groups
// - 1000: self
// - 1234: SupplementalGroups
// - 50000: groups defined in the container image (/etc/group)
if containerStatus.User != nil && containerStatus.User.Linux != nil {
slices.Sort(containerStatus.User.Linux.SupplementalGroups)
}
g.Expect(containerStatus.User).To(BeEquivalentTo(&runtimeapi.ContainerUser{
Linux: &runtimeapi.LinuxContainerUser{
Uid: imagePredefinedGroupUID,
Gid: imagePredefinedGroupUID,
SupplementalGroups: []int64{imagePredefinedGroupUID, supplementalGroup, imagePredefinedGroupGID},
},
}))
g.Expect(parseLogLine(podConfig, logPath)).NotTo(BeEmpty())
}, time.Minute, time.Second*4).Should(Succeed())

// $ id -G
// 1000 1234 50000
expectedOutput := fmt.Sprintf("%d %d %d\n", imagePredefinedGroupUID, supplementalGroup, imagePredefinedGroupGID)

By("verify groups for the first process of the container")
verifyLogContents(podConfig, logPath, expectedOutput, stdoutType)

By("verify groups for 'exec'-ed process of container")
command := []string{"id", "-G"}
o := execSyncContainer(rc, containerID, command)
Expect(o).To(BeEquivalentTo(expectedOutput))
})
})
When("SupplementalGroupsPolicy=Strict", func() {
It("even if the container's primary UID belongs to some groups in the image, runtime should not add SupplementalGroups to them", func() {
By("create pod")
podID, podConfig, podLogDir = createPodSandboxWithLogDirectory(rc)

By("create container for security context SupplementalGroups")
supplementalGroup := int64(1234)
containerName := "container-with-SupplementalGroupsPolicyMerge-" + framework.NewUUID()
logPath := containerName + ".log"
containerConfig := &runtimeapi.ContainerConfig{
Metadata: framework.BuildContainerMetadata(containerName, framework.DefaultAttempt),
Image: &runtimeapi.ImageSpec{Image: testImagePreDefinedGroup},
Command: []string{"sh", "-c", "id -G; sleep infinity"},
Linux: &runtimeapi.LinuxContainerConfig{
SecurityContext: &runtimeapi.LinuxContainerSecurityContext{
RunAsUser: &runtimeapi.Int64Value{Value: imagePredefinedGroupUID},
SupplementalGroups: []int64{supplementalGroup},
SupplementalGroupsPolicy: runtimeapi.SupplementalGroupsPolicy_Strict,
},
},
LogPath: logPath,
}
containerID := framework.CreateContainer(rc, ic, containerConfig, podID, podConfig)

By("start container")
startContainer(rc, containerID)

Eventually(func(g Gomega) {
containerStatus := getContainerStatus(rc, containerID)
g.Expect(containerStatus.State).To(Equal(runtimeapi.ContainerState_CONTAINER_RUNNING))
// In testImagePreDefinedGroup,
// - its default user is default-user(uid=1000)
// - default-user belongs to group-defined-in-image(gid=50000) in /etc/group
// And, SupplementalGroupsPolicy is Strict
//
// Thus, firstly attached process identity of the first container processes should be
// (5000(defined in /etc/group) is not appended to supplementary groups)
// - uid: 1000 (RunAsUser)
// - gid: 1000 (default group for uid=1000)
// - supplementary groups
// - 1000: self
// - 1234: SupplementalGroups
if containerStatus.User != nil && containerStatus.User.Linux != nil {
slices.Sort(containerStatus.User.Linux.SupplementalGroups)
}
g.Expect(containerStatus.User).To(BeEquivalentTo(&runtimeapi.ContainerUser{
Linux: &runtimeapi.LinuxContainerUser{
Uid: imagePredefinedGroupUID,
Gid: imagePredefinedGroupUID,
SupplementalGroups: []int64{imagePredefinedGroupUID, supplementalGroup},
},
}))
g.Expect(parseLogLine(podConfig, logPath)).NotTo(BeEmpty())
}, time.Minute, time.Second*4).Should(Succeed())

// $ id -G
// 1000 1234
expectedOutput := fmt.Sprintf("%d %d\n", imagePredefinedGroupUID, supplementalGroup)

By("verify groups for the first process of the container")
verifyLogContents(podConfig, logPath, expectedOutput, stdoutType)

By("verify groups for 'exec'-ed process of container")
command := []string{"id", "-G"}
o := execSyncContainer(rc, containerID, command)
Expect(o).To(BeEquivalentTo(expectedOutput))
})
})
})

// TODO(random-liu): We should set apparmor to unconfined in seccomp test to prevent
// them from interfering with each other.
Context("SeccompProfilePath", func() {
Expand Down
Loading

0 comments on commit ee58080

Please sign in to comment.