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:
@@ -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 {
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user