Merge pull request #34811 from feiskyer/security-contex

Automatic merge from submit-queue

CRI: Add security context for sandbox/container

Part of #29478. This PR 
- adds security context for sandbox and fixes #33139
- encaps container security context to `SecurityContext` and adds missing features
- Note that capability is not fully accomplished in this PR because it is under discussion at  #33614.

cc/ @yujuhong @yifan-gu @Random-Liu @kubernetes/sig-node
This commit is contained in:
Kubernetes Submit Queue 2016-11-07 18:55:32 -08:00 committed by GitHub
commit a132e5c580
20 changed files with 1211 additions and 541 deletions

File diff suppressed because it is too large Load Diff

View File

@ -148,6 +148,26 @@ message NamespaceOption {
optional bool host_ipc = 3; optional bool host_ipc = 3;
} }
// LinuxSandboxSecurityContext holds linux security configuration that will be
// applied to a sandbox. Note that:
// 1) It does not apply to containers in the pods.
// 2) It may not be applicable to a PodSandbox which does not contain any running
// process.
message LinuxSandboxSecurityContext {
// The configurations for the sandbox's namespaces.
// This will be used only if the PodSandbox uses namespace for isolation.
optional NamespaceOption namespace_options = 1;
// Optional SELinux context to be applied.
optional SELinuxOption selinux_options = 2;
// The UID to run the entrypoint of the sandbox process.
optional int64 run_as_user = 3;
// If set, the root filesystem of the sandbox is read-only.
optional bool readonly_rootfs = 4;
// A list of groups applied to the first process run in the sandbox, in addition
// to the sandbox's primary GID.
repeated int64 supplemental_groups = 5;
}
// LinuxPodSandboxConfig holds platform-specific configurations for Linux // LinuxPodSandboxConfig holds platform-specific configurations for Linux
// host platforms and Linux-based containers. // host platforms and Linux-based containers.
message LinuxPodSandboxConfig { message LinuxPodSandboxConfig {
@ -155,9 +175,8 @@ message LinuxPodSandboxConfig {
// The cgroupfs style syntax will be used, but the container runtime can // The cgroupfs style syntax will be used, but the container runtime can
// convert it to systemd semantics if needed. // convert it to systemd semantics if needed.
optional string cgroup_parent = 1; optional string cgroup_parent = 1;
// The configurations for the sandbox's namespaces. // LinuxSandboxSecurityContext holds sandbox security attributes.
// This will be used only if the PodSandbox uses namespace for isolation. optional LinuxSandboxSecurityContext security_context = 2;
optional NamespaceOption namespace_options = 2;
} }
// PodSandboxMetadata holds all necessary information for building the sandbox name. // PodSandboxMetadata holds all necessary information for building the sandbox name.
@ -409,26 +428,34 @@ message Capability {
repeated string drop_capabilities = 2; repeated string drop_capabilities = 2;
} }
// LinuxContainerSecurityContext holds linux security configuration that will be applied to a container.
message LinuxContainerSecurityContext {
// Capabilities to add or drop.
optional Capability capabilities = 1;
// If set, run container in privileged mode.
optional bool privileged = 2;
// The configurations for the container's namespaces.
// This will be used only if the container uses namespace for isolation.
optional NamespaceOption namespace_options = 3;
// Optional SELinux context to be applied.
optional SELinuxOption selinux_options = 4;
// The UID to run the the container process as.
// Defaults to user specified in image metadata if unspecified.
optional int64 run_as_user = 5;
// If set, the root filesystem of the container is read-only.
optional bool readonly_rootfs = 6;
// A list of groups applied to the first process run in the container, in addition
// to the container's primary GID.
repeated int64 supplemental_groups = 7;
}
// LinuxContainerConfig contains platform-specific configuration for // LinuxContainerConfig contains platform-specific configuration for
// Linux-based containers. // Linux-based containers.
message LinuxContainerConfig { message LinuxContainerConfig {
// Resources specification for the container. // Resources specification for the container.
optional LinuxContainerResources resources = 1; optional LinuxContainerResources resources = 1;
// Capabilities to add or drop. // LinuxContainerSecurityContext configuration for the container.
optional Capability capabilities = 2; optional LinuxContainerSecurityContext security_context = 2;
// Optional SELinux context to be applied.
optional SELinuxOption selinux_options = 3;
// User contains the user for the container process.
optional LinuxUser user = 4;
}
message LinuxUser {
// uid specifies the user ID the container process has.
optional int64 uid = 1;
// gid specifies the group ID the container process has.
optional int64 gid = 2;
// additional_gids specifies additional GIDs the container process has.
repeated int64 additional_gids = 3;
} }
// ContainerMetadata holds all necessary information for building the container // ContainerMetadata holds all necessary information for building the container
@ -488,11 +515,6 @@ message ContainerConfig {
// Annotations is an unstructured key value map that may be set by external // Annotations is an unstructured key value map that may be set by external
// tools to store and retrieve arbitrary metadata. // tools to store and retrieve arbitrary metadata.
map<string, string> annotations = 10; map<string, string> annotations = 10;
// If set, run container in privileged mode.
// Processes in privileged containers are essentially equivalent to root on the host.
optional bool privileged = 11;
// If set, the root filesystem of the container is read-only.
optional bool readonly_rootfs = 12;
// Path relative to PodSandboxConfig.LogDirectory for container to store // Path relative to PodSandboxConfig.LogDirectory for container to store
// the log (STDOUT and STDERR) on the host. // the log (STDOUT and STDERR) on the host.
// E.g., // E.g.,
@ -503,19 +525,18 @@ message ContainerConfig {
// container logs are under active discussion in // container logs are under active discussion in
// https://issues.k8s.io/24677. There *may* be future change of direction // https://issues.k8s.io/24677. There *may* be future change of direction
// for logging as the discussion carries on. // for logging as the discussion carries on.
optional string log_path = 13; optional string log_path = 11;
// The hash of container config
// Variables for interactive containers, these have very specialized // Variables for interactive containers, these have very specialized
// use-cases (e.g. debugging). // use-cases (e.g. debugging).
// TODO: Determine if we need to continue supporting these fields that are // TODO: Determine if we need to continue supporting these fields that are
// part of Kubernetes's Container Spec. // part of Kubernetes's Container Spec.
optional bool stdin = 14; optional bool stdin = 12;
optional bool stdin_once = 15; optional bool stdin_once = 13;
optional bool tty = 16; optional bool tty = 14;
// Linux contains configuration specific to Linux containers. // Linux contains configuration specific to Linux containers.
optional LinuxContainerConfig linux = 17; optional LinuxContainerConfig linux = 15;
} }
message CreateContainerRequest { message CreateContainerRequest {
@ -737,6 +758,8 @@ message Image {
repeated string repo_digests = 3; repeated string repo_digests = 3;
// The size of the image in bytes. // The size of the image in bytes.
optional uint64 size = 4; optional uint64 size = 4;
// The uid that will run the command(s).
optional int64 uid = 5;
} }
message ListImagesResponse { message ListImagesResponse {

View File

@ -23,6 +23,7 @@ go_library(
"helpers.go", "helpers.go",
"legacy.go", "legacy.go",
"naming.go", "naming.go",
"security_context.go",
], ],
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
@ -41,6 +42,7 @@ go_library(
"//pkg/kubelet/server/streaming:go_default_library", "//pkg/kubelet/server/streaming:go_default_library",
"//pkg/kubelet/types:go_default_library", "//pkg/kubelet/types:go_default_library",
"//pkg/kubelet/util/ioutils:go_default_library", "//pkg/kubelet/util/ioutils:go_default_library",
"//pkg/securitycontext:go_default_library",
"//pkg/util/term:go_default_library", "//pkg/util/term:go_default_library",
"//vendor:github.com/docker/engine-api/types", "//vendor:github.com/docker/engine-api/types",
"//vendor:github.com/docker/engine-api/types/container", "//vendor:github.com/docker/engine-api/types/container",
@ -63,6 +65,7 @@ go_test(
"docker_service_test.go", "docker_service_test.go",
"helpers_test.go", "helpers_test.go",
"naming_test.go", "naming_test.go",
"security_context_test.go",
], ],
library = "go_default_library", library = "go_default_library",
tags = ["automanaged"], tags = ["automanaged"],
@ -76,8 +79,10 @@ go_test(
"//pkg/kubelet/network/mock_network:go_default_library", "//pkg/kubelet/network/mock_network:go_default_library",
"//pkg/kubelet/types:go_default_library", "//pkg/kubelet/types:go_default_library",
"//pkg/security/apparmor:go_default_library", "//pkg/security/apparmor:go_default_library",
"//pkg/securitycontext:go_default_library",
"//pkg/util/clock:go_default_library", "//pkg/util/clock:go_default_library",
"//vendor:github.com/docker/engine-api/types", "//vendor:github.com/docker/engine-api/types",
"//vendor:github.com/docker/engine-api/types/container",
"//vendor:github.com/golang/mock/gomock", "//vendor:github.com/golang/mock/gomock",
"//vendor:github.com/stretchr/testify/assert", "//vendor:github.com/stretchr/testify/assert",
], ],

View File

@ -18,12 +18,14 @@ package dockershim
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"time" "time"
dockertypes "github.com/docker/engine-api/types" dockertypes "github.com/docker/engine-api/types"
runtimeApi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime" runtimeApi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"k8s.io/kubernetes/pkg/kubelet/dockertools"
) )
// This file contains helper functions to convert docker API types to runtime // This file contains helper functions to convert docker API types to runtime
@ -55,14 +57,25 @@ func imageInspectToRuntimeAPIImage(image *dockertypes.ImageInspect) (*runtimeApi
return nil, fmt.Errorf("unable to convert a nil pointer to a runtime API image") return nil, fmt.Errorf("unable to convert a nil pointer to a runtime API image")
} }
var err error
var uid int64
size := uint64(image.VirtualSize) size := uint64(image.VirtualSize)
imageUid := dockertools.GetUidFromUser(image.Config.User)
// Convert image UID to int64 format. Not that it assumes the process in
// the image is running as root if image.Config.User is not set.
if imageUid != "" {
uid, err = strconv.ParseInt(imageUid, 10, 64)
if err != nil {
return nil, fmt.Errorf("non-numeric user (%q)", imageUid)
}
}
return &runtimeApi.Image{ return &runtimeApi.Image{
Id: &image.ID, Id: &image.ID,
RepoTags: image.RepoTags, RepoTags: image.RepoTags,
RepoDigests: image.RepoDigests, RepoDigests: image.RepoDigests,
Size_: &size, Size_: &size,
Uid: &uid,
}, nil }, nil
} }
func toPullableImageID(id string, image *dockertypes.ImageInspect) string { func toPullableImageID(id string, image *dockertypes.ImageInspect) string {

View File

@ -121,33 +121,14 @@ func (ds *dockerService) CreateContainer(podSandboxID string, config *runtimeApi
// Fill the HostConfig. // Fill the HostConfig.
hc := &dockercontainer.HostConfig{ hc := &dockercontainer.HostConfig{
Binds: generateMountBindings(config.GetMounts()), Binds: generateMountBindings(config.GetMounts()),
ReadonlyRootfs: config.GetReadonlyRootfs(),
Privileged: config.GetPrivileged(),
} }
// Apply options derived from the sandbox config. // Apply cgroupsParent derived from the sandbox config.
if lc := sandboxConfig.GetLinux(); lc != nil { if lc := sandboxConfig.GetLinux(); lc != nil {
// Apply Cgroup options. // Apply Cgroup options.
// TODO: Check if this works with per-pod cgroups. // TODO: Check if this works with per-pod cgroups.
// TODO: we need to pass the cgroup in syntax expected by cgroup driver but shim does not use docker info yet... // TODO: we need to pass the cgroup in syntax expected by cgroup driver but shim does not use docker info yet...
hc.CgroupParent = lc.GetCgroupParent() hc.CgroupParent = lc.GetCgroupParent()
// Apply namespace options.
sandboxNSMode := fmt.Sprintf("container:%v", podSandboxID)
hc.NetworkMode = dockercontainer.NetworkMode(sandboxNSMode)
hc.IpcMode = dockercontainer.IpcMode(sandboxNSMode)
hc.UTSMode = ""
hc.PidMode = ""
nsOpts := lc.GetNamespaceOptions()
if nsOpts != nil {
if nsOpts.GetHostNetwork() {
hc.UTSMode = namespaceModeHost
}
if nsOpts.GetHostPid() {
hc.PidMode = namespaceModeHost
}
}
} }
// Apply Linux-specific options if applicable. // Apply Linux-specific options if applicable.
@ -167,6 +148,9 @@ func (ds *dockerService) CreateContainer(podSandboxID string, config *runtimeApi
hc.OomScoreAdj = int(rOpts.GetOomScoreAdj()) hc.OomScoreAdj = int(rOpts.GetOomScoreAdj())
} }
// Note: ShmSize is handled in kube_docker_client.go // Note: ShmSize is handled in kube_docker_client.go
// Apply security context.
applyContainerSecurityContext(lc, podSandboxID, createConfig.Config, hc)
} }
// Set devices for container. // Set devices for container.
@ -180,12 +164,12 @@ func (ds *dockerService) CreateContainer(podSandboxID string, config *runtimeApi
} }
hc.Resources.Devices = devices hc.Resources.Devices = devices
var err error // Apply appArmor and seccomp options.
hc.SecurityOpt, err = getContainerSecurityOpts(config.Metadata.GetName(), sandboxConfig, ds.seccompProfileRoot) securityOpts, err := getContainerSecurityOpts(config.Metadata.GetName(), sandboxConfig, ds.seccompProfileRoot)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to generate container security options for container %q: %v", config.Metadata.GetName(), err) return "", fmt.Errorf("failed to generate container security options for container %q: %v", config.Metadata.GetName(), err)
} }
// TODO: Add or drop capabilities. hc.SecurityOpt = append(hc.SecurityOpt, securityOpts...)
createConfig.HostConfig = hc createConfig.HostConfig = hc
createResp, err := ds.client.CreateContainer(createConfig) createResp, err := ds.client.CreateContainer(createConfig)

View File

@ -79,7 +79,7 @@ func (ds *dockerService) RunPodSandbox(config *runtimeApi.PodSandboxConfig) (str
if err != nil { if err != nil {
return createResp.ID, fmt.Errorf("failed to start sandbox container for pod %q: %v", config.Metadata.GetName(), err) return createResp.ID, fmt.Errorf("failed to start sandbox container for pod %q: %v", config.Metadata.GetName(), err)
} }
if config.GetLinux().GetNamespaceOptions().GetHostNetwork() { if config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetHostNetwork() {
return createResp.ID, nil return createResp.ID, nil
} }
@ -286,6 +286,18 @@ func (ds *dockerService) ListPodSandbox(filter *runtimeApi.PodSandboxFilter) ([]
return result, nil return result, nil
} }
// applySandboxLinuxOptions applies LinuxPodSandboxConfig to dockercontainer.HostConfig and dockercontainer.ContainerCreateConfig.
func (ds *dockerService) applySandboxLinuxOptions(hc *dockercontainer.HostConfig, lc *runtimeApi.LinuxPodSandboxConfig, createConfig *dockertypes.ContainerCreateConfig, image string) error {
// Apply Cgroup options.
// TODO: Check if this works with per-pod cgroups.
hc.CgroupParent = lc.GetCgroupParent()
// Apply security context.
applySandboxSecurityContext(lc, createConfig.Config, hc)
return nil
}
// makeSandboxDockerConfig returns dockertypes.ContainerCreateConfig based on runtimeApi.PodSandboxConfig.
func (ds *dockerService) makeSandboxDockerConfig(c *runtimeApi.PodSandboxConfig, image string) (*dockertypes.ContainerCreateConfig, error) { func (ds *dockerService) makeSandboxDockerConfig(c *runtimeApi.PodSandboxConfig, image string) (*dockertypes.ContainerCreateConfig, error) {
// Merge annotations and labels because docker supports only labels. // Merge annotations and labels because docker supports only labels.
labels := makeLabels(c.GetLabels(), c.GetAnnotations()) labels := makeLabels(c.GetLabels(), c.GetAnnotations())
@ -316,29 +328,11 @@ func (ds *dockerService) makeSandboxDockerConfig(c *runtimeApi.PodSandboxConfig,
// Apply linux-specific options. // Apply linux-specific options.
if lc := c.GetLinux(); lc != nil { if lc := c.GetLinux(); lc != nil {
// Apply Cgroup options. if err := ds.applySandboxLinuxOptions(hc, lc, createConfig, image); err != nil {
// TODO: Check if this works with per-pod cgroups. return nil, err
hc.CgroupParent = lc.GetCgroupParent() }
}
// Apply namespace options.
hc.NetworkMode, hc.UTSMode, hc.PidMode = "", "", ""
nsOpts := lc.GetNamespaceOptions()
if nsOpts != nil {
if nsOpts.GetHostNetwork() {
hc.NetworkMode = namespaceModeHost
} else {
// Assume kubelet uses either the cni or the kubenet plugin.
// TODO: support docker networking.
hc.NetworkMode = "none"
}
if nsOpts.GetHostIpc() {
hc.IpcMode = namespaceModeHost
}
if nsOpts.GetHostPid() {
hc.PidMode = namespaceModeHost
}
}
}
// Set port mappings. // Set port mappings.
exposedPorts, portBindings := makePortsAndBindings(c.GetPortMappings()) exposedPorts, portBindings := makePortsAndBindings(c.GetPortMappings())
createConfig.Config.ExposedPorts = exposedPorts createConfig.Config.ExposedPorts = exposedPorts
@ -355,10 +349,11 @@ func (ds *dockerService) makeSandboxDockerConfig(c *runtimeApi.PodSandboxConfig,
setSandboxResources(hc) setSandboxResources(hc)
// Set security options. // Set security options.
hc.SecurityOpt, err = getSandboxSecurityOpts(c, ds.seccompProfileRoot) securityOpts, err := getSandboxSecurityOpts(c, ds.seccompProfileRoot)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to generate sandbox security options for sandbox %q: %v", c.Metadata.GetName(), err) return nil, fmt.Errorf("failed to generate sandbox security options for sandbox %q: %v", c.Metadata.GetName(), err)
} }
hc.SecurityOpt = append(hc.SecurityOpt, securityOpts...)
return createConfig, nil return createConfig, nil
} }

View File

@ -186,7 +186,13 @@ func TestHostNetworkPluginInvocation(t *testing.T) {
map[string]string{"annotation": ns}, map[string]string{"annotation": ns},
) )
hostNetwork := true hostNetwork := true
c.Linux = &runtimeApi.LinuxPodSandboxConfig{NamespaceOptions: &runtimeApi.NamespaceOption{HostNetwork: &hostNetwork}} c.Linux = &runtimeApi.LinuxPodSandboxConfig{
SecurityContext: &runtimeApi.LinuxSandboxSecurityContext{
NamespaceOptions: &runtimeApi.NamespaceOption{
HostNetwork: &hostNetwork,
},
},
}
cID := kubecontainer.ContainerID{Type: runtimeName, ID: fmt.Sprintf("/%v", makeSandboxName(c))} cID := kubecontainer.ContainerID{Type: runtimeName, ID: fmt.Sprintf("/%v", makeSandboxName(c))}
// No calls to network plugin are expected // No calls to network plugin are expected

View File

@ -123,13 +123,16 @@ func extractLabels(input map[string]string) (map[string]string, map[string]strin
// '<HostPath>:<ContainerPath>:Z', if the volume requires SELinux // '<HostPath>:<ContainerPath>:Z', if the volume requires SELinux
// relabeling and the pod provides an SELinux label // relabeling and the pod provides an SELinux label
func generateMountBindings(mounts []*runtimeApi.Mount) (result []string) { func generateMountBindings(mounts []*runtimeApi.Mount) (result []string) {
// TODO: resolve podHasSELinuxLabel
for _, m := range mounts { for _, m := range mounts {
bind := fmt.Sprintf("%s:%s", m.GetHostPath(), m.GetContainerPath()) bind := fmt.Sprintf("%s:%s", m.GetHostPath(), m.GetContainerPath())
readOnly := m.GetReadonly() readOnly := m.GetReadonly()
if readOnly { if readOnly {
bind += ":ro" bind += ":ro"
} }
// Only request relabeling if the pod provides an SELinux context. If the pod
// does not provide an SELinux context relabeling will label the volume with
// the container's randomly allocated MCS label. This would restrict access
// to the volume to the container which mounts it first.
if m.GetSelinuxRelabel() { if m.GetSelinuxRelabel() {
if readOnly { if readOnly {
bind += ",Z" bind += ",Z"

View File

@ -0,0 +1,153 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dockershim
import (
"fmt"
"strconv"
dockercontainer "github.com/docker/engine-api/types/container"
"k8s.io/kubernetes/pkg/api"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"k8s.io/kubernetes/pkg/securitycontext"
)
// applySandboxSecurityContext updates docker sandbox options according to security context.
func applySandboxSecurityContext(lc *runtimeapi.LinuxPodSandboxConfig, config *dockercontainer.Config, hc *dockercontainer.HostConfig) {
if lc == nil {
return
}
var sc *runtimeapi.LinuxContainerSecurityContext
if lc.SecurityContext != nil {
sc = &runtimeapi.LinuxContainerSecurityContext{
SupplementalGroups: lc.SecurityContext.SupplementalGroups,
RunAsUser: lc.SecurityContext.RunAsUser,
ReadonlyRootfs: lc.SecurityContext.ReadonlyRootfs,
SelinuxOptions: lc.SecurityContext.SelinuxOptions,
NamespaceOptions: lc.SecurityContext.NamespaceOptions,
}
}
modifyContainerConfig(sc, config)
modifyHostConfig(sc, "", hc)
}
// applyContainerSecurityContext updates docker container options according to security context.
func applyContainerSecurityContext(lc *runtimeapi.LinuxContainerConfig, sandboxID string, config *dockercontainer.Config, hc *dockercontainer.HostConfig) {
if lc == nil {
return
}
modifyContainerConfig(lc.SecurityContext, config)
modifyHostConfig(lc.SecurityContext, sandboxID, hc)
return
}
// modifyContainerConfig applies container security context config to dockercontainer.Config.
func modifyContainerConfig(sc *runtimeapi.LinuxContainerSecurityContext, config *dockercontainer.Config) {
if sc != nil && sc.RunAsUser != nil {
config.User = strconv.FormatInt(sc.GetRunAsUser(), 10)
}
}
// modifyHostConfig applies security context config to dockercontainer.HostConfig.
func modifyHostConfig(sc *runtimeapi.LinuxContainerSecurityContext, sandboxID string, hostConfig *dockercontainer.HostConfig) {
// Apply namespace options.
modifyNamespaceOptions(sc.GetNamespaceOptions(), sandboxID, hostConfig)
if sc == nil {
return
}
// Apply supplemental groups.
for _, group := range sc.SupplementalGroups {
hostConfig.GroupAdd = append(hostConfig.GroupAdd, strconv.FormatInt(group, 10))
}
// Apply security context for the container.
if sc.Privileged != nil {
hostConfig.Privileged = sc.GetPrivileged()
}
if sc.ReadonlyRootfs != nil {
hostConfig.ReadonlyRootfs = sc.GetReadonlyRootfs()
}
if sc.Capabilities != nil {
hostConfig.CapAdd = sc.GetCapabilities().GetAddCapabilities()
hostConfig.CapDrop = sc.GetCapabilities().GetDropCapabilities()
}
if sc.SelinuxOptions != nil {
hostConfig.SecurityOpt = securitycontext.ModifySecurityOptions(
hostConfig.SecurityOpt,
&api.SELinuxOptions{
User: sc.SelinuxOptions.GetUser(),
Role: sc.SelinuxOptions.GetRole(),
Type: sc.SelinuxOptions.GetType(),
Level: sc.SelinuxOptions.GetLevel(),
},
)
}
}
// modifyNamespaceOptions applies namespaceoptions to dockercontainer.HostConfig.
func modifyNamespaceOptions(nsOpts *runtimeapi.NamespaceOption, sandboxID string, hostConfig *dockercontainer.HostConfig) {
hostNetwork := false
if nsOpts != nil {
if nsOpts.HostNetwork != nil {
hostNetwork = nsOpts.GetHostNetwork()
}
if nsOpts.GetHostPid() {
hostConfig.PidMode = namespaceModeHost
}
if nsOpts.GetHostIpc() {
hostConfig.IpcMode = namespaceModeHost
}
}
// Set for sandbox if sandboxID is not provided.
if sandboxID == "" {
modifyHostNetworkOptionForSandbox(hostNetwork, hostConfig)
} else {
// Set for container if sandboxID is provided.
modifyHostNetworkOptionForContainer(hostNetwork, sandboxID, hostConfig)
}
}
// modifyHostNetworkOptionForSandbox applies NetworkMode/UTSMode to sandbox's dockercontainer.HostConfig.
func modifyHostNetworkOptionForSandbox(hostNetwork bool, hc *dockercontainer.HostConfig) {
if hostNetwork {
hc.NetworkMode = namespaceModeHost
} else {
// Assume kubelet uses either the cni or the kubenet plugin.
// TODO: support docker networking.
hc.NetworkMode = "none"
}
}
// modifyHostNetworkOptionForContainer applies NetworkMode/UTSMode to container's dockercontainer.HostConfig.
func modifyHostNetworkOptionForContainer(hostNetwork bool, sandboxID string, hc *dockercontainer.HostConfig) {
sandboxNSMode := fmt.Sprintf("container:%v", sandboxID)
hc.NetworkMode = dockercontainer.NetworkMode(sandboxNSMode)
hc.IpcMode = dockercontainer.IpcMode(sandboxNSMode)
hc.UTSMode = ""
hc.PidMode = ""
if hostNetwork {
hc.UTSMode = namespaceModeHost
}
}

View File

@ -0,0 +1,265 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dockershim
import (
"fmt"
"strconv"
"testing"
dockercontainer "github.com/docker/engine-api/types/container"
"github.com/stretchr/testify/assert"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"k8s.io/kubernetes/pkg/securitycontext"
)
func TestModifyContainerConfig(t *testing.T) {
var uid int64 = 123
cases := []struct {
name string
sc *runtimeapi.LinuxContainerSecurityContext
expected *dockercontainer.Config
}{
{
name: "container.SecurityContext.RunAsUser set",
sc: &runtimeapi.LinuxContainerSecurityContext{
RunAsUser: &uid,
},
expected: &dockercontainer.Config{
User: strconv.FormatInt(uid, 10),
},
},
{
name: "no RunAsUser value set",
sc: &runtimeapi.LinuxContainerSecurityContext{},
expected: &dockercontainer.Config{},
},
}
for _, tc := range cases {
dockerCfg := &dockercontainer.Config{}
modifyContainerConfig(tc.sc, dockerCfg)
assert.Equal(t, tc.expected, dockerCfg, "[Test case %q]", tc.name)
}
}
func TestModifyHostConfig(t *testing.T) {
priv := true
setNetworkHC := &dockercontainer.HostConfig{
NetworkMode: "none",
}
setPrivSC := &runtimeapi.LinuxContainerSecurityContext{}
setPrivSC.Privileged = &priv
setPrivHC := &dockercontainer.HostConfig{
Privileged: true,
NetworkMode: "none",
}
setCapsHC := &dockercontainer.HostConfig{
NetworkMode: "none",
CapAdd: []string{"addCapA", "addCapB"},
CapDrop: []string{"dropCapA", "dropCapB"},
}
setSELinuxHC := &dockercontainer.HostConfig{
NetworkMode: "none",
SecurityOpt: []string{
fmt.Sprintf("%s:%s", securitycontext.DockerLabelUser, "user"),
fmt.Sprintf("%s:%s", securitycontext.DockerLabelRole, "role"),
fmt.Sprintf("%s:%s", securitycontext.DockerLabelType, "type"),
fmt.Sprintf("%s:%s", securitycontext.DockerLabelLevel, "level"),
},
}
cases := []struct {
name string
sc *runtimeapi.LinuxContainerSecurityContext
expected *dockercontainer.HostConfig
}{
{
name: "fully set container.SecurityContext",
sc: fullValidSecurityContext(),
expected: fullValidHostConfig(),
},
{
name: "empty container.SecurityContext",
sc: &runtimeapi.LinuxContainerSecurityContext{},
expected: setNetworkHC,
},
{
name: "container.SecurityContext.Privileged",
sc: setPrivSC,
expected: setPrivHC,
},
{
name: "container.SecurityContext.Capabilities",
sc: &runtimeapi.LinuxContainerSecurityContext{
Capabilities: inputCapabilities(),
},
expected: setCapsHC,
},
{
name: "container.SecurityContext.SELinuxOptions",
sc: &runtimeapi.LinuxContainerSecurityContext{
SelinuxOptions: inputSELinuxOptions(),
},
expected: setSELinuxHC,
},
}
for _, tc := range cases {
dockerCfg := &dockercontainer.HostConfig{}
modifyHostConfig(tc.sc, "", dockerCfg)
assert.Equal(t, tc.expected, dockerCfg, "[Test case %q]", tc.name)
}
}
func TestModifyHostConfigWithGroups(t *testing.T) {
supplementalGroupsSC := &runtimeapi.LinuxContainerSecurityContext{}
supplementalGroupsSC.SupplementalGroups = []int64{2222}
supplementalGroupHC := &dockercontainer.HostConfig{NetworkMode: "none"}
supplementalGroupHC.GroupAdd = []string{"2222"}
testCases := []struct {
name string
securityContext *runtimeapi.LinuxContainerSecurityContext
expected *dockercontainer.HostConfig
}{
{
name: "nil",
securityContext: nil,
expected: &dockercontainer.HostConfig{NetworkMode: "none"},
},
{
name: "SupplementalGroup",
securityContext: supplementalGroupsSC,
expected: supplementalGroupHC,
},
}
for _, tc := range testCases {
dockerCfg := &dockercontainer.HostConfig{}
modifyHostConfig(tc.securityContext, "", dockerCfg)
assert.Equal(t, tc.expected, dockerCfg, "[Test case %q]", tc.name)
}
}
func TestModifyHostConfigWithSandboxID(t *testing.T) {
priv := true
sandboxID := "sandbox"
sandboxNSMode := fmt.Sprintf("container:%v", sandboxID)
setPrivSC := &runtimeapi.LinuxContainerSecurityContext{}
setPrivSC.Privileged = &priv
setPrivHC := &dockercontainer.HostConfig{
Privileged: true,
IpcMode: dockercontainer.IpcMode(sandboxNSMode),
NetworkMode: dockercontainer.NetworkMode(sandboxNSMode),
}
setCapsHC := &dockercontainer.HostConfig{
CapAdd: []string{"addCapA", "addCapB"},
CapDrop: []string{"dropCapA", "dropCapB"},
IpcMode: dockercontainer.IpcMode(sandboxNSMode),
NetworkMode: dockercontainer.NetworkMode(sandboxNSMode),
}
setSELinuxHC := &dockercontainer.HostConfig{
SecurityOpt: []string{
fmt.Sprintf("%s:%s", securitycontext.DockerLabelUser, "user"),
fmt.Sprintf("%s:%s", securitycontext.DockerLabelRole, "role"),
fmt.Sprintf("%s:%s", securitycontext.DockerLabelType, "type"),
fmt.Sprintf("%s:%s", securitycontext.DockerLabelLevel, "level"),
},
IpcMode: dockercontainer.IpcMode(sandboxNSMode),
NetworkMode: dockercontainer.NetworkMode(sandboxNSMode),
}
cases := []struct {
name string
sc *runtimeapi.LinuxContainerSecurityContext
expected *dockercontainer.HostConfig
}{
{
name: "container.SecurityContext.Privileged",
sc: setPrivSC,
expected: setPrivHC,
},
{
name: "container.SecurityContext.Capabilities",
sc: &runtimeapi.LinuxContainerSecurityContext{
Capabilities: inputCapabilities(),
},
expected: setCapsHC,
},
{
name: "container.SecurityContext.SELinuxOptions",
sc: &runtimeapi.LinuxContainerSecurityContext{
SelinuxOptions: inputSELinuxOptions(),
},
expected: setSELinuxHC,
},
}
for _, tc := range cases {
dockerCfg := &dockercontainer.HostConfig{}
modifyHostConfig(tc.sc, sandboxID, dockerCfg)
assert.Equal(t, tc.expected, dockerCfg, "[Test case %q]", tc.name)
}
}
func fullValidSecurityContext() *runtimeapi.LinuxContainerSecurityContext {
priv := true
return &runtimeapi.LinuxContainerSecurityContext{
Privileged: &priv,
Capabilities: inputCapabilities(),
SelinuxOptions: inputSELinuxOptions(),
}
}
func inputCapabilities() *runtimeapi.Capability {
return &runtimeapi.Capability{
AddCapabilities: []string{"addCapA", "addCapB"},
DropCapabilities: []string{"dropCapA", "dropCapB"},
}
}
func inputSELinuxOptions() *runtimeapi.SELinuxOption {
user := "user"
role := "role"
stype := "type"
level := "level"
return &runtimeapi.SELinuxOption{
User: &user,
Role: &role,
Type: &stype,
Level: &level,
}
}
func fullValidHostConfig() *dockercontainer.HostConfig {
return &dockercontainer.HostConfig{
Privileged: true,
NetworkMode: "none",
CapAdd: []string{"addCapA", "addCapB"},
CapDrop: []string{"dropCapA", "dropCapB"},
SecurityOpt: []string{
fmt.Sprintf("%s:%s", securitycontext.DockerLabelUser, "user"),
fmt.Sprintf("%s:%s", securitycontext.DockerLabelRole, "role"),
fmt.Sprintf("%s:%s", securitycontext.DockerLabelType, "type"),
fmt.Sprintf("%s:%s", securitycontext.DockerLabelLevel, "level"),
},
}
}

View File

@ -2474,7 +2474,7 @@ func (dm *DockerManager) isImageRoot(image string) (bool, error) {
return false, fmt.Errorf("unable to inspect image %s, nil Config", image) return false, fmt.Errorf("unable to inspect image %s, nil Config", image)
} }
user := getUidFromUser(img.Config.User) user := GetUidFromUser(img.Config.User)
// if no user is defined container will run as root // if no user is defined container will run as root
if user == "" { if user == "" {
return true, nil return true, nil
@ -2488,8 +2488,8 @@ func (dm *DockerManager) isImageRoot(image string) (bool, error) {
return uid == 0, nil return uid == 0, nil
} }
// getUidFromUser splits the uid out of an uid:gid string. // GetUidFromUser splits the uid out of an uid:gid string.
func getUidFromUser(id string) string { func GetUidFromUser(id string) string {
if id == "" { if id == "" {
return id return id
} }

View File

@ -1454,7 +1454,7 @@ func TestGetUidFromUser(t *testing.T) {
}, },
} }
for k, v := range tests { for k, v := range tests {
actual := getUidFromUser(v.input) actual := GetUidFromUser(v.input)
if actual != v.expect { if actual != v.expect {
t.Errorf("%s failed. Expected %s but got %s", k, v.expect, actual) t.Errorf("%s failed. Expected %s but got %s", k, v.expect, actual)
} }

View File

@ -25,6 +25,7 @@ go_library(
"kuberuntime_sandbox.go", "kuberuntime_sandbox.go",
"labels.go", "labels.go",
"legacy.go", "legacy.go",
"security_context.go",
], ],
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
@ -47,11 +48,13 @@ go_library(
"//pkg/kubelet/types:go_default_library", "//pkg/kubelet/types:go_default_library",
"//pkg/kubelet/util/cache:go_default_library", "//pkg/kubelet/util/cache:go_default_library",
"//pkg/kubelet/util/format:go_default_library", "//pkg/kubelet/util/format:go_default_library",
"//pkg/securitycontext:go_default_library",
"//pkg/types:go_default_library", "//pkg/types:go_default_library",
"//pkg/util/errors:go_default_library", "//pkg/util/errors:go_default_library",
"//pkg/util/flowcontrol:go_default_library", "//pkg/util/flowcontrol:go_default_library",
"//pkg/util/parsers:go_default_library", "//pkg/util/parsers:go_default_library",
"//pkg/util/runtime:go_default_library", "//pkg/util/runtime:go_default_library",
"//pkg/util/selinux:go_default_library",
"//pkg/util/sets:go_default_library", "//pkg/util/sets:go_default_library",
"//pkg/util/term:go_default_library", "//pkg/util/term:go_default_library",
"//vendor:github.com/coreos/go-semver/semver", "//vendor:github.com/coreos/go-semver/semver",

View File

@ -146,6 +146,16 @@ func getContainerSpec(pod *api.Pod, containerName string) *api.Container {
return nil return nil
} }
// getImageUID gets uid that will run the command(s) from image.
func (m *kubeGenericRuntimeManager) getImageUser(image string) (int64, error) {
imageStatus, err := m.imageService.ImageStatus(&runtimeApi.ImageSpec{Image: &image})
if err != nil {
return 0, err
}
return imageStatus.GetUid(), nil
}
// isContainerFailed returns true if container has exited and exitcode is not zero. // isContainerFailed returns true if container has exited and exitcode is not zero.
func isContainerFailed(status *kubecontainer.ContainerStatus) bool { func isContainerFailed(status *kubecontainer.ContainerStatus) bool {
if status.State == kubecontainer.ContainerStateExited && status.ExitCode != 0 { if status.State == kubecontainer.ContainerStateExited && status.ExitCode != 0 {

View File

@ -40,6 +40,7 @@ import (
"k8s.io/kubernetes/pkg/kubelet/util/format" "k8s.io/kubernetes/pkg/kubelet/util/format"
kubetypes "k8s.io/kubernetes/pkg/types" kubetypes "k8s.io/kubernetes/pkg/types"
utilruntime "k8s.io/kubernetes/pkg/util/runtime" utilruntime "k8s.io/kubernetes/pkg/util/runtime"
"k8s.io/kubernetes/pkg/util/selinux"
"k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/util/term" "k8s.io/kubernetes/pkg/util/term"
) )
@ -136,9 +137,17 @@ func (m *kubeGenericRuntimeManager) generateContainerConfig(container *api.Conta
return nil, err return nil, err
} }
// Verify RunAsNonRoot.
imageUser, err := m.getImageUser(container.Image)
if err != nil {
return nil, err
}
if err := verifyRunAsNonRoot(pod, container, imageUser); err != nil {
return nil, err
}
command, args := kubecontainer.ExpandContainerCommandAndArgs(container, opts.Envs) command, args := kubecontainer.ExpandContainerCommandAndArgs(container, opts.Envs)
containerLogsPath := buildContainerLogsPath(container.Name, restartCount) containerLogsPath := buildContainerLogsPath(container.Name, restartCount)
podHasSELinuxLabel := pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SELinuxOptions != nil
restartCountUint32 := uint32(restartCount) restartCountUint32 := uint32(restartCount)
config := &runtimeApi.ContainerConfig{ config := &runtimeApi.ContainerConfig{
Metadata: &runtimeApi.ContainerMetadata{ Metadata: &runtimeApi.ContainerMetadata{
@ -151,24 +160,13 @@ func (m *kubeGenericRuntimeManager) generateContainerConfig(container *api.Conta
WorkingDir: &container.WorkingDir, WorkingDir: &container.WorkingDir,
Labels: newContainerLabels(container, pod), Labels: newContainerLabels(container, pod),
Annotations: newContainerAnnotations(container, pod, restartCount), Annotations: newContainerAnnotations(container, pod, restartCount),
Mounts: m.makeMounts(opts, container, podHasSELinuxLabel),
Devices: makeDevices(opts), Devices: makeDevices(opts),
Mounts: m.makeMounts(opts, container),
LogPath: &containerLogsPath, LogPath: &containerLogsPath,
Stdin: &container.Stdin, Stdin: &container.Stdin,
StdinOnce: &container.StdinOnce, StdinOnce: &container.StdinOnce,
Tty: &container.TTY, Tty: &container.TTY,
Linux: m.generateLinuxContainerConfig(container, pod), Linux: m.generateLinuxContainerConfig(container, pod, imageUser),
}
// set privileged and readonlyRootfs
if container.SecurityContext != nil {
securityContext := container.SecurityContext
if securityContext.Privileged != nil {
config.Privileged = securityContext.Privileged
}
if securityContext.ReadOnlyRootFilesystem != nil {
config.ReadonlyRootfs = securityContext.ReadOnlyRootFilesystem
}
} }
// set environment variables // set environment variables
@ -186,9 +184,10 @@ func (m *kubeGenericRuntimeManager) generateContainerConfig(container *api.Conta
} }
// generateLinuxContainerConfig generates linux container config for kubelet runtime api. // generateLinuxContainerConfig generates linux container config for kubelet runtime api.
func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *api.Container, pod *api.Pod) *runtimeApi.LinuxContainerConfig { func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *api.Container, pod *api.Pod, imageUser int64) *runtimeApi.LinuxContainerConfig {
linuxConfig := &runtimeApi.LinuxContainerConfig{ lc := &runtimeApi.LinuxContainerConfig{
Resources: &runtimeApi.LinuxContainerResources{}, Resources: &runtimeApi.LinuxContainerResources{},
SecurityContext: m.determineEffectiveSecurityContext(pod, container, imageUser),
} }
// set linux container resources // set linux container resources
@ -208,49 +207,23 @@ func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *api.
// of CPU shares. // of CPU shares.
cpuShares = milliCPUToShares(cpuRequest.MilliValue()) cpuShares = milliCPUToShares(cpuRequest.MilliValue())
} }
linuxConfig.Resources.CpuShares = &cpuShares lc.Resources.CpuShares = &cpuShares
if memoryLimit != 0 { if memoryLimit != 0 {
linuxConfig.Resources.MemoryLimitInBytes = &memoryLimit lc.Resources.MemoryLimitInBytes = &memoryLimit
} }
// Set OOM score of the container based on qos policy. Processes in lower-priority pods should // Set OOM score of the container based on qos policy. Processes in lower-priority pods should
// be killed first if the system runs out of memory. // be killed first if the system runs out of memory.
linuxConfig.Resources.OomScoreAdj = &oomScoreAdj lc.Resources.OomScoreAdj = &oomScoreAdj
if m.cpuCFSQuota { if m.cpuCFSQuota {
// if cpuLimit.Amount is nil, then the appropriate default value is returned // if cpuLimit.Amount is nil, then the appropriate default value is returned
// to allow full usage of cpu resource. // to allow full usage of cpu resource.
cpuQuota, cpuPeriod := milliCPUToQuota(cpuLimit.MilliValue()) cpuQuota, cpuPeriod := milliCPUToQuota(cpuLimit.MilliValue())
linuxConfig.Resources.CpuQuota = &cpuQuota lc.Resources.CpuQuota = &cpuQuota
linuxConfig.Resources.CpuPeriod = &cpuPeriod lc.Resources.CpuPeriod = &cpuPeriod
} }
// set security context options return lc
if container.SecurityContext != nil {
securityContext := container.SecurityContext
if securityContext.Capabilities != nil {
linuxConfig.Capabilities = &runtimeApi.Capability{
AddCapabilities: make([]string, len(securityContext.Capabilities.Add)),
DropCapabilities: make([]string, len(securityContext.Capabilities.Drop)),
}
for index, value := range securityContext.Capabilities.Add {
linuxConfig.Capabilities.AddCapabilities[index] = string(value)
}
for index, value := range securityContext.Capabilities.Drop {
linuxConfig.Capabilities.DropCapabilities[index] = string(value)
}
}
if securityContext.SELinuxOptions != nil {
linuxConfig.SelinuxOptions = &runtimeApi.SELinuxOption{
User: &securityContext.SELinuxOptions.User,
Role: &securityContext.SELinuxOptions.Role,
Type: &securityContext.SELinuxOptions.Type,
Level: &securityContext.SELinuxOptions.Level,
}
}
}
return linuxConfig
} }
// makeDevices generates container devices for kubelet runtime api. // makeDevices generates container devices for kubelet runtime api.
@ -270,21 +243,20 @@ func makeDevices(opts *kubecontainer.RunContainerOptions) []*runtimeApi.Device {
} }
// makeMounts generates container volume mounts for kubelet runtime api. // makeMounts generates container volume mounts for kubelet runtime api.
func (m *kubeGenericRuntimeManager) makeMounts(opts *kubecontainer.RunContainerOptions, container *api.Container, podHasSELinuxLabel bool) []*runtimeApi.Mount { func (m *kubeGenericRuntimeManager) makeMounts(opts *kubecontainer.RunContainerOptions, container *api.Container) []*runtimeApi.Mount {
volumeMounts := []*runtimeApi.Mount{} volumeMounts := []*runtimeApi.Mount{}
for idx := range opts.Mounts { for idx := range opts.Mounts {
v := opts.Mounts[idx] v := opts.Mounts[idx]
m := &runtimeApi.Mount{ selinuxRelabel := v.SELinuxRelabel && selinux.SELinuxEnabled()
mount := &runtimeApi.Mount{
HostPath: &v.HostPath, HostPath: &v.HostPath,
ContainerPath: &v.ContainerPath, ContainerPath: &v.ContainerPath,
Readonly: &v.ReadOnly, Readonly: &v.ReadOnly,
} SelinuxRelabel: &selinuxRelabel,
if podHasSELinuxLabel && v.SELinuxRelabel {
m.SelinuxRelabel = &v.SELinuxRelabel
} }
volumeMounts = append(volumeMounts, m) volumeMounts = append(volumeMounts, mount)
} }
// The reason we create and mount the log file in here (not in kubelet) is because // The reason we create and mount the log file in here (not in kubelet) is because
@ -301,9 +273,11 @@ func (m *kubeGenericRuntimeManager) makeMounts(opts *kubecontainer.RunContainerO
glog.Errorf("Error on creating termination-log file %q: %v", containerLogPath, err) glog.Errorf("Error on creating termination-log file %q: %v", containerLogPath, err)
} else { } else {
fs.Close() fs.Close()
selinuxRelabel := selinux.SELinuxEnabled()
volumeMounts = append(volumeMounts, &runtimeApi.Mount{ volumeMounts = append(volumeMounts, &runtimeApi.Mount{
HostPath: &containerLogPath, HostPath: &containerLogPath,
ContainerPath: &container.TerminationMessagePath, ContainerPath: &container.TerminationMessagePath,
SelinuxRelabel: &selinuxRelabel,
}) })
} }
} }

View File

@ -120,7 +120,7 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxConfig(pod *api.Pod, attem
// TODO: refactor kubelet to get cgroup parent for pod instead of containers // TODO: refactor kubelet to get cgroup parent for pod instead of containers
cgroupParent = opts.CgroupParent cgroupParent = opts.CgroupParent
} }
podSandboxConfig.Linux = generatePodSandboxLinuxConfig(pod, cgroupParent) podSandboxConfig.Linux = m.generatePodSandboxLinuxConfig(pod, cgroupParent)
if len(portMappings) > 0 { if len(portMappings) > 0 {
podSandboxConfig.PortMappings = portMappings podSandboxConfig.PortMappings = portMappings
} }
@ -129,26 +129,46 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxConfig(pod *api.Pod, attem
} }
// generatePodSandboxLinuxConfig generates LinuxPodSandboxConfig from api.Pod. // generatePodSandboxLinuxConfig generates LinuxPodSandboxConfig from api.Pod.
func generatePodSandboxLinuxConfig(pod *api.Pod, cgroupParent string) *runtimeApi.LinuxPodSandboxConfig { func (m *kubeGenericRuntimeManager) generatePodSandboxLinuxConfig(pod *api.Pod, cgroupParent string) *runtimeApi.LinuxPodSandboxConfig {
if pod.Spec.SecurityContext == nil && cgroupParent == "" { if pod.Spec.SecurityContext == nil && cgroupParent == "" {
return nil return nil
} }
linuxPodSandboxConfig := &runtimeApi.LinuxPodSandboxConfig{} lc := &runtimeApi.LinuxPodSandboxConfig{}
if pod.Spec.SecurityContext != nil {
securityContext := pod.Spec.SecurityContext
linuxPodSandboxConfig.NamespaceOptions = &runtimeApi.NamespaceOption{
HostNetwork: &securityContext.HostNetwork,
HostIpc: &securityContext.HostIPC,
HostPid: &securityContext.HostPID,
}
}
if cgroupParent != "" { if cgroupParent != "" {
linuxPodSandboxConfig.CgroupParent = &cgroupParent lc.CgroupParent = &cgroupParent
}
if pod.Spec.SecurityContext != nil {
sc := pod.Spec.SecurityContext
lc.SecurityContext = &runtimeApi.LinuxSandboxSecurityContext{
NamespaceOptions: &runtimeApi.NamespaceOption{
HostNetwork: &sc.HostNetwork,
HostIpc: &sc.HostIPC,
HostPid: &sc.HostPID,
},
RunAsUser: sc.RunAsUser,
} }
return linuxPodSandboxConfig if sc.FSGroup != nil {
lc.SecurityContext.SupplementalGroups = append(lc.SecurityContext.SupplementalGroups, *sc.FSGroup)
}
if groups := m.runtimeHelper.GetExtraSupplementalGroupsForPod(pod); len(groups) > 0 {
lc.SecurityContext.SupplementalGroups = append(lc.SecurityContext.SupplementalGroups, groups...)
}
if sc.SupplementalGroups != nil {
lc.SecurityContext.SupplementalGroups = append(lc.SecurityContext.SupplementalGroups, sc.SupplementalGroups...)
}
if sc.SELinuxOptions != nil {
lc.SecurityContext.SelinuxOptions = &runtimeApi.SELinuxOption{
User: &sc.SELinuxOptions.User,
Role: &sc.SELinuxOptions.Role,
Type: &sc.SELinuxOptions.Type,
Level: &sc.SELinuxOptions.Level,
}
}
}
return lc
} }
// getKubeletSandboxes lists all (or just the running) sandboxes managed by kubelet. // getKubeletSandboxes lists all (or just the running) sandboxes managed by kubelet.

View File

@ -0,0 +1,128 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kuberuntime
import (
"fmt"
"k8s.io/kubernetes/pkg/api"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"k8s.io/kubernetes/pkg/securitycontext"
)
// determineEffectiveSecurityContext gets container's security context from api.Pod and api.Container.
func (m *kubeGenericRuntimeManager) determineEffectiveSecurityContext(pod *api.Pod, container *api.Container, imageUser int64) *runtimeapi.LinuxContainerSecurityContext {
effectiveSc := securitycontext.DetermineEffectiveSecurityContext(pod, container)
synthesized := convertToRuntimeSecurityContext(effectiveSc)
if synthesized == nil {
synthesized = &runtimeapi.LinuxContainerSecurityContext{}
}
// set RunAsUser.
if synthesized.RunAsUser == nil {
synthesized.RunAsUser = &imageUser
}
// set namespace options and supplemental groups.
podSc := pod.Spec.SecurityContext
if podSc == nil {
return synthesized
}
synthesized.NamespaceOptions = &runtimeapi.NamespaceOption{
HostNetwork: &podSc.HostNetwork,
HostIpc: &podSc.HostIPC,
HostPid: &podSc.HostPID,
}
if podSc.FSGroup != nil {
synthesized.SupplementalGroups = append(synthesized.SupplementalGroups, *podSc.FSGroup)
}
if groups := m.runtimeHelper.GetExtraSupplementalGroupsForPod(pod); len(groups) > 0 {
synthesized.SupplementalGroups = append(synthesized.SupplementalGroups, groups...)
}
if podSc.SupplementalGroups != nil {
synthesized.SupplementalGroups = append(synthesized.SupplementalGroups, podSc.SupplementalGroups...)
}
return synthesized
}
// verifyRunAsNonRoot verifies RunAsNonRoot.
func verifyRunAsNonRoot(pod *api.Pod, container *api.Container, imageUser int64) error {
effectiveSc := securitycontext.DetermineEffectiveSecurityContext(pod, container)
if effectiveSc == nil || effectiveSc.RunAsNonRoot == nil {
return nil
}
if effectiveSc.RunAsUser != nil && *effectiveSc.RunAsUser == 0 {
return fmt.Errorf("container's runAsUser breaks non-root policy")
}
if imageUser == 0 {
return fmt.Errorf("container has runAsNonRoot and image will run as root")
}
return nil
}
// convertToRuntimeSecurityContext converts api.SecurityContext to runtimeapi.SecurityContext.
func convertToRuntimeSecurityContext(securityContext *api.SecurityContext) *runtimeapi.LinuxContainerSecurityContext {
if securityContext == nil {
return nil
}
return &runtimeapi.LinuxContainerSecurityContext{
RunAsUser: securityContext.RunAsUser,
Privileged: securityContext.Privileged,
ReadonlyRootfs: securityContext.ReadOnlyRootFilesystem,
Capabilities: convertToRuntimeCapabilities(securityContext.Capabilities),
SelinuxOptions: convertToRuntimeSELinuxOption(securityContext.SELinuxOptions),
}
}
// convertToRuntimeSELinuxOption converts api.SELinuxOptions to runtimeapi.SELinuxOption.
func convertToRuntimeSELinuxOption(opts *api.SELinuxOptions) *runtimeapi.SELinuxOption {
if opts == nil {
return nil
}
return &runtimeapi.SELinuxOption{
User: &opts.User,
Role: &opts.Role,
Type: &opts.Type,
Level: &opts.Level,
}
}
// convertToRuntimeCapabilities converts api.Capabilities to runtimeapi.Capability.
func convertToRuntimeCapabilities(opts *api.Capabilities) *runtimeapi.Capability {
if opts == nil {
return nil
}
capabilities := &runtimeapi.Capability{
AddCapabilities: make([]string, len(opts.Add)),
DropCapabilities: make([]string, len(opts.Drop)),
}
for index, value := range opts.Add {
capabilities.AddCapabilities[index] = string(value)
}
for index, value := range opts.Drop {
capabilities.DropCapabilities[index] = string(value)
}
return capabilities
}

View File

@ -91,13 +91,20 @@ func (p SimpleSecurityContextProvider) ModifyHostConfig(pod *api.Pod, container
} }
if effectiveSC.SELinuxOptions != nil { if effectiveSC.SELinuxOptions != nil {
hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelUser, effectiveSC.SELinuxOptions.User) hostConfig.SecurityOpt = ModifySecurityOptions(hostConfig.SecurityOpt, effectiveSC.SELinuxOptions)
hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelRole, effectiveSC.SELinuxOptions.Role)
hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelType, effectiveSC.SELinuxOptions.Type)
hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelLevel, effectiveSC.SELinuxOptions.Level)
} }
} }
// ModifySecurityOptions adds SELinux options to config.
func ModifySecurityOptions(config []string, selinuxOpts *api.SELinuxOptions) []string {
config = modifySecurityOption(config, DockerLabelUser, selinuxOpts.User)
config = modifySecurityOption(config, DockerLabelRole, selinuxOpts.Role)
config = modifySecurityOption(config, DockerLabelType, selinuxOpts.Type)
config = modifySecurityOption(config, DockerLabelLevel, selinuxOpts.Level)
return config
}
// modifySecurityOption adds the security option of name to the config array with value in the form // modifySecurityOption adds the security option of name to the config array with value in the form
// of name:value // of name:value
func modifySecurityOption(config []string, name, value string) []string { func modifySecurityOption(config []string, name, value string) []string {

View File

@ -104,10 +104,10 @@ func TestModifyHostConfig(t *testing.T) {
setSELinuxHC := &dockercontainer.HostConfig{} setSELinuxHC := &dockercontainer.HostConfig{}
setSELinuxHC.SecurityOpt = []string{ setSELinuxHC.SecurityOpt = []string{
fmt.Sprintf("%s:%s", dockerLabelUser, "user"), fmt.Sprintf("%s:%s", DockerLabelUser, "user"),
fmt.Sprintf("%s:%s", dockerLabelRole, "role"), fmt.Sprintf("%s:%s", DockerLabelRole, "role"),
fmt.Sprintf("%s:%s", dockerLabelType, "type"), fmt.Sprintf("%s:%s", DockerLabelType, "type"),
fmt.Sprintf("%s:%s", dockerLabelLevel, "level"), fmt.Sprintf("%s:%s", DockerLabelLevel, "level"),
} }
// seLinuxLabelsSC := fullValidSecurityContext() // seLinuxLabelsSC := fullValidSecurityContext()
@ -325,10 +325,10 @@ func fullValidHostConfig() *dockercontainer.HostConfig {
CapAdd: []string{"addCapA", "addCapB"}, CapAdd: []string{"addCapA", "addCapB"},
CapDrop: []string{"dropCapA", "dropCapB"}, CapDrop: []string{"dropCapA", "dropCapB"},
SecurityOpt: []string{ SecurityOpt: []string{
fmt.Sprintf("%s:%s", dockerLabelUser, "user"), fmt.Sprintf("%s:%s", DockerLabelUser, "user"),
fmt.Sprintf("%s:%s", dockerLabelRole, "role"), fmt.Sprintf("%s:%s", DockerLabelRole, "role"),
fmt.Sprintf("%s:%s", dockerLabelType, "type"), fmt.Sprintf("%s:%s", DockerLabelType, "type"),
fmt.Sprintf("%s:%s", dockerLabelLevel, "level"), fmt.Sprintf("%s:%s", DockerLabelLevel, "level"),
}, },
} }
} }

View File

@ -41,9 +41,9 @@ type SecurityContextProvider interface {
} }
const ( const (
dockerLabelUser string = "label:user" DockerLabelUser string = "label:user"
dockerLabelRole string = "label:role" DockerLabelRole string = "label:role"
dockerLabelType string = "label:type" DockerLabelType string = "label:type"
dockerLabelLevel string = "label:level" DockerLabelLevel string = "label:level"
dockerLabelDisable string = "label:disable" DockerLabelDisable string = "label:disable"
) )