Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
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
197 changes: 146 additions & 51 deletions pkg/validate/security_context_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gstruct"
"golang.org/x/sys/unix"
internalapi "k8s.io/cri-api/pkg/apis"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
Expand Down Expand Up @@ -306,57 +307,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 +589,151 @@ 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; while :; do sleep 1; done"},
Linux: &runtimeapi.LinuxContainerConfig{
SecurityContext: &runtimeapi.LinuxContainerSecurityContext{
RunAsUser: &runtimeapi.Int64Value{Value: imagePredefinedGroupUID},
SupplementalGroups: []int64{supplementalGroup},
// SupplementalGroupsPolicy_Merge is default(0)
// SupplementalGroupsPolicy: runtimeapi.SupplementalGroupsPolicy_Merge,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to keep this comment here?

Copy link
Contributor Author

@everpeace everpeace Jul 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really. fixed in c45cf82

},
},
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)
g.Expect(containerStatus.User).To(PointTo(MatchFields(IgnoreExtras, Fields{
"Linux": PointTo(MatchFields(IgnoreExtras, Fields{
"Uid": Equal(imagePredefinedGroupUID),
"Gid": Equal(imagePredefinedGroupUID),
// we can not assume the order of gids
"SupplementalGroups": And(ContainElements(imagePredefinedGroupUID, supplementalGroup, imagePredefinedGroupGID), HaveLen(3)),
})),
})))
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; while :; do sleep 1; done"},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this work in place of the loop?

Suggested change
Command: []string{"sh", "-c", "id -G; while :; do sleep 1; done"},
Command: []string{"sh", "-c", "id -G; sleep infinity"},

Copy link
Member

@kwilczynski kwilczynski Jul 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realise this is a code that existed prior... hence I apologise for asking about it. However, perhaps we can make things bit better. 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

never mind. fixed in c9e3de6

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
g.Expect(containerStatus.User).To(PointTo(MatchFields(IgnoreExtras, Fields{
"Linux": PointTo(MatchFields(IgnoreExtras, Fields{
"Uid": Equal(imagePredefinedGroupUID),
"Gid": Equal(imagePredefinedGroupUID),
// we can not assume the order of gids
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would sorting the list make it a little more deterministic?

Copy link
Contributor Author

@everpeace everpeace Jul 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, fixed in 695b675. Sorting removes gstruct usage, too👍.

"SupplementalGroups": And(ContainElements(imagePredefinedGroupUID, supplementalGroup), HaveLen(2)),
})),
})))
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"}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the GroupIds() from os/user work here in lieu of fork-out to the id command? Nothing against doing this, just curious...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know GroupIds() always parse /etc/groups. So, I think GroupIds() wouldn't work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Thank you!

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