From 5d511aeb549dabe0f6d41f334c08e92f589d2f82 Mon Sep 17 00:00:00 2001 From: Nick Schuch Date: Fri, 12 Feb 2016 08:31:26 +1000 Subject: [PATCH] Adds ReadOnlyRootFilesystem support for containers --- api/swagger-spec/extensions_v1beta1.json | 4 + api/swagger-spec/v1.json | 4 + .../extensions/v1beta1/definitions.html | 9 +- docs/api-reference/v1/definitions.html | 9 +- pkg/api/deep_copy_generated.go | 7 + pkg/api/types.generated.go | 166 +++++++++++++----- pkg/api/types.go | 3 + pkg/api/v1/conversion_generated.go | 12 ++ pkg/api/v1/deep_copy_generated.go | 6 + pkg/api/v1/types.generated.go | 166 +++++++++++++----- pkg/api/v1/types.go | 3 + pkg/api/v1/types_swagger_doc_generated.go | 13 +- .../v1beta1/conversion_generated.go | 12 ++ .../extensions/v1beta1/deep_copy_generated.go | 6 + pkg/kubelet/container/runtime.go | 2 + pkg/kubelet/dockertools/manager.go | 16 +- test/e2e_node/kubelet_test.go | 47 +++++ test/integration/pods.go | 43 +++++ 18 files changed, 427 insertions(+), 101 deletions(-) diff --git a/api/swagger-spec/extensions_v1beta1.json b/api/swagger-spec/extensions_v1beta1.json index 1c13198afdb..3d04a499760 100644 --- a/api/swagger-spec/extensions_v1beta1.json +++ b/api/swagger-spec/extensions_v1beta1.json @@ -5635,6 +5635,10 @@ "runAsNonRoot": { "type": "boolean", "description": "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence." + }, + "readOnlyRootFilesystem": { + "type": "boolean", + "description": "Whether this container has a read-only root filesystem. Default is false." } } }, diff --git a/api/swagger-spec/v1.json b/api/swagger-spec/v1.json index cca5b4aba0c..a22ec44e894 100644 --- a/api/swagger-spec/v1.json +++ b/api/swagger-spec/v1.json @@ -16030,6 +16030,10 @@ "runAsNonRoot": { "type": "boolean", "description": "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence." + }, + "readOnlyRootFilesystem": { + "type": "boolean", + "description": "Whether this container has a read-only root filesystem. Default is false." } } }, diff --git a/docs/api-reference/extensions/v1beta1/definitions.html b/docs/api-reference/extensions/v1beta1/definitions.html index c475e8c676e..6c8cf934c65 100755 --- a/docs/api-reference/extensions/v1beta1/definitions.html +++ b/docs/api-reference/extensions/v1beta1/definitions.html @@ -3114,6 +3114,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i

boolean

false

+ +

readOnlyRootFilesystem

+

Whether this container has a read-only root filesystem. Default is false.

+

false

+

boolean

+

false

+ @@ -5266,7 +5273,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i diff --git a/docs/api-reference/v1/definitions.html b/docs/api-reference/v1/definitions.html index da1baf0593d..7c7f1e448e0 100755 --- a/docs/api-reference/v1/definitions.html +++ b/docs/api-reference/v1/definitions.html @@ -5904,6 +5904,13 @@ The resulting set of endpoints can be viewed as:

boolean

false

+ +

readOnlyRootFilesystem

+

Whether this container has a read-only root filesystem. Default is false.

+

false

+

boolean

+

false

+ @@ -7378,7 +7385,7 @@ The resulting set of endpoints can be viewed as:
diff --git a/pkg/api/deep_copy_generated.go b/pkg/api/deep_copy_generated.go index bb6a4ce6fb6..52ade58214c 100644 --- a/pkg/api/deep_copy_generated.go +++ b/pkg/api/deep_copy_generated.go @@ -2597,6 +2597,13 @@ func DeepCopy_api_SecurityContext(in SecurityContext, out *SecurityContext, c *c } else { out.RunAsNonRoot = nil } + if in.ReadOnlyRootFilesystem != nil { + in, out := in.ReadOnlyRootFilesystem, &out.ReadOnlyRootFilesystem + *out = new(bool) + **out = *in + } else { + out.ReadOnlyRootFilesystem = nil + } return nil } diff --git a/pkg/api/types.generated.go b/pkg/api/types.generated.go index 3d632b1a03f..7ab28e41642 100644 --- a/pkg/api/types.generated.go +++ b/pkg/api/types.generated.go @@ -46664,7 +46664,7 @@ func (x *SecurityContext) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [5]bool + var yyq2 [6]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[0] = x.Capabilities != nil @@ -46672,9 +46672,10 @@ func (x *SecurityContext) CodecEncodeSelf(e *codec1978.Encoder) { yyq2[2] = x.SELinuxOptions != nil yyq2[3] = x.RunAsUser != nil yyq2[4] = x.RunAsNonRoot != nil + yyq2[5] = x.ReadOnlyRootFilesystem != nil var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(5) + r.EncodeArrayStart(6) } else { yynn2 = 0 for _, b := range yyq2 { @@ -46836,6 +46837,41 @@ func (x *SecurityContext) CodecEncodeSelf(e *codec1978.Encoder) { } } } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[5] { + if x.ReadOnlyRootFilesystem == nil { + r.EncodeNil() + } else { + yy25 := *x.ReadOnlyRootFilesystem + yym26 := z.EncBinary() + _ = yym26 + if false { + } else { + r.EncodeBool(bool(yy25)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[5] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("readOnlyRootFilesystem")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.ReadOnlyRootFilesystem == nil { + r.EncodeNil() + } else { + yy27 := *x.ReadOnlyRootFilesystem + yym28 := z.EncBinary() + _ = yym28 + if false { + } else { + r.EncodeBool(bool(yy27)) + } + } + } + } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayEnd1234) } else { @@ -46967,6 +47003,22 @@ func (x *SecurityContext) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { *((*bool)(x.RunAsNonRoot)) = r.DecodeBool() } } + case "readOnlyRootFilesystem": + if r.TryDecodeAsNil() { + if x.ReadOnlyRootFilesystem != nil { + x.ReadOnlyRootFilesystem = nil + } + } else { + if x.ReadOnlyRootFilesystem == nil { + x.ReadOnlyRootFilesystem = new(bool) + } + yym13 := z.DecBinary() + _ = yym13 + if false { + } else { + *((*bool)(x.ReadOnlyRootFilesystem)) = r.DecodeBool() + } + } default: z.DecStructFieldNotFound(-1, yys3) } // end switch yys3 @@ -46978,16 +47030,16 @@ func (x *SecurityContext) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj12 int - var yyb12 bool - var yyhl12 bool = l >= 0 - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + var yyj14 int + var yyb14 bool + var yyhl14 bool = l >= 0 + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -47002,13 +47054,13 @@ func (x *SecurityContext) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) } x.Capabilities.CodecDecodeSelf(d) } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -47021,20 +47073,20 @@ func (x *SecurityContext) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) if x.Privileged == nil { x.Privileged = new(bool) } - yym15 := z.DecBinary() - _ = yym15 + yym17 := z.DecBinary() + _ = yym17 if false { } else { *((*bool)(x.Privileged)) = r.DecodeBool() } } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -47049,13 +47101,13 @@ func (x *SecurityContext) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) } x.SELinuxOptions.CodecDecodeSelf(d) } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -47068,20 +47120,20 @@ func (x *SecurityContext) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) if x.RunAsUser == nil { x.RunAsUser = new(int64) } - yym18 := z.DecBinary() - _ = yym18 + yym20 := z.DecBinary() + _ = yym20 if false { } else { *((*int64)(x.RunAsUser)) = int64(r.DecodeInt(64)) } } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -47094,25 +47146,51 @@ func (x *SecurityContext) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) if x.RunAsNonRoot == nil { x.RunAsNonRoot = new(bool) } - yym20 := z.DecBinary() - _ = yym20 + yym22 := z.DecBinary() + _ = yym22 if false { } else { *((*bool)(x.RunAsNonRoot)) = r.DecodeBool() } } - for { - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l - } else { - yyb12 = r.CheckBreak() + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l + } else { + yyb14 = r.CheckBreak() + } + if yyb14 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.ReadOnlyRootFilesystem != nil { + x.ReadOnlyRootFilesystem = nil } - if yyb12 { + } else { + if x.ReadOnlyRootFilesystem == nil { + x.ReadOnlyRootFilesystem = new(bool) + } + yym24 := z.DecBinary() + _ = yym24 + if false { + } else { + *((*bool)(x.ReadOnlyRootFilesystem)) = r.DecodeBool() + } + } + for { + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l + } else { + yyb14 = r.CheckBreak() + } + if yyb14 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj12-1, "") + z.DecStructFieldNotFound(yyj14-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } diff --git a/pkg/api/types.go b/pkg/api/types.go index ab854971590..f2ac32f43f6 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -2412,6 +2412,9 @@ type SecurityContext struct { // May also be set in PodSecurityContext. If set in both SecurityContext and // PodSecurityContext, the value specified in SecurityContext takes precedence. RunAsNonRoot *bool `json:"runAsNonRoot,omitempty"` + // The read-only root filesystem allows you to restrict the locations that an application can write + // files to, ensuring the persistent data can only be written to mounts. + ReadOnlyRootFilesystem *bool `json:"readOnlyRootFilesystem,omitempty"` } // SELinuxOptions are the labels to be applied to the container. diff --git a/pkg/api/v1/conversion_generated.go b/pkg/api/v1/conversion_generated.go index 7c2c2f8ea55..c2add8e37ac 100644 --- a/pkg/api/v1/conversion_generated.go +++ b/pkg/api/v1/conversion_generated.go @@ -2827,6 +2827,12 @@ func autoConvert_api_SecurityContext_To_v1_SecurityContext(in *api.SecurityConte } else { out.RunAsNonRoot = nil } + if in.ReadOnlyRootFilesystem != nil { + out.ReadOnlyRootFilesystem = new(bool) + *out.ReadOnlyRootFilesystem = *in.ReadOnlyRootFilesystem + } else { + out.ReadOnlyRootFilesystem = nil + } return nil } @@ -5936,6 +5942,12 @@ func autoConvert_v1_SecurityContext_To_api_SecurityContext(in *SecurityContext, } else { out.RunAsNonRoot = nil } + if in.ReadOnlyRootFilesystem != nil { + out.ReadOnlyRootFilesystem = new(bool) + *out.ReadOnlyRootFilesystem = *in.ReadOnlyRootFilesystem + } else { + out.ReadOnlyRootFilesystem = nil + } return nil } diff --git a/pkg/api/v1/deep_copy_generated.go b/pkg/api/v1/deep_copy_generated.go index f5b35610466..ee4d013f026 100644 --- a/pkg/api/v1/deep_copy_generated.go +++ b/pkg/api/v1/deep_copy_generated.go @@ -2211,6 +2211,12 @@ func deepCopy_v1_SecurityContext(in SecurityContext, out *SecurityContext, c *co } else { out.RunAsNonRoot = nil } + if in.ReadOnlyRootFilesystem != nil { + out.ReadOnlyRootFilesystem = new(bool) + *out.ReadOnlyRootFilesystem = *in.ReadOnlyRootFilesystem + } else { + out.ReadOnlyRootFilesystem = nil + } return nil } diff --git a/pkg/api/v1/types.generated.go b/pkg/api/v1/types.generated.go index b1214958ef9..49d294b1a09 100644 --- a/pkg/api/v1/types.generated.go +++ b/pkg/api/v1/types.generated.go @@ -46815,7 +46815,7 @@ func (x *SecurityContext) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [5]bool + var yyq2 [6]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[0] = x.Capabilities != nil @@ -46823,9 +46823,10 @@ func (x *SecurityContext) CodecEncodeSelf(e *codec1978.Encoder) { yyq2[2] = x.SELinuxOptions != nil yyq2[3] = x.RunAsUser != nil yyq2[4] = x.RunAsNonRoot != nil + yyq2[5] = x.ReadOnlyRootFilesystem != nil var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(5) + r.EncodeArrayStart(6) } else { yynn2 = 0 for _, b := range yyq2 { @@ -46987,6 +46988,41 @@ func (x *SecurityContext) CodecEncodeSelf(e *codec1978.Encoder) { } } } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[5] { + if x.ReadOnlyRootFilesystem == nil { + r.EncodeNil() + } else { + yy25 := *x.ReadOnlyRootFilesystem + yym26 := z.EncBinary() + _ = yym26 + if false { + } else { + r.EncodeBool(bool(yy25)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[5] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("readOnlyRootFilesystem")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.ReadOnlyRootFilesystem == nil { + r.EncodeNil() + } else { + yy27 := *x.ReadOnlyRootFilesystem + yym28 := z.EncBinary() + _ = yym28 + if false { + } else { + r.EncodeBool(bool(yy27)) + } + } + } + } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayEnd1234) } else { @@ -47118,6 +47154,22 @@ func (x *SecurityContext) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { *((*bool)(x.RunAsNonRoot)) = r.DecodeBool() } } + case "readOnlyRootFilesystem": + if r.TryDecodeAsNil() { + if x.ReadOnlyRootFilesystem != nil { + x.ReadOnlyRootFilesystem = nil + } + } else { + if x.ReadOnlyRootFilesystem == nil { + x.ReadOnlyRootFilesystem = new(bool) + } + yym13 := z.DecBinary() + _ = yym13 + if false { + } else { + *((*bool)(x.ReadOnlyRootFilesystem)) = r.DecodeBool() + } + } default: z.DecStructFieldNotFound(-1, yys3) } // end switch yys3 @@ -47129,16 +47181,16 @@ func (x *SecurityContext) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj12 int - var yyb12 bool - var yyhl12 bool = l >= 0 - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + var yyj14 int + var yyb14 bool + var yyhl14 bool = l >= 0 + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -47153,13 +47205,13 @@ func (x *SecurityContext) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) } x.Capabilities.CodecDecodeSelf(d) } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -47172,20 +47224,20 @@ func (x *SecurityContext) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) if x.Privileged == nil { x.Privileged = new(bool) } - yym15 := z.DecBinary() - _ = yym15 + yym17 := z.DecBinary() + _ = yym17 if false { } else { *((*bool)(x.Privileged)) = r.DecodeBool() } } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -47200,13 +47252,13 @@ func (x *SecurityContext) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) } x.SELinuxOptions.CodecDecodeSelf(d) } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -47219,20 +47271,20 @@ func (x *SecurityContext) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) if x.RunAsUser == nil { x.RunAsUser = new(int64) } - yym18 := z.DecBinary() - _ = yym18 + yym20 := z.DecBinary() + _ = yym20 if false { } else { *((*int64)(x.RunAsUser)) = int64(r.DecodeInt(64)) } } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -47245,25 +47297,51 @@ func (x *SecurityContext) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) if x.RunAsNonRoot == nil { x.RunAsNonRoot = new(bool) } - yym20 := z.DecBinary() - _ = yym20 + yym22 := z.DecBinary() + _ = yym22 if false { } else { *((*bool)(x.RunAsNonRoot)) = r.DecodeBool() } } - for { - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l - } else { - yyb12 = r.CheckBreak() + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l + } else { + yyb14 = r.CheckBreak() + } + if yyb14 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.ReadOnlyRootFilesystem != nil { + x.ReadOnlyRootFilesystem = nil } - if yyb12 { + } else { + if x.ReadOnlyRootFilesystem == nil { + x.ReadOnlyRootFilesystem = new(bool) + } + yym24 := z.DecBinary() + _ = yym24 + if false { + } else { + *((*bool)(x.ReadOnlyRootFilesystem)) = r.DecodeBool() + } + } + for { + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l + } else { + yyb14 = r.CheckBreak() + } + if yyb14 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj12-1, "") + z.DecStructFieldNotFound(yyj14-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index c06eafcb670..532505827b0 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -2844,6 +2844,9 @@ type SecurityContext struct { // May also be set in PodSecurityContext. If set in both SecurityContext and // PodSecurityContext, the value specified in SecurityContext takes precedence. RunAsNonRoot *bool `json:"runAsNonRoot,omitempty"` + // Whether this container has a read-only root filesystem. + // Default is false. + ReadOnlyRootFilesystem *bool `json:"readOnlyRootFilesystem,omitempty"` } // SELinuxOptions are the labels to be applied to the container diff --git a/pkg/api/v1/types_swagger_doc_generated.go b/pkg/api/v1/types_swagger_doc_generated.go index 192931f1254..4b934238e61 100644 --- a/pkg/api/v1/types_swagger_doc_generated.go +++ b/pkg/api/v1/types_swagger_doc_generated.go @@ -1404,12 +1404,13 @@ func (SecretVolumeSource) SwaggerDoc() map[string]string { } var map_SecurityContext = map[string]string{ - "": "SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence.", - "capabilities": "The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime.", - "privileged": "Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false.", - "seLinuxOptions": "The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", - "runAsUser": "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", - "runAsNonRoot": "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + "": "SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence.", + "capabilities": "The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime.", + "privileged": "Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false.", + "seLinuxOptions": "The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + "runAsUser": "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + "runAsNonRoot": "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + "readOnlyRootFilesystem": "Whether this container has a read-only root filesystem. Default is false.", } func (SecurityContext) SwaggerDoc() map[string]string { diff --git a/pkg/apis/extensions/v1beta1/conversion_generated.go b/pkg/apis/extensions/v1beta1/conversion_generated.go index 0ec163ab094..19487ac886d 100644 --- a/pkg/apis/extensions/v1beta1/conversion_generated.go +++ b/pkg/apis/extensions/v1beta1/conversion_generated.go @@ -1064,6 +1064,12 @@ func autoConvert_api_SecurityContext_To_v1_SecurityContext(in *api.SecurityConte } else { out.RunAsNonRoot = nil } + if in.ReadOnlyRootFilesystem != nil { + out.ReadOnlyRootFilesystem = new(bool) + *out.ReadOnlyRootFilesystem = *in.ReadOnlyRootFilesystem + } else { + out.ReadOnlyRootFilesystem = nil + } return nil } @@ -2348,6 +2354,12 @@ func autoConvert_v1_SecurityContext_To_api_SecurityContext(in *v1.SecurityContex } else { out.RunAsNonRoot = nil } + if in.ReadOnlyRootFilesystem != nil { + out.ReadOnlyRootFilesystem = new(bool) + *out.ReadOnlyRootFilesystem = *in.ReadOnlyRootFilesystem + } else { + out.ReadOnlyRootFilesystem = nil + } return nil } diff --git a/pkg/apis/extensions/v1beta1/deep_copy_generated.go b/pkg/apis/extensions/v1beta1/deep_copy_generated.go index 386131f6554..48d910a62ef 100644 --- a/pkg/apis/extensions/v1beta1/deep_copy_generated.go +++ b/pkg/apis/extensions/v1beta1/deep_copy_generated.go @@ -809,6 +809,12 @@ func deepCopy_v1_SecurityContext(in v1.SecurityContext, out *v1.SecurityContext, } else { out.RunAsNonRoot = nil } + if in.ReadOnlyRootFilesystem != nil { + out.ReadOnlyRootFilesystem = new(bool) + *out.ReadOnlyRootFilesystem = *in.ReadOnlyRootFilesystem + } else { + out.ReadOnlyRootFilesystem = nil + } return nil } diff --git a/pkg/kubelet/container/runtime.go b/pkg/kubelet/container/runtime.go index 67edb6a985e..33bea5bf5c4 100644 --- a/pkg/kubelet/container/runtime.go +++ b/pkg/kubelet/container/runtime.go @@ -354,6 +354,8 @@ type RunContainerOptions struct { DNSSearch []string // The parent cgroup to pass to Docker CgroupParent string + // The type of container rootfs + ReadOnly bool } // VolumeInfo contains information about the volume. diff --git a/pkg/kubelet/dockertools/manager.go b/pkg/kubelet/dockertools/manager.go index 32664de4e23..38d30f511fd 100644 --- a/pkg/kubelet/dockertools/manager.go +++ b/pkg/kubelet/dockertools/manager.go @@ -544,11 +544,12 @@ func (dm *DockerManager) runContainer( } hc := &docker.HostConfig{ - Binds: binds, - NetworkMode: netMode, - IpcMode: ipcMode, - UTSMode: utsMode, - PidMode: pidMode, + Binds: binds, + NetworkMode: netMode, + IpcMode: ipcMode, + UTSMode: utsMode, + PidMode: pidMode, + ReadonlyRootfs: readOnlyRootFilesystem(container), // Memory and CPU are set here for newer versions of Docker (1.6+). Memory: memoryLimit, MemorySwap: -1, @@ -815,6 +816,11 @@ func usesHostNetwork(pod *api.Pod) bool { return pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostNetwork } +// determine if the container root should be a read only filesystem. +func readOnlyRootFilesystem(container *api.Container) bool { + return container.SecurityContext != nil && container.SecurityContext.ReadOnlyRootFilesystem != nil && *container.SecurityContext.ReadOnlyRootFilesystem +} + // dockerVersion implementes kubecontainer.Version interface by implementing // Compare() and String() (which is implemented by the underlying semver.Version) // TODO: this code is the same as rktVersion and may make sense to be moved to diff --git a/test/e2e_node/kubelet_test.go b/test/e2e_node/kubelet_test.go index 5cd8efa0449..3f1b2925765 100644 --- a/test/e2e_node/kubelet_test.go +++ b/test/e2e_node/kubelet_test.go @@ -85,6 +85,53 @@ var _ = Describe("Kubelet", func() { Expect(err).To(BeNil(), fmt.Sprintf("Error creating Pod %v", err)) }) }) + + Context("when scheduling a read only busybox container", func() { + It("it should return success", func() { + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "busybox", + Namespace: api.NamespaceDefault, + }, + Spec: api.PodSpec{ + // Force the Pod to schedule to the node without a scheduler running + NodeName: *nodeName, + // Don't restart the Pod since it is expected to exit + RestartPolicy: api.RestartPolicyNever, + Containers: []api.Container{ + { + Image: "gcr.io/google_containers/busybox", + Name: "busybox", + Command: []string{"sh", "-c", "echo test > /file"}, + SecurityContext: &api.SecurityContext{ + ReadOnlyRootFilesystem: &isReadOnly, + }, + }, + }, + }, + } + _, err := cl.Pods(api.NamespaceDefault).Create(pod) + Expect(err).To(BeNil(), fmt.Sprintf("Error creating Pod %v", err)) + }) + + It("it should not write to the root filesystem", func() { + Eventually(func() string { + rc, err := cl.Pods(api.NamespaceDefault).GetLogs("busybox", &api.PodLogOptions{}).Stream() + if err != nil { + return "" + } + defer rc.Close() + buf := new(bytes.Buffer) + buf.ReadFrom(rc) + return buf.String() + }, time.Second*30, time.Second*4).Should(Equal("sh: can't create /file: Read-only file system")) + }) + + It("it should be possible to delete", func() { + err := cl.Pods(api.NamespaceDefault).Delete("busybox", &api.DeleteOptions{}) + Expect(err).To(BeNil(), fmt.Sprintf("Error creating Pod %v", err)) + }) + }) }) Describe("metrics api", func() { diff --git a/test/integration/pods.go b/test/integration/pods.go index 0407d627410..4d2970cf039 100644 --- a/test/integration/pods.go +++ b/test/integration/pods.go @@ -161,3 +161,46 @@ func TestPodUpdateActiveDeadlineSeconds(t *testing.T) { deletePodOrErrorf(t, client, ns, pod.Name) } } + +func TestPodReadOnlyFilesystem(t *testing.T) { + var m *master.Master + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + m.Handler.ServeHTTP(w, req) + })) + // TODO: Uncomment when fix #19254 + // defer s.Close() + + isReadOnly := true + ns := "pod-readonly-root" + masterConfig := framework.NewIntegrationTestMasterConfig() + m, err := master.New(masterConfig) + if err != nil { + t.Fatalf("Error in bringing up the master: %v", err) + } + + framework.DeleteAllEtcdKeys() + client := client.NewOrDie(&client.Config{Host: s.URL, ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "XXX", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "fake-name", + Image: "fakeimage", + SecurityContext: &api.SecurityContext{ + ReadOnlyRootFilesystem: &isReadOnly, + }, + }, + }, + }, + } + + if _, err := client.Pods(ns).Create(pod); err != nil { + t.Errorf("Failed to create pod: %v", err) + } + + deletePodOrErrorf(t, client, ns, pod.Name) +}