KEP-3619: Fine-grained SupplementalGroups control (#117842)

* Add `Linux{Sandbox,Container}SecurityContext.SupplementalGroupsPolicy` and `ContainerStatus.user` in cri-api

* Add `PodSecurityContext.SupplementalGroupsPolicy`, `ContainerStatus.User` and its featuregate

* Implement DropDisabledPodFields for PodSecurityContext.SupplementalGroupsPolicy and ContainerStatus.User fields

* Implement kubelet so to wire between SecurityContext.SupplementalGroupsPolicy/ContainerStatus.User and cri-api in kubelet

* Clarify `SupplementalGroupsPolicy` is an OS depdendent field.

* Make `ContainerStatus.User` is initially attached user identity to the first process in the ContainerStatus

It is because, the process identity can be dynamic if the initially attached identity
has enough privilege calling setuid/setgid/setgroups syscalls in Linux.

* Rewording suggestion applied

* Add TODO comment for updating SupplementalGroupsPolicy default value in v1.34

* Added validations for SupplementalGroupsPolicy and ContainerUser

* No need featuregate check in validation when adding new field with no default value

* fix typo: identitiy -> identity
This commit is contained in:
Shingo Omura
2024-05-30 07:40:29 +09:00
committed by GitHub
parent ee2c1ffa80
commit 552fd7e850
98 changed files with 4782 additions and 1801 deletions

View File

@@ -655,6 +655,15 @@ func dropDisabledFields(
}
}
// If the feature is disabled and not in use, drop the SupplementalGroupsPolicy field.
if !utilfeature.DefaultFeatureGate.Enabled(features.SupplementalGroupsPolicy) && !supplementalGroupsPolicyInUse(oldPodSpec) {
// Drop the field in podSpec only if SecurityContext is not nil.
// If it is nil, there is no need to set supplementalGroupsPolicy=nil (it will be nil too).
if podSpec.SecurityContext != nil {
podSpec.SecurityContext.SupplementalGroupsPolicy = nil
}
}
dropDisabledProcMountField(podSpec, oldPodSpec)
dropDisabledNodeInclusionPolicyFields(podSpec, oldPodSpec)
@@ -820,6 +829,18 @@ func dropDisabledPodStatusFields(podStatus, oldPodStatus *api.PodStatus, podSpec
podStatus.EphemeralContainerStatuses[i].VolumeMounts = nil
}
}
// drop ContainerStatus.User field to empty (disable SupplementalGroupsPolicy)
if !utilfeature.DefaultFeatureGate.Enabled(features.SupplementalGroupsPolicy) && !supplementalGroupsPolicyInUse(oldPodSpec) {
dropUserField := func(csl []api.ContainerStatus) {
for i := range csl {
csl[i].User = nil
}
}
dropUserField(podStatus.InitContainerStatuses)
dropUserField(podStatus.ContainerStatuses)
dropUserField(podStatus.EphemeralContainerStatuses)
}
}
func hostIPsInUse(podStatus *api.PodStatus) bool {
@@ -1030,6 +1051,14 @@ func hostUsersInUse(podSpec *api.PodSpec) bool {
return false
}
func supplementalGroupsPolicyInUse(podSpec *api.PodSpec) bool {
if podSpec != nil && podSpec.SecurityContext != nil && podSpec.SecurityContext.SupplementalGroupsPolicy != nil {
return true
}
return false
}
// inPlacePodVerticalScalingInUse returns true if pod spec is non-nil and ResizePolicy is set
func inPlacePodVerticalScalingInUse(podSpec *api.PodSpec) bool {
if podSpec == nil {

View File

@@ -3443,3 +3443,136 @@ func TestDropPodLifecycleSleepAction(t *testing.T) {
})
}
}
func TestDropSupplementalGroupsPolicy(t *testing.T) {
supplementalGroupsPolicyMerge := api.SupplementalGroupsPolicyMerge
podWithSupplementalGroupsPolicy := func() *api.Pod {
return &api.Pod{
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{
SupplementalGroupsPolicy: &supplementalGroupsPolicyMerge,
},
Containers: []api.Container{
{
Name: "c1",
Image: "image",
},
},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{
{
Name: "c1",
Image: "image",
User: &api.ContainerUser{
Linux: &api.LinuxContainerUser{
UID: 0,
GID: 0,
SupplementalGroups: []int64{0, 1000},
},
},
},
},
},
}
}
podWithoutSupplementalGroupsPolicy := func() *api.Pod {
return &api.Pod{
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{},
Containers: []api.Container{
{
Name: "c1",
Image: "image",
},
},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{
{
Name: "c1",
Image: "image",
},
},
},
}
}
podInfo := []struct {
description string
hasSupplementalGroupsPolicy bool
pod func() *api.Pod
}{
{
description: "with SupplementalGroupsPolicy and User",
hasSupplementalGroupsPolicy: true,
pod: podWithSupplementalGroupsPolicy,
},
{
description: "without SupplementalGroupsPolicy and User",
hasSupplementalGroupsPolicy: false,
pod: podWithoutSupplementalGroupsPolicy,
},
{
description: "is nil",
hasSupplementalGroupsPolicy: false,
pod: func() *api.Pod { return nil },
},
}
for _, enabled := range []bool{true, false} {
for _, oldPodInfo := range podInfo {
for _, newPodInfo := range podInfo {
oldPodHasSupplementalGroupsPolicy, oldPod := oldPodInfo.hasSupplementalGroupsPolicy, oldPodInfo.pod()
newPodHasSupplementalGroupsPolicy, newPod := newPodInfo.hasSupplementalGroupsPolicy, newPodInfo.pod()
if newPod == nil {
continue
}
t.Run(
fmt.Sprintf(
"feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description,
),
func(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SupplementalGroupsPolicy, enabled)
var oldPodSpec *api.PodSpec
var oldPodStatus *api.PodStatus
if oldPod != nil {
oldPodSpec = &oldPod.Spec
oldPodStatus = &oldPod.Status
}
dropDisabledFields(&newPod.Spec, nil, oldPodSpec, nil)
dropDisabledPodStatusFields(&newPod.Status, oldPodStatus, &newPod.Spec, oldPodSpec)
// old pod should never be changed
if !reflect.DeepEqual(oldPod, oldPodInfo.pod()) {
t.Errorf("old pod changed: %v", cmp.Diff(oldPod, oldPodInfo.pod()))
}
switch {
case enabled || oldPodHasSupplementalGroupsPolicy:
// new pod shouldn't change if feature enabled or if old pod has SupplementalGroupsPolicy and User set
if !reflect.DeepEqual(newPod, newPodInfo.pod()) {
t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod()))
}
case newPodHasSupplementalGroupsPolicy:
// new pod should be changed
if reflect.DeepEqual(newPod, newPodInfo.pod()) {
t.Errorf("new pod was not changed")
}
// new pod should not have SupplementalGroupsPolicy and User fields
if !reflect.DeepEqual(newPod, podWithoutSupplementalGroupsPolicy()) {
t.Errorf("new pod has SupplementalGroups and User: %v", cmp.Diff(newPod, podWithoutSupplementalGroupsPolicy()))
}
default:
// new pod should not need to be changed
if !reflect.DeepEqual(newPod, newPodInfo.pod()) {
t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod()))
}
}
},
)
}
}
}
}