mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
Merge pull request #99023 from verb/1.21-securitycontext
Allow setting securityContext in ephemeral containers
This commit is contained in:
commit
e799d7b191
2
api/openapi-spec/swagger.json
generated
2
api/openapi-spec/swagger.json
generated
@ -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",
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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
|
||||
|
||||
|
@ -84,6 +84,7 @@ var allowedEphemeralContainerFields = map[string]bool{
|
||||
"TerminationMessagePath": true,
|
||||
"TerminationMessagePolicy": true,
|
||||
"ImagePullPolicy": true,
|
||||
"SecurityContext": true,
|
||||
"Stdin": true,
|
||||
"StdinOnce": true,
|
||||
"TTY": true,
|
||||
|
@ -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{
|
||||
|
@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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"`
|
||||
|
||||
|
@ -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.",
|
||||
|
Loading…
Reference in New Issue
Block a user