mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-19 16:49:35 +00:00
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:
commit
a132e5c580
File diff suppressed because it is too large
Load Diff
@ -148,6 +148,26 @@ message NamespaceOption {
|
||||
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
|
||||
// host platforms and Linux-based containers.
|
||||
message LinuxPodSandboxConfig {
|
||||
@ -155,9 +175,8 @@ message LinuxPodSandboxConfig {
|
||||
// The cgroupfs style syntax will be used, but the container runtime can
|
||||
// convert it to systemd semantics if needed.
|
||||
optional string cgroup_parent = 1;
|
||||
// The configurations for the sandbox's namespaces.
|
||||
// This will be used only if the PodSandbox uses namespace for isolation.
|
||||
optional NamespaceOption namespace_options = 2;
|
||||
// LinuxSandboxSecurityContext holds sandbox security attributes.
|
||||
optional LinuxSandboxSecurityContext security_context = 2;
|
||||
}
|
||||
|
||||
// PodSandboxMetadata holds all necessary information for building the sandbox name.
|
||||
@ -409,26 +428,34 @@ message Capability {
|
||||
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
|
||||
// Linux-based containers.
|
||||
message LinuxContainerConfig {
|
||||
// Resources specification for the container.
|
||||
optional LinuxContainerResources resources = 1;
|
||||
// Capabilities to add or drop.
|
||||
optional Capability capabilities = 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;
|
||||
// LinuxContainerSecurityContext configuration for the container.
|
||||
optional LinuxContainerSecurityContext security_context = 2;
|
||||
}
|
||||
|
||||
// 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
|
||||
// tools to store and retrieve arbitrary metadata.
|
||||
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
|
||||
// the log (STDOUT and STDERR) on the host.
|
||||
// E.g.,
|
||||
@ -503,19 +525,18 @@ message ContainerConfig {
|
||||
// container logs are under active discussion in
|
||||
// https://issues.k8s.io/24677. There *may* be future change of direction
|
||||
// for logging as the discussion carries on.
|
||||
optional string log_path = 13;
|
||||
// The hash of container config
|
||||
optional string log_path = 11;
|
||||
|
||||
// Variables for interactive containers, these have very specialized
|
||||
// use-cases (e.g. debugging).
|
||||
// TODO: Determine if we need to continue supporting these fields that are
|
||||
// part of Kubernetes's Container Spec.
|
||||
optional bool stdin = 14;
|
||||
optional bool stdin_once = 15;
|
||||
optional bool tty = 16;
|
||||
optional bool stdin = 12;
|
||||
optional bool stdin_once = 13;
|
||||
optional bool tty = 14;
|
||||
|
||||
// Linux contains configuration specific to Linux containers.
|
||||
optional LinuxContainerConfig linux = 17;
|
||||
optional LinuxContainerConfig linux = 15;
|
||||
}
|
||||
|
||||
message CreateContainerRequest {
|
||||
@ -737,6 +758,8 @@ message Image {
|
||||
repeated string repo_digests = 3;
|
||||
// The size of the image in bytes.
|
||||
optional uint64 size = 4;
|
||||
// The uid that will run the command(s).
|
||||
optional int64 uid = 5;
|
||||
}
|
||||
|
||||
message ListImagesResponse {
|
||||
|
@ -23,6 +23,7 @@ go_library(
|
||||
"helpers.go",
|
||||
"legacy.go",
|
||||
"naming.go",
|
||||
"security_context.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
@ -41,6 +42,7 @@ go_library(
|
||||
"//pkg/kubelet/server/streaming:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//pkg/kubelet/util/ioutils:go_default_library",
|
||||
"//pkg/securitycontext:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//vendor:github.com/docker/engine-api/types",
|
||||
"//vendor:github.com/docker/engine-api/types/container",
|
||||
@ -63,6 +65,7 @@ go_test(
|
||||
"docker_service_test.go",
|
||||
"helpers_test.go",
|
||||
"naming_test.go",
|
||||
"security_context_test.go",
|
||||
],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
@ -76,8 +79,10 @@ go_test(
|
||||
"//pkg/kubelet/network/mock_network:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//pkg/security/apparmor:go_default_library",
|
||||
"//pkg/securitycontext:go_default_library",
|
||||
"//pkg/util/clock:go_default_library",
|
||||
"//vendor:github.com/docker/engine-api/types",
|
||||
"//vendor:github.com/docker/engine-api/types/container",
|
||||
"//vendor:github.com/golang/mock/gomock",
|
||||
"//vendor:github.com/stretchr/testify/assert",
|
||||
],
|
||||
|
@ -18,12 +18,14 @@ package dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dockertypes "github.com/docker/engine-api/types"
|
||||
|
||||
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
|
||||
@ -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")
|
||||
}
|
||||
|
||||
var err error
|
||||
var uid int64
|
||||
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{
|
||||
Id: &image.ID,
|
||||
RepoTags: image.RepoTags,
|
||||
RepoDigests: image.RepoDigests,
|
||||
Size_: &size,
|
||||
Uid: &uid,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func toPullableImageID(id string, image *dockertypes.ImageInspect) string {
|
||||
|
@ -120,34 +120,15 @@ func (ds *dockerService) CreateContainer(podSandboxID string, config *runtimeApi
|
||||
|
||||
// Fill the HostConfig.
|
||||
hc := &dockercontainer.HostConfig{
|
||||
Binds: generateMountBindings(config.GetMounts()),
|
||||
ReadonlyRootfs: config.GetReadonlyRootfs(),
|
||||
Privileged: config.GetPrivileged(),
|
||||
Binds: generateMountBindings(config.GetMounts()),
|
||||
}
|
||||
|
||||
// Apply options derived from the sandbox config.
|
||||
// Apply cgroupsParent derived from the sandbox config.
|
||||
if lc := sandboxConfig.GetLinux(); lc != nil {
|
||||
// Apply Cgroup options.
|
||||
// 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...
|
||||
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.
|
||||
@ -167,6 +148,9 @@ func (ds *dockerService) CreateContainer(podSandboxID string, config *runtimeApi
|
||||
hc.OomScoreAdj = int(rOpts.GetOomScoreAdj())
|
||||
}
|
||||
// Note: ShmSize is handled in kube_docker_client.go
|
||||
|
||||
// Apply security context.
|
||||
applyContainerSecurityContext(lc, podSandboxID, createConfig.Config, hc)
|
||||
}
|
||||
|
||||
// Set devices for container.
|
||||
@ -180,12 +164,12 @@ func (ds *dockerService) CreateContainer(podSandboxID string, config *runtimeApi
|
||||
}
|
||||
hc.Resources.Devices = devices
|
||||
|
||||
var err error
|
||||
hc.SecurityOpt, err = getContainerSecurityOpts(config.Metadata.GetName(), sandboxConfig, ds.seccompProfileRoot)
|
||||
// Apply appArmor and seccomp options.
|
||||
securityOpts, err := getContainerSecurityOpts(config.Metadata.GetName(), sandboxConfig, ds.seccompProfileRoot)
|
||||
if err != nil {
|
||||
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
|
||||
createResp, err := ds.client.CreateContainer(createConfig)
|
||||
|
@ -79,7 +79,7 @@ func (ds *dockerService) RunPodSandbox(config *runtimeApi.PodSandboxConfig) (str
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@ -286,6 +286,18 @@ func (ds *dockerService) ListPodSandbox(filter *runtimeApi.PodSandboxFilter) ([]
|
||||
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) {
|
||||
// Merge annotations and labels because docker supports only labels.
|
||||
labels := makeLabels(c.GetLabels(), c.GetAnnotations())
|
||||
@ -316,29 +328,11 @@ func (ds *dockerService) makeSandboxDockerConfig(c *runtimeApi.PodSandboxConfig,
|
||||
|
||||
// Apply linux-specific options.
|
||||
if lc := c.GetLinux(); lc != nil {
|
||||
// Apply Cgroup options.
|
||||
// TODO: Check if this works with per-pod cgroups.
|
||||
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
|
||||
}
|
||||
if err := ds.applySandboxLinuxOptions(hc, lc, createConfig, image); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Set port mappings.
|
||||
exposedPorts, portBindings := makePortsAndBindings(c.GetPortMappings())
|
||||
createConfig.Config.ExposedPorts = exposedPorts
|
||||
@ -355,10 +349,11 @@ func (ds *dockerService) makeSandboxDockerConfig(c *runtimeApi.PodSandboxConfig,
|
||||
setSandboxResources(hc)
|
||||
|
||||
// Set security options.
|
||||
hc.SecurityOpt, err = getSandboxSecurityOpts(c, ds.seccompProfileRoot)
|
||||
securityOpts, err := getSandboxSecurityOpts(c, ds.seccompProfileRoot)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -186,7 +186,13 @@ func TestHostNetworkPluginInvocation(t *testing.T) {
|
||||
map[string]string{"annotation": ns},
|
||||
)
|
||||
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))}
|
||||
|
||||
// No calls to network plugin are expected
|
||||
|
@ -123,13 +123,16 @@ func extractLabels(input map[string]string) (map[string]string, map[string]strin
|
||||
// '<HostPath>:<ContainerPath>:Z', if the volume requires SELinux
|
||||
// relabeling and the pod provides an SELinux label
|
||||
func generateMountBindings(mounts []*runtimeApi.Mount) (result []string) {
|
||||
// TODO: resolve podHasSELinuxLabel
|
||||
for _, m := range mounts {
|
||||
bind := fmt.Sprintf("%s:%s", m.GetHostPath(), m.GetContainerPath())
|
||||
readOnly := m.GetReadonly()
|
||||
if readOnly {
|
||||
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 readOnly {
|
||||
bind += ",Z"
|
||||
|
153
pkg/kubelet/dockershim/security_context.go
Normal file
153
pkg/kubelet/dockershim/security_context.go
Normal 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
|
||||
}
|
||||
}
|
265
pkg/kubelet/dockershim/security_context_test.go
Normal file
265
pkg/kubelet/dockershim/security_context_test.go
Normal 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"),
|
||||
},
|
||||
}
|
||||
}
|
@ -2474,7 +2474,7 @@ func (dm *DockerManager) isImageRoot(image string) (bool, error) {
|
||||
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 user == "" {
|
||||
return true, nil
|
||||
@ -2488,8 +2488,8 @@ func (dm *DockerManager) isImageRoot(image string) (bool, error) {
|
||||
return uid == 0, nil
|
||||
}
|
||||
|
||||
// getUidFromUser splits the uid out of an uid:gid string.
|
||||
func getUidFromUser(id string) string {
|
||||
// GetUidFromUser splits the uid out of an uid:gid string.
|
||||
func GetUidFromUser(id string) string {
|
||||
if id == "" {
|
||||
return id
|
||||
}
|
||||
|
@ -1454,7 +1454,7 @@ func TestGetUidFromUser(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for k, v := range tests {
|
||||
actual := getUidFromUser(v.input)
|
||||
actual := GetUidFromUser(v.input)
|
||||
if actual != v.expect {
|
||||
t.Errorf("%s failed. Expected %s but got %s", k, v.expect, actual)
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ go_library(
|
||||
"kuberuntime_sandbox.go",
|
||||
"labels.go",
|
||||
"legacy.go",
|
||||
"security_context.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
@ -47,11 +48,13 @@ go_library(
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//pkg/kubelet/util/cache:go_default_library",
|
||||
"//pkg/kubelet/util/format:go_default_library",
|
||||
"//pkg/securitycontext:go_default_library",
|
||||
"//pkg/types:go_default_library",
|
||||
"//pkg/util/errors:go_default_library",
|
||||
"//pkg/util/flowcontrol:go_default_library",
|
||||
"//pkg/util/parsers:go_default_library",
|
||||
"//pkg/util/runtime:go_default_library",
|
||||
"//pkg/util/selinux:go_default_library",
|
||||
"//pkg/util/sets:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//vendor:github.com/coreos/go-semver/semver",
|
||||
|
@ -146,6 +146,16 @@ func getContainerSpec(pod *api.Pod, containerName string) *api.Container {
|
||||
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.
|
||||
func isContainerFailed(status *kubecontainer.ContainerStatus) bool {
|
||||
if status.State == kubecontainer.ContainerStateExited && status.ExitCode != 0 {
|
||||
|
@ -40,6 +40,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/kubelet/util/format"
|
||||
kubetypes "k8s.io/kubernetes/pkg/types"
|
||||
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/selinux"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
)
|
||||
@ -136,9 +137,17 @@ func (m *kubeGenericRuntimeManager) generateContainerConfig(container *api.Conta
|
||||
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)
|
||||
containerLogsPath := buildContainerLogsPath(container.Name, restartCount)
|
||||
podHasSELinuxLabel := pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SELinuxOptions != nil
|
||||
restartCountUint32 := uint32(restartCount)
|
||||
config := &runtimeApi.ContainerConfig{
|
||||
Metadata: &runtimeApi.ContainerMetadata{
|
||||
@ -151,24 +160,13 @@ func (m *kubeGenericRuntimeManager) generateContainerConfig(container *api.Conta
|
||||
WorkingDir: &container.WorkingDir,
|
||||
Labels: newContainerLabels(container, pod),
|
||||
Annotations: newContainerAnnotations(container, pod, restartCount),
|
||||
Mounts: m.makeMounts(opts, container, podHasSELinuxLabel),
|
||||
Devices: makeDevices(opts),
|
||||
Mounts: m.makeMounts(opts, container),
|
||||
LogPath: &containerLogsPath,
|
||||
Stdin: &container.Stdin,
|
||||
StdinOnce: &container.StdinOnce,
|
||||
Tty: &container.TTY,
|
||||
Linux: m.generateLinuxContainerConfig(container, pod),
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
Linux: m.generateLinuxContainerConfig(container, pod, imageUser),
|
||||
}
|
||||
|
||||
// set environment variables
|
||||
@ -186,9 +184,10 @@ func (m *kubeGenericRuntimeManager) generateContainerConfig(container *api.Conta
|
||||
}
|
||||
|
||||
// generateLinuxContainerConfig generates linux container config for kubelet runtime api.
|
||||
func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *api.Container, pod *api.Pod) *runtimeApi.LinuxContainerConfig {
|
||||
linuxConfig := &runtimeApi.LinuxContainerConfig{
|
||||
Resources: &runtimeApi.LinuxContainerResources{},
|
||||
func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *api.Container, pod *api.Pod, imageUser int64) *runtimeApi.LinuxContainerConfig {
|
||||
lc := &runtimeApi.LinuxContainerConfig{
|
||||
Resources: &runtimeApi.LinuxContainerResources{},
|
||||
SecurityContext: m.determineEffectiveSecurityContext(pod, container, imageUser),
|
||||
}
|
||||
|
||||
// set linux container resources
|
||||
@ -208,49 +207,23 @@ func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *api.
|
||||
// of CPU shares.
|
||||
cpuShares = milliCPUToShares(cpuRequest.MilliValue())
|
||||
}
|
||||
linuxConfig.Resources.CpuShares = &cpuShares
|
||||
lc.Resources.CpuShares = &cpuShares
|
||||
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
|
||||
// be killed first if the system runs out of memory.
|
||||
linuxConfig.Resources.OomScoreAdj = &oomScoreAdj
|
||||
lc.Resources.OomScoreAdj = &oomScoreAdj
|
||||
|
||||
if m.cpuCFSQuota {
|
||||
// if cpuLimit.Amount is nil, then the appropriate default value is returned
|
||||
// to allow full usage of cpu resource.
|
||||
cpuQuota, cpuPeriod := milliCPUToQuota(cpuLimit.MilliValue())
|
||||
linuxConfig.Resources.CpuQuota = &cpuQuota
|
||||
linuxConfig.Resources.CpuPeriod = &cpuPeriod
|
||||
lc.Resources.CpuQuota = &cpuQuota
|
||||
lc.Resources.CpuPeriod = &cpuPeriod
|
||||
}
|
||||
|
||||
// set security context options
|
||||
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
|
||||
return lc
|
||||
}
|
||||
|
||||
// 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.
|
||||
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{}
|
||||
|
||||
for idx := range opts.Mounts {
|
||||
v := opts.Mounts[idx]
|
||||
m := &runtimeApi.Mount{
|
||||
HostPath: &v.HostPath,
|
||||
ContainerPath: &v.ContainerPath,
|
||||
Readonly: &v.ReadOnly,
|
||||
}
|
||||
if podHasSELinuxLabel && v.SELinuxRelabel {
|
||||
m.SelinuxRelabel = &v.SELinuxRelabel
|
||||
selinuxRelabel := v.SELinuxRelabel && selinux.SELinuxEnabled()
|
||||
mount := &runtimeApi.Mount{
|
||||
HostPath: &v.HostPath,
|
||||
ContainerPath: &v.ContainerPath,
|
||||
Readonly: &v.ReadOnly,
|
||||
SelinuxRelabel: &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
|
||||
@ -301,9 +273,11 @@ func (m *kubeGenericRuntimeManager) makeMounts(opts *kubecontainer.RunContainerO
|
||||
glog.Errorf("Error on creating termination-log file %q: %v", containerLogPath, err)
|
||||
} else {
|
||||
fs.Close()
|
||||
selinuxRelabel := selinux.SELinuxEnabled()
|
||||
volumeMounts = append(volumeMounts, &runtimeApi.Mount{
|
||||
HostPath: &containerLogPath,
|
||||
ContainerPath: &container.TerminationMessagePath,
|
||||
HostPath: &containerLogPath,
|
||||
ContainerPath: &container.TerminationMessagePath,
|
||||
SelinuxRelabel: &selinuxRelabel,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxConfig(pod *api.Pod, attem
|
||||
// TODO: refactor kubelet to get cgroup parent for pod instead of containers
|
||||
cgroupParent = opts.CgroupParent
|
||||
}
|
||||
podSandboxConfig.Linux = generatePodSandboxLinuxConfig(pod, cgroupParent)
|
||||
podSandboxConfig.Linux = m.generatePodSandboxLinuxConfig(pod, cgroupParent)
|
||||
if len(portMappings) > 0 {
|
||||
podSandboxConfig.PortMappings = portMappings
|
||||
}
|
||||
@ -129,26 +129,46 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxConfig(pod *api.Pod, attem
|
||||
}
|
||||
|
||||
// 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 == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
linuxPodSandboxConfig := &runtimeApi.LinuxPodSandboxConfig{}
|
||||
lc := &runtimeApi.LinuxPodSandboxConfig{}
|
||||
if cgroupParent != "" {
|
||||
lc.CgroupParent = &cgroupParent
|
||||
}
|
||||
if pod.Spec.SecurityContext != nil {
|
||||
securityContext := pod.Spec.SecurityContext
|
||||
linuxPodSandboxConfig.NamespaceOptions = &runtimeApi.NamespaceOption{
|
||||
HostNetwork: &securityContext.HostNetwork,
|
||||
HostIpc: &securityContext.HostIPC,
|
||||
HostPid: &securityContext.HostPID,
|
||||
sc := pod.Spec.SecurityContext
|
||||
lc.SecurityContext = &runtimeApi.LinuxSandboxSecurityContext{
|
||||
NamespaceOptions: &runtimeApi.NamespaceOption{
|
||||
HostNetwork: &sc.HostNetwork,
|
||||
HostIpc: &sc.HostIPC,
|
||||
HostPid: &sc.HostPID,
|
||||
},
|
||||
RunAsUser: sc.RunAsUser,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cgroupParent != "" {
|
||||
linuxPodSandboxConfig.CgroupParent = &cgroupParent
|
||||
}
|
||||
|
||||
return linuxPodSandboxConfig
|
||||
return lc
|
||||
}
|
||||
|
||||
// getKubeletSandboxes lists all (or just the running) sandboxes managed by kubelet.
|
||||
|
128
pkg/kubelet/kuberuntime/security_context.go
Normal file
128
pkg/kubelet/kuberuntime/security_context.go
Normal 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
|
||||
}
|
@ -91,13 +91,20 @@ func (p SimpleSecurityContextProvider) ModifyHostConfig(pod *api.Pod, container
|
||||
}
|
||||
|
||||
if effectiveSC.SELinuxOptions != nil {
|
||||
hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelUser, effectiveSC.SELinuxOptions.User)
|
||||
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)
|
||||
hostConfig.SecurityOpt = ModifySecurityOptions(hostConfig.SecurityOpt, effectiveSC.SELinuxOptions)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// of name:value
|
||||
func modifySecurityOption(config []string, name, value string) []string {
|
||||
|
@ -104,10 +104,10 @@ func TestModifyHostConfig(t *testing.T) {
|
||||
|
||||
setSELinuxHC := &dockercontainer.HostConfig{}
|
||||
setSELinuxHC.SecurityOpt = []string{
|
||||
fmt.Sprintf("%s:%s", dockerLabelUser, "user"),
|
||||
fmt.Sprintf("%s:%s", dockerLabelRole, "role"),
|
||||
fmt.Sprintf("%s:%s", dockerLabelType, "type"),
|
||||
fmt.Sprintf("%s:%s", dockerLabelLevel, "level"),
|
||||
fmt.Sprintf("%s:%s", DockerLabelUser, "user"),
|
||||
fmt.Sprintf("%s:%s", DockerLabelRole, "role"),
|
||||
fmt.Sprintf("%s:%s", DockerLabelType, "type"),
|
||||
fmt.Sprintf("%s:%s", DockerLabelLevel, "level"),
|
||||
}
|
||||
|
||||
// seLinuxLabelsSC := fullValidSecurityContext()
|
||||
@ -325,10 +325,10 @@ func fullValidHostConfig() *dockercontainer.HostConfig {
|
||||
CapAdd: []string{"addCapA", "addCapB"},
|
||||
CapDrop: []string{"dropCapA", "dropCapB"},
|
||||
SecurityOpt: []string{
|
||||
fmt.Sprintf("%s:%s", dockerLabelUser, "user"),
|
||||
fmt.Sprintf("%s:%s", dockerLabelRole, "role"),
|
||||
fmt.Sprintf("%s:%s", dockerLabelType, "type"),
|
||||
fmt.Sprintf("%s:%s", dockerLabelLevel, "level"),
|
||||
fmt.Sprintf("%s:%s", DockerLabelUser, "user"),
|
||||
fmt.Sprintf("%s:%s", DockerLabelRole, "role"),
|
||||
fmt.Sprintf("%s:%s", DockerLabelType, "type"),
|
||||
fmt.Sprintf("%s:%s", DockerLabelLevel, "level"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -41,9 +41,9 @@ type SecurityContextProvider interface {
|
||||
}
|
||||
|
||||
const (
|
||||
dockerLabelUser string = "label:user"
|
||||
dockerLabelRole string = "label:role"
|
||||
dockerLabelType string = "label:type"
|
||||
dockerLabelLevel string = "label:level"
|
||||
dockerLabelDisable string = "label:disable"
|
||||
DockerLabelUser string = "label:user"
|
||||
DockerLabelRole string = "label:role"
|
||||
DockerLabelType string = "label:type"
|
||||
DockerLabelLevel string = "label:level"
|
||||
DockerLabelDisable string = "label:disable"
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user