diff --git a/api/swagger-spec/v1.json b/api/swagger-spec/v1.json index 146bfb2eaf850..cf2069aaaed77 100644 --- a/api/swagger-spec/v1.json +++ b/api/swagger-spec/v1.json @@ -12531,6 +12531,10 @@ "type": "boolean", "description": "Use the host's pid namespace. Optional: Default to false." }, + "hostIPC": { + "type": "boolean", + "description": "Use the host's ipc namespace. Optional: Default to false." + }, "imagePullSecrets": { "type": "array", "items": { diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index 4f8c80330d3d3..9346028993372 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -293,6 +293,7 @@ func (s *APIServer) Run(_ []string) error { PrivilegedSources: capabilities.PrivilegedSources{ HostNetworkSources: []string{}, HostPIDSources: []string{}, + HostIPCSources: []string{}, }, PerConnectionBandwidthLimitBytesPerSec: s.MaxConnectionBytesPerSec, }) diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index a14d0f452afd2..f8c6cb0cabfaa 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -95,6 +95,7 @@ type KubeletServer struct { HostnameOverride string HostNetworkSources string HostPIDSources string + HostIPCSources string HTTPCheckFrequency time.Duration ImageGCHighThresholdPercent int ImageGCLowThresholdPercent int @@ -173,6 +174,7 @@ func NewKubeletServer() *KubeletServer { HealthzPort: 10248, HostNetworkSources: kubelet.FileSource, HostPIDSources: kubelet.FileSource, + HostIPCSources: kubelet.FileSource, HTTPCheckFrequency: 20 * time.Second, ImageGCHighThresholdPercent: 90, ImageGCLowThresholdPercent: 80, @@ -226,6 +228,7 @@ func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) { fs.BoolVar(&s.AllowPrivileged, "allow-privileged", s.AllowPrivileged, "If true, allow containers to request privileged mode. [default=false]") fs.StringVar(&s.HostNetworkSources, "host-network-sources", s.HostNetworkSources, "Comma-separated list of sources from which the Kubelet allows pods to use of host network. For all sources use \"*\" [default=\"file\"]") fs.StringVar(&s.HostPIDSources, "host-pid-sources", s.HostPIDSources, "Comma-separated list of sources from which the Kubelet allows pods to use the host pid namespace. For all sources use \"*\" [default=\"file\"]") + fs.StringVar(&s.HostIPCSources, "host-ipc-sources", s.HostIPCSources, "Comma-separated list of sources from which the Kubelet allows pods to use the host ipc namespace. For all sources use \"*\" [default=\"file\"]") fs.Float64Var(&s.RegistryPullQPS, "registry-qps", s.RegistryPullQPS, "If > 0, limit registry pull QPS to this value. If 0, unlimited. [default=0.0]") fs.IntVar(&s.RegistryBurst, "registry-burst", s.RegistryBurst, "Maximum size of a bursty pulls, temporarily allows pulls to burst to this number, while still not exceeding registry-qps. Only used if --registry-qps > 0") fs.Float32Var(&s.EventRecordQPS, "event-qps", s.EventRecordQPS, "If > 0, limit event creations per second to this value. If 0, unlimited. [default=0.0]") @@ -287,6 +290,11 @@ func (s *KubeletServer) KubeletConfig() (*KubeletConfig, error) { return nil, err } + hostIPCSources, err := kubelet.GetValidatedSources(strings.Split(s.HostIPCSources, ",")) + if err != nil { + return nil, err + } + mounter := mount.New() var writer io.Writer = &io.StdWriter{} if s.Containerized { @@ -354,6 +362,7 @@ func (s *KubeletServer) KubeletConfig() (*KubeletConfig, error) { HostnameOverride: s.HostnameOverride, HostNetworkSources: hostNetworkSources, HostPIDSources: hostPIDSources, + HostIPCSources: hostIPCSources, HTTPCheckFrequency: s.HTTPCheckFrequency, ImageGCPolicy: imageGCPolicy, KubeClient: nil, @@ -689,6 +698,7 @@ func RunKubelet(kcfg *KubeletConfig, builder KubeletBuilder) error { privilegedSources := capabilities.PrivilegedSources{ HostNetworkSources: kcfg.HostNetworkSources, HostPIDSources: kcfg.HostPIDSources, + HostIPCSources: kcfg.HostIPCSources, } capabilities.Setup(kcfg.AllowPrivileged, privilegedSources, 0) @@ -783,6 +793,7 @@ type KubeletConfig struct { HostnameOverride string HostNetworkSources []string HostPIDSources []string + HostIPCSources []string HTTPCheckFrequency time.Duration ImageGCPolicy kubelet.ImageGCPolicy KubeClient *client.Client diff --git a/docs/admin/kubelet.md b/docs/admin/kubelet.md index 21a00399fb338..6a7026c1af50e 100644 --- a/docs/admin/kubelet.md +++ b/docs/admin/kubelet.md @@ -85,6 +85,7 @@ HTTP server: The kubelet can also listen for HTTP and respond to a simple API -h, --help=false: help for kubelet --host-network-sources="": Comma-separated list of sources from which the Kubelet allows pods to use of host network. For all sources use "*" [default="file"] --host-pid-sources="": Comma-separated list of sources from which the Kubelet allows pods to use the host pid namespace. For all sources use "*" [default="file"] + --host-ipc-sources="": Comma-separated list of sources from which the Kubelet allows pods to use the host ipc namespace. For all sources use "*" [default="file"] --hostname-override="": If non-empty, will use this string as identification instead of the actual hostname. --http-check-frequency=0: Duration between checking http for new data --image-gc-high-threshold=0: The percent of disk usage after which image garbage collection is always run. Default: 90%% diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index 48fb7fdc8078b..81e94c0d0460a 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -107,6 +107,7 @@ deployment-controller-sync-period hostname-override host-network-sources host-pid-sources +host-ipc-sources http-check-frequency http-port ignore-not-found diff --git a/pkg/api/deep_copy_generated.go b/pkg/api/deep_copy_generated.go index 0b050d8996e71..ba8351a1e6fa1 100644 --- a/pkg/api/deep_copy_generated.go +++ b/pkg/api/deep_copy_generated.go @@ -1452,6 +1452,7 @@ func deepCopy_api_PodSpec(in PodSpec, out *PodSpec, c *conversion.Cloner) error out.NodeName = in.NodeName out.HostNetwork = in.HostNetwork out.HostPID = in.HostPID + out.HostIPC = in.HostIPC if in.ImagePullSecrets != nil { out.ImagePullSecrets = make([]LocalObjectReference, len(in.ImagePullSecrets)) for i := range in.ImagePullSecrets { diff --git a/pkg/api/types.go b/pkg/api/types.go index f5593d308f16a..2c085e0a79202 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -959,6 +959,9 @@ type PodSpec struct { // Use the host's pid namespace. // Optional: Default to false. HostPID bool `json:"hostPID,omitempty"` + // Use the host's ipc namespace. + // Optional: Default to false. + HostIPC bool `json:"hostIPC,omitempty"` // ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. // If specified, these secrets will be passed to individual puller implementations for them to use. For example, // in the case of docker, only DockerConfig type secrets are honored. diff --git a/pkg/api/v1/conversion.go b/pkg/api/v1/conversion.go index 07ffd791cbe1d..d16bd81875818 100644 --- a/pkg/api/v1/conversion.go +++ b/pkg/api/v1/conversion.go @@ -283,6 +283,7 @@ func convert_api_PodSpec_To_v1_PodSpec(in *api.PodSpec, out *PodSpec, s conversi out.NodeName = in.NodeName out.HostNetwork = in.HostNetwork out.HostPID = in.HostPID + out.HostIPC = in.HostIPC if in.ImagePullSecrets != nil { out.ImagePullSecrets = make([]LocalObjectReference, len(in.ImagePullSecrets)) for i := range in.ImagePullSecrets { @@ -351,6 +352,7 @@ func convert_v1_PodSpec_To_api_PodSpec(in *PodSpec, out *api.PodSpec, s conversi out.NodeName = in.NodeName out.HostNetwork = in.HostNetwork out.HostPID = in.HostPID + out.HostIPC = in.HostIPC if in.ImagePullSecrets != nil { out.ImagePullSecrets = make([]api.LocalObjectReference, len(in.ImagePullSecrets)) for i := range in.ImagePullSecrets { diff --git a/pkg/api/v1/deep_copy_generated.go b/pkg/api/v1/deep_copy_generated.go index 9592cfa00e8b6..6f413ac47d9b2 100644 --- a/pkg/api/v1/deep_copy_generated.go +++ b/pkg/api/v1/deep_copy_generated.go @@ -1473,6 +1473,7 @@ func deepCopy_v1_PodSpec(in PodSpec, out *PodSpec, c *conversion.Cloner) error { out.NodeName = in.NodeName out.HostNetwork = in.HostNetwork out.HostPID = in.HostPID + out.HostIPC = in.HostIPC if in.ImagePullSecrets != nil { out.ImagePullSecrets = make([]LocalObjectReference, len(in.ImagePullSecrets)) for i := range in.ImagePullSecrets { diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index 62cebcd3d5e1c..a225be606680e 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -1197,6 +1197,9 @@ type PodSpec struct { // Use the host's pid namespace. // Optional: Default to false. HostPID bool `json:"hostPID,omitempty"` + // Use the host's ipc namespace. + // Optional: Default to false. + HostIPC bool `json:"hostIPC,omitempty"` // ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. // If specified, these secrets will be passed to individual puller implementations for them to use. For example, // in the case of docker, only DockerConfig type secrets are honored. diff --git a/pkg/api/v1/types_swagger_doc_generated.go b/pkg/api/v1/types_swagger_doc_generated.go index 08db845bcc79f..b1d5c6efb2cde 100644 --- a/pkg/api/v1/types_swagger_doc_generated.go +++ b/pkg/api/v1/types_swagger_doc_generated.go @@ -965,6 +965,7 @@ var map_PodSpec = map[string]string{ "nodeName": "NodeName is a request to schedule this pod onto a specific node. If it is non-empty, the scheduler simply schedules this pod onto that node, assuming that it fits resource requirements.", "hostNetwork": "Host networking requested for this pod. Use the host's network namespace. If this option is set, the ports that will be used must be specified. Default to false.", "hostPID": "Use the host's pid namespace. Optional: Default to false.", + "hostIPC": "Use the host's ipc namespace. Optional: Default to false.", "imagePullSecrets": "ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. If specified, these secrets will be passed to individual puller implementations for them to use. For example, in the case of docker, only DockerConfig type secrets are honored. More info: http://releases.k8s.io/HEAD/docs/user-guide/images.md#specifying-imagepullsecrets-on-a-pod", } diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index b376db62b2bc0..cfd2eef896d2d 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -1249,6 +1249,7 @@ func TestValidatePodSpec(t *testing.T) { }, }, HostNetwork: true, + HostIPC: true, RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, }, @@ -1299,6 +1300,7 @@ func TestValidatePodSpec(t *testing.T) { }, }, HostNetwork: true, + HostIPC: true, RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, }, diff --git a/pkg/apis/experimental/deep_copy_generated.go b/pkg/apis/experimental/deep_copy_generated.go index bb080f662210c..5029d9ba63c99 100644 --- a/pkg/apis/experimental/deep_copy_generated.go +++ b/pkg/apis/experimental/deep_copy_generated.go @@ -461,6 +461,7 @@ func deepCopy_api_PodSpec(in api.PodSpec, out *api.PodSpec, c *conversion.Cloner out.NodeName = in.NodeName out.HostNetwork = in.HostNetwork out.HostPID = in.HostPID + out.HostIPC = in.HostIPC if in.ImagePullSecrets != nil { out.ImagePullSecrets = make([]api.LocalObjectReference, len(in.ImagePullSecrets)) for i := range in.ImagePullSecrets { diff --git a/pkg/apis/experimental/v1/conversion.go b/pkg/apis/experimental/v1/conversion.go index e008397a6cb12..c3034e9fba07c 100644 --- a/pkg/apis/experimental/v1/conversion.go +++ b/pkg/apis/experimental/v1/conversion.go @@ -99,6 +99,7 @@ func convert_api_PodSpec_To_v1_PodSpec(in *api.PodSpec, out *v1.PodSpec, s conve out.NodeName = in.NodeName out.HostNetwork = in.HostNetwork out.HostPID = in.HostPID + out.HostIPC = in.HostIPC if in.ImagePullSecrets != nil { out.ImagePullSecrets = make([]v1.LocalObjectReference, len(in.ImagePullSecrets)) for i := range in.ImagePullSecrets { @@ -167,6 +168,7 @@ func convert_v1_PodSpec_To_api_PodSpec(in *v1.PodSpec, out *api.PodSpec, s conve out.NodeName = in.NodeName out.HostNetwork = in.HostNetwork out.HostPID = in.HostPID + out.HostIPC = in.HostIPC if in.ImagePullSecrets != nil { out.ImagePullSecrets = make([]api.LocalObjectReference, len(in.ImagePullSecrets)) for i := range in.ImagePullSecrets { diff --git a/pkg/apis/experimental/v1/deep_copy_generated.go b/pkg/apis/experimental/v1/deep_copy_generated.go index 084ce724761c7..f51b8e068b25a 100644 --- a/pkg/apis/experimental/v1/deep_copy_generated.go +++ b/pkg/apis/experimental/v1/deep_copy_generated.go @@ -500,6 +500,7 @@ func deepCopy_v1_PodSpec(in v1.PodSpec, out *v1.PodSpec, c *conversion.Cloner) e out.NodeName = in.NodeName out.HostNetwork = in.HostNetwork out.HostPID = in.HostPID + out.HostIPC = in.HostIPC if in.ImagePullSecrets != nil { out.ImagePullSecrets = make([]v1.LocalObjectReference, len(in.ImagePullSecrets)) for i := range in.ImagePullSecrets { diff --git a/pkg/capabilities/capabilities.go b/pkg/capabilities/capabilities.go index 1895e178c4d3f..d0a882c547827 100644 --- a/pkg/capabilities/capabilities.go +++ b/pkg/capabilities/capabilities.go @@ -41,6 +41,9 @@ type PrivilegedSources struct { // List of pod sources for which using host pid namespace is allowed. HostPIDSources []string + + // List of pod sources for which using host ipc is allowed. + HostIPCSources []string } // TODO: Clean these up into a singleton @@ -83,6 +86,7 @@ func Get() Capabilities { PrivilegedSources: PrivilegedSources{ HostNetworkSources: []string{}, HostPIDSources: []string{}, + HostIPCSources: []string{}, }, }) } diff --git a/pkg/kubelet/dockertools/manager.go b/pkg/kubelet/dockertools/manager.go index 1e096b61d0532..4407b29971f5e 100644 --- a/pkg/kubelet/dockertools/manager.go +++ b/pkg/kubelet/dockertools/manager.go @@ -1396,7 +1396,7 @@ func containerAndPodFromLabels(inspect *docker.Container) (pod *api.Pod, contain } // Run a single container from a pod. Returns the docker container ID -func (dm *DockerManager) runContainerInPod(pod *api.Pod, container *api.Container, netMode, ipcMode string, pidMode string) (kubeletTypes.DockerID, error) { +func (dm *DockerManager) runContainerInPod(pod *api.Pod, container *api.Container, netMode, ipcMode, pidMode string) (kubeletTypes.DockerID, error) { start := time.Now() defer func() { metrics.ContainerManagerLatency.WithLabelValues("runContainerInPod").Observe(metrics.SinceInMicroseconds(start)) @@ -1548,7 +1548,7 @@ func (dm *DockerManager) createPodInfraContainer(pod *api.Pod) (kubeletTypes.Doc return "", err } - id, err := dm.runContainerInPod(pod, container, netNamespace, "", getPidMode(pod)) + id, err := dm.runContainerInPod(pod, container, netNamespace, getIPCMode(pod, ""), getPidMode(pod)) if err != nil { return "", err } @@ -1804,7 +1804,7 @@ func (dm *DockerManager) SyncPod(pod *api.Pod, runningPod kubecontainer.Pod, pod // TODO(dawnchen): Check RestartPolicy.DelaySeconds before restart a container namespaceMode := fmt.Sprintf("container:%v", podInfraContainerID) - _, err = dm.runContainerInPod(pod, container, namespaceMode, namespaceMode, getPidMode(pod)) + _, err = dm.runContainerInPod(pod, container, namespaceMode, getIPCMode(pod, namespaceMode), getPidMode(pod)) dm.updateReasonCache(pod, container, "RunContainerError", err) if err != nil { // TODO(bburns) : Perhaps blacklist a container after N failures? @@ -1927,3 +1927,11 @@ func getPidMode(pod *api.Pod) string { } return pidMode } + +// getIPCMode returns the ipc mode to use on the docker container based on pod.Spec.HostIPC. +func getIPCMode(pod *api.Pod, ipcMode string) string { + if pod.Spec.HostIPC { + ipcMode = "host" + } + return ipcMode +} diff --git a/pkg/kubelet/dockertools/manager_test.go b/pkg/kubelet/dockertools/manager_test.go index 57f92ddafb15e..bbc6d3aa24646 100644 --- a/pkg/kubelet/dockertools/manager_test.go +++ b/pkg/kubelet/dockertools/manager_test.go @@ -2067,3 +2067,20 @@ func TestGetPidMode(t *testing.T) { t.Errorf("expected host pid mode for pod but got %v", pidMode) } } + +func TestGetIPCMode(t *testing.T) { + // test false + pod := &api.Pod{} + ipcMode := getIPCMode(pod, "") + + if ipcMode != "" { + t.Errorf("expected empty ipc mode for pod but got %v", ipcMode) + } + + // test true + pod.Spec.HostIPC = true + ipcMode = getIPCMode(pod, "") + if ipcMode != "host" { + t.Errorf("expected host ipc mode for pod but got %v", ipcMode) + } +} diff --git a/pkg/kubelet/rkt/rkt.go b/pkg/kubelet/rkt/rkt.go index c1680ebcd1fff..f5f4f664c8bca 100644 --- a/pkg/kubelet/rkt/rkt.go +++ b/pkg/kubelet/rkt/rkt.go @@ -594,6 +594,7 @@ func (r *runtime) preparePod(pod *api.Pod, pullSecrets []api.Secret) (string, *k } // TODO handle pod.Spec.HostPID + // TODO handle pod.Spec.HostIPC units := []*unit.UnitOption{ newUnitOption(unitKubernetesSection, unitRktID, uuid), diff --git a/pkg/kubelet/util.go b/pkg/kubelet/util.go index 4499dc94858ea..1c2ab0a29c904 100644 --- a/pkg/kubelet/util.go +++ b/pkg/kubelet/util.go @@ -60,6 +60,16 @@ func canRunPod(pod *api.Pod) error { } } + if pod.Spec.HostIPC { + allowed, err := allowHostIPC(pod) + if err != nil { + return err + } + if !allowed { + return fmt.Errorf("pod with UID %q specified host ipc, but is disallowed", pod.UID) + } + } + if !capabilities.Get().AllowPrivileged { for _, container := range pod.Spec.Containers { if securitycontext.HasPrivilegedRequest(&container) { @@ -97,3 +107,17 @@ func allowHostPID(pod *api.Pod) (bool, error) { } return false, nil } + +// Determined whether the specified pod is allowed to use host ipc +func allowHostIPC(pod *api.Pod) (bool, error) { + podSource, err := getPodSource(pod) + if err != nil { + return false, err + } + for _, source := range capabilities.Get().PrivilegedSources.HostIPCSources { + if source == podSource { + return true, nil + } + } + return false, nil +} diff --git a/plugin/pkg/admission/exec/admission.go b/plugin/pkg/admission/exec/admission.go index 762db266f4675..fc33d4a70607a 100644 --- a/plugin/pkg/admission/exec/admission.go +++ b/plugin/pkg/admission/exec/admission.go @@ -94,10 +94,9 @@ func (d *denyExec) Admit(a admission.Attributes) (err error) { return admission.NewForbidden(a, fmt.Errorf("Cannot exec into or attach to a container using host pid")) } - //TODO uncomment when this feature lands https://github.com/kubernetes/kubernetes/pull/12470 - // if d.hostIPC && pod.Spec.HostIPC { - // return admission.NewForbidden(a, fmt.Errorf("Cannot exec into or attach to a container using host ipc")) - // } + if d.hostIPC && pod.Spec.HostIPC { + return admission.NewForbidden(a, fmt.Errorf("Cannot exec into or attach to a container using host ipc")) + } if d.privileged && isPrivileged(pod) { return admission.NewForbidden(a, fmt.Errorf("Cannot exec into or attach to a privileged container")) diff --git a/plugin/pkg/admission/exec/admission_test.go b/plugin/pkg/admission/exec/admission_test.go index 647d049018ba6..2a5742aae3416 100644 --- a/plugin/pkg/admission/exec/admission_test.go +++ b/plugin/pkg/admission/exec/admission_test.go @@ -36,8 +36,8 @@ func TestAdmission(t *testing.T) { hostPIDPod := validPod("hostPID") hostPIDPod.Spec.HostPID = true - // hostIPCPod := validPod("hostIPC") - // hostIPCPod.Spec.HostIPC = true + hostIPCPod := validPod("hostIPC") + hostIPCPod.Spec.HostIPC = true testCases := map[string]struct { pod *api.Pod @@ -51,10 +51,10 @@ func TestAdmission(t *testing.T) { shouldAccept: false, pod: hostPIDPod, }, - // "hostIPC": { - // shouldAccept: false, - // pod: hostIPCPod, - // }, + "hostIPC": { + shouldAccept: false, + pod: hostIPCPod, + }, "non privileged": { shouldAccept: true, pod: validPod("nonPrivileged"), @@ -132,8 +132,8 @@ func TestDenyExecOnPrivileged(t *testing.T) { hostPIDPod := validPod("hostPID") hostPIDPod.Spec.HostPID = true - // hostIPCPod := validPod("hostIPC") - // hostIPCPod.Spec.HostIPC = true + hostIPCPod := validPod("hostIPC") + hostIPCPod.Spec.HostIPC = true testCases := map[string]struct { pod *api.Pod @@ -147,10 +147,10 @@ func TestDenyExecOnPrivileged(t *testing.T) { shouldAccept: true, pod: hostPIDPod, }, - // "hostIPC": { - // shouldAccept: true, - // pod: hostIPCPod, - // }, + "hostIPC": { + shouldAccept: true, + pod: hostIPCPod, + }, "non privileged": { shouldAccept: true, pod: validPod("nonPrivileged"),