Merge pull request #99023 from verb/1.21-securitycontext

Allow setting securityContext in ephemeral containers
This commit is contained in:
Kubernetes Prow Robot 2021-07-09 20:50:46 -07:00 committed by GitHub
commit e799d7b191
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 75 additions and 16 deletions

View File

@ -5523,7 +5523,7 @@
},
"securityContext": {
"$ref": "#/definitions/io.k8s.api.core.v1.SecurityContext",
"description": "SecurityContext is not allowed for ephemeral containers."
"description": "Optional: SecurityContext defines the security options the ephemeral container should be run with. If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext."
},
"startupProbe": {
"$ref": "#/definitions/io.k8s.api.core.v1.Probe",

View File

@ -1365,12 +1365,12 @@ func TestDropEphemeralContainers(t *testing.T) {
pod func() *api.Pod
}{
{
description: "has subpaths",
description: "has ephemeral containers",
hasEphemeralContainers: true,
pod: podWithEphemeralContainers,
},
{
description: "does not have subpaths",
description: "does not have ephemeral containers",
hasEphemeralContainers: false,
pod: podWithoutEphemeralContainers,
},

View File

@ -3161,7 +3161,8 @@ type EphemeralContainerCommon struct {
TerminationMessagePolicy TerminationMessagePolicy
// Required: Policy for pulling images for this container
ImagePullPolicy PullPolicy
// SecurityContext is not allowed for ephemeral containers.
// Optional: SecurityContext defines the security options the ephemeral container should be run with.
// If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext.
// +optional
SecurityContext *SecurityContext

View File

@ -84,6 +84,7 @@ var allowedEphemeralContainerFields = map[string]bool{
"TerminationMessagePath": true,
"TerminationMessagePolicy": true,
"ImagePullPolicy": true,
"SecurityContext": true,
"Stdin": true,
"StdinOnce": true,
"TTY": true,

View File

@ -6134,7 +6134,7 @@ func TestValidateEphemeralContainers(t *testing.T) {
TargetContainerName: "ctr",
},
},
"All Whitelisted Fields": {
"All allowed Fields": {
{
EphemeralContainerCommon: core.EphemeralContainerCommon{
@ -6160,9 +6160,14 @@ func TestValidateEphemeralContainers(t *testing.T) {
TerminationMessagePath: "/dev/termination-log",
TerminationMessagePolicy: "File",
ImagePullPolicy: "IfNotPresent",
Stdin: true,
StdinOnce: true,
TTY: true,
SecurityContext: &core.SecurityContext{
Capabilities: &core.Capabilities{
Add: []core.Capability{"SYS_ADMIN"},
},
},
Stdin: true,
StdinOnce: true,
TTY: true,
},
},
},
@ -6235,7 +6240,7 @@ func TestValidateEphemeralContainers(t *testing.T) {
field.Error{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[0].targetContainerName"},
},
{
"Container uses non-whitelisted field: Lifecycle",
"Container uses disallowed field: Lifecycle",
[]core.EphemeralContainer{
{
EphemeralContainerCommon: core.EphemeralContainerCommon{
@ -6254,7 +6259,7 @@ func TestValidateEphemeralContainers(t *testing.T) {
field.Error{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"},
},
{
"Container uses non-whitelisted field: LivenessProbe",
"Container uses disallowed field: LivenessProbe",
[]core.EphemeralContainer{
{
EphemeralContainerCommon: core.EphemeralContainerCommon{
@ -6274,7 +6279,7 @@ func TestValidateEphemeralContainers(t *testing.T) {
field.Error{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].livenessProbe"},
},
{
"Container uses non-whitelisted field: Ports",
"Container uses disallowed field: Ports",
[]core.EphemeralContainer{
{
EphemeralContainerCommon: core.EphemeralContainerCommon{
@ -6291,7 +6296,7 @@ func TestValidateEphemeralContainers(t *testing.T) {
field.Error{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].ports"},
},
{
"Container uses non-whitelisted field: ReadinessProbe",
"Container uses disallowed field: ReadinessProbe",
[]core.EphemeralContainer{
{
EphemeralContainerCommon: core.EphemeralContainerCommon{
@ -6310,7 +6315,7 @@ func TestValidateEphemeralContainers(t *testing.T) {
field.Error{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].readinessProbe"},
},
{
"Container uses non-whitelisted field: Resources",
"Container uses disallowed field: Resources",
[]core.EphemeralContainer{
{
EphemeralContainerCommon: core.EphemeralContainerCommon{

View File

@ -104,6 +104,8 @@ func TestMutatePodNonmutating(t *testing.T) {
}
func TestMutateContainerNonmutating(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
untrue := false
tests := []struct {
security *api.SecurityContext
@ -120,6 +122,11 @@ func TestMutateContainerNonmutating(t *testing.T) {
Containers: []api.Container{{
SecurityContext: tc.security,
}},
EphemeralContainers: []api.EphemeralContainer{{
EphemeralContainerCommon: api.EphemeralContainerCommon{
SecurityContext: tc.security,
},
}},
},
}
}
@ -546,6 +553,8 @@ func allowFlexVolumesPSP(allowAllFlexVolumes, allowAllVolumes bool) *policy.PodS
}
func TestValidateContainerFailures(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
// fail user strategy
failUserPSP := defaultPSP()
uid := int64(999)
@ -689,6 +698,13 @@ func TestValidateContainerFailures(t *testing.T) {
errs := provider.ValidatePod(test.pod)
require.NotEmpty(t, errs, "expected validation failure but did not receive errors")
assert.Contains(t, errs[0].Error(), test.expectedError, "unexpected error")
// We want EphemeralContainers to behave the same as regular containers, so move the
// containers to ephemeralContainers and validate again.
ecPod := moveContainersToEphemeral(test.pod)
errs = provider.ValidatePod(ecPod)
require.NotEmpty(t, errs, "expected validation failure for ephemeral containers but did not receive errors")
assert.Contains(t, errs[0].Error(), test.expectedError, "unexpected error")
})
}
}
@ -1062,6 +1078,8 @@ func TestValidatePodSuccess(t *testing.T) {
}
func TestValidateContainerSuccess(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
// success user strategy
userPSP := defaultPSP()
uid := int64(999)
@ -1221,6 +1239,12 @@ func TestValidateContainerSuccess(t *testing.T) {
require.NoError(t, err, "unable to create provider")
errs := provider.ValidatePod(test.pod)
assert.Empty(t, errs, "expected validation pass but received errors")
// We want EphemeralContainers to behave the same as regular containers, so move the
// containers to ephemeralContainers and validate again.
ecPod := moveContainersToEphemeral(test.pod)
errs = provider.ValidatePod(ecPod)
assert.Empty(t, errs, "expected validation pass for ephemeral containers but received errors")
})
}
}
@ -1377,6 +1401,17 @@ func defaultV1Pod() *v1.Pod {
}
}
func moveContainersToEphemeral(in *api.Pod) *api.Pod {
out := in.DeepCopy()
for _, c := range out.Spec.Containers {
out.Spec.EphemeralContainers = append(out.Spec.EphemeralContainers, api.EphemeralContainer{
EphemeralContainerCommon: api.EphemeralContainerCommon(c),
})
}
out.Spec.Containers = nil
return out
}
// TestValidateAllowedVolumes will test that for every field of VolumeSource we can create
// a pod with that type of volume and deny it, accept it explicitly, or accept it with
// the FSTypeAll wildcard.
@ -1490,6 +1525,8 @@ func TestValidateProjectedVolume(t *testing.T) {
}
func TestAllowPrivilegeEscalation(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
ptr := pointer.BoolPtr
tests := []struct {
pspAPE bool // PSP AllowPrivilegeEscalation
@ -1528,6 +1565,7 @@ func TestAllowPrivilegeEscalation(t *testing.T) {
t.Run(fmt.Sprintf("pspAPE:%t_pspDAPE:%s_podAPE:%s", test.pspAPE, fmtPtr(test.pspDAPE), fmtPtr(test.podAPE)), func(t *testing.T) {
pod := defaultPod()
pod.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation = test.podAPE
ecPod := moveContainersToEphemeral(pod)
psp := defaultPSP()
psp.Spec.AllowPrivilegeEscalation = &test.pspAPE
@ -1547,6 +1585,18 @@ func TestAllowPrivilegeEscalation(t *testing.T) {
ape := pod.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation
assert.Equal(t, test.expectAPE, ape, "expected pod AllowPrivilegeEscalation")
}
err = provider.MutatePod(ecPod)
require.NoError(t, err)
errs = provider.ValidatePod(ecPod)
if test.expectErr {
assert.NotEmpty(t, errs, "expected validation error for ephemeral containers")
} else {
assert.Empty(t, errs, "expected no validation errors for ephemeral containers")
ape := ecPod.Spec.EphemeralContainers[0].SecurityContext.AllowPrivilegeEscalation
assert.Equal(t, test.expectAPE, ape, "expected pod AllowPrivilegeEscalation for ephemeral container")
}
})
}
}

View File

@ -1337,7 +1337,8 @@ message EphemeralContainerCommon {
// +optional
optional string imagePullPolicy = 14;
// SecurityContext is not allowed for ephemeral containers.
// Optional: SecurityContext defines the security options the ephemeral container should be run with.
// If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext.
// +optional
optional SecurityContext securityContext = 15;

View File

@ -3546,7 +3546,8 @@ type EphemeralContainerCommon struct {
// More info: https://kubernetes.io/docs/concepts/containers/images#updating-images
// +optional
ImagePullPolicy PullPolicy `json:"imagePullPolicy,omitempty" protobuf:"bytes,14,opt,name=imagePullPolicy,casttype=PullPolicy"`
// SecurityContext is not allowed for ephemeral containers.
// Optional: SecurityContext defines the security options the ephemeral container should be run with.
// If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext.
// +optional
SecurityContext *SecurityContext `json:"securityContext,omitempty" protobuf:"bytes,15,opt,name=securityContext"`

View File

@ -607,7 +607,7 @@ var map_EphemeralContainerCommon = map[string]string{
"terminationMessagePath": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.",
"terminationMessagePolicy": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.",
"imagePullPolicy": "Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images",
"securityContext": "SecurityContext is not allowed for ephemeral containers.",
"securityContext": "Optional: SecurityContext defines the security options the ephemeral container should be run with. If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext.",
"stdin": "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.",
"stdinOnce": "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false",
"tty": "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.",