mirror of https://github.com/k3s-io/k3s
security context initial implementation - squash
parent
20ea35105d
commit
982bf19c20
|
@ -72,4 +72,4 @@ DNS_DOMAIN="kubernetes.local"
|
|||
DNS_REPLICAS=1
|
||||
|
||||
# Admission Controllers to invoke prior to persisting objects in cluster
|
||||
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,ResourceQuota
|
||||
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,SecurityContextDeny,ResourceQuota
|
||||
|
|
|
@ -49,4 +49,4 @@ ELASTICSEARCH_LOGGING_REPLICAS=1
|
|||
ENABLE_CLUSTER_MONITORING="${KUBE_ENABLE_CLUSTER_MONITORING:-true}"
|
||||
|
||||
# Admission Controllers to invoke prior to persisting objects in cluster
|
||||
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,ResourceQuota
|
||||
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,SecurityContextDeny,ResourceQuota
|
||||
|
|
|
@ -111,4 +111,4 @@ DNS_DOMAIN="kubernetes.local"
|
|||
DNS_REPLICAS=1
|
||||
|
||||
# Admission Controllers to invoke prior to persisting objects in cluster
|
||||
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,ResourceQuota
|
||||
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,SecurityContextDeny,ResourceQuota,
|
||||
|
|
|
@ -70,4 +70,4 @@ DNS_SERVER_IP="10.0.0.10"
|
|||
DNS_DOMAIN="kubernetes.local"
|
||||
DNS_REPLICAS=1
|
||||
|
||||
ADMISSION_CONTROL=NamespaceAutoProvision,LimitRanger,ResourceQuota
|
||||
ADMISSION_CONTROL=NamespaceAutoProvision,LimitRanger,SecurityContextDeny,ResourceQuota
|
||||
|
|
|
@ -50,7 +50,7 @@ MASTER_USER=vagrant
|
|||
MASTER_PASSWD=vagrant
|
||||
|
||||
# Admission Controllers to invoke prior to persisting objects in cluster
|
||||
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,ResourceQuota
|
||||
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,SecurityContextDeny,ResourceQuota
|
||||
|
||||
# Optional: Install node monitoring.
|
||||
ENABLE_NODE_MONITORING=true
|
||||
|
|
|
@ -36,4 +36,5 @@ import (
|
|||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/namespace/exists"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/namespace/lifecycle"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/resourcequota"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/securitycontext/scdeny"
|
||||
)
|
||||
|
|
|
@ -116,7 +116,7 @@ echo "Starting etcd"
|
|||
kube::etcd::start
|
||||
|
||||
# Admission Controllers to invoke prior to persisting objects in cluster
|
||||
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,ResourceQuota
|
||||
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,SecurityContextDeny,ResourceQuota
|
||||
|
||||
APISERVER_LOG=/tmp/kube-apiserver.log
|
||||
sudo -E "${GO_OUT}/kube-apiserver" \
|
||||
|
|
|
@ -204,6 +204,17 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
|
|||
ev.ValueFrom.FieldRef.FieldPath = c.RandString()
|
||||
}
|
||||
},
|
||||
func(sc *api.SecurityContext, c fuzz.Continue) {
|
||||
c.FuzzNoCustom(sc) // fuzz self without calling this function again
|
||||
priv := c.RandBool()
|
||||
sc.Privileged = &priv
|
||||
sc.Capabilities = &api.Capabilities{
|
||||
Add: make([]api.CapabilityType, 0),
|
||||
Drop: make([]api.CapabilityType, 0),
|
||||
}
|
||||
c.Fuzz(&sc.Capabilities.Add)
|
||||
c.Fuzz(&sc.Capabilities.Drop)
|
||||
},
|
||||
func(e *api.Event, c fuzz.Continue) {
|
||||
c.FuzzNoCustom(e) // fuzz self without calling this function again
|
||||
// Fix event count to 1, otherwise, if a v1beta1 or v1beta2 event has a count set arbitrarily, it's count is ignored
|
||||
|
|
|
@ -623,12 +623,10 @@ type Container struct {
|
|||
Lifecycle *Lifecycle `json:"lifecycle,omitempty"`
|
||||
// Required.
|
||||
TerminationMessagePath string `json:"terminationMessagePath,omitempty"`
|
||||
// Optional: Default to false.
|
||||
Privileged bool `json:"privileged,omitempty"`
|
||||
// Required: Policy for pulling images for this container
|
||||
ImagePullPolicy PullPolicy `json:"imagePullPolicy"`
|
||||
// Optional: Capabilities for container.
|
||||
Capabilities Capabilities `json:"capabilities,omitempty"`
|
||||
// Optional: SecurityContext defines the security options the pod should be run with
|
||||
SecurityContext *SecurityContext `json:"securityContext,omitempty" description:"security options the pod should run with"`
|
||||
}
|
||||
|
||||
// Handler defines a specific action that should be taken
|
||||
|
@ -1876,3 +1874,37 @@ type ComponentStatusList struct {
|
|||
|
||||
Items []ComponentStatus `json:"items"`
|
||||
}
|
||||
|
||||
// SecurityContext holds security configuration that will be applied to a container. SecurityContext
|
||||
// contains duplication of some existing fields from the Container resource. These duplicate fields
|
||||
// will be populated based on the Container configuration if they are not set. Defining them on
|
||||
// both the Container AND the SecurityContext will result in an error.
|
||||
type SecurityContext struct {
|
||||
// Capabilities are the capabilities to add/drop when running the container
|
||||
Capabilities *Capabilities `json:"capabilities,omitempty" description:"the linux capabilites that should be added or removed"`
|
||||
|
||||
// Run the container in privileged mode
|
||||
Privileged *bool `json:"privileged,omitempty" description:"run the container in privileged mode"`
|
||||
|
||||
// SELinuxOptions are the labels to be applied to the container
|
||||
// and volumes
|
||||
SELinuxOptions *SELinuxOptions `json:"seLinuxOptions,omitempty" description:"options that control the SELinux labels applied"`
|
||||
|
||||
// RunAsUser is the UID to run the entrypoint of the container process.
|
||||
RunAsUser *int64 `json:"runAsUser,omitempty" description:"the user id that runs the first process in the container"`
|
||||
}
|
||||
|
||||
// SELinuxOptions are the labels to be applied to the container.
|
||||
type SELinuxOptions struct {
|
||||
// SELinux user label
|
||||
User string `json:"user,omitempty" description:"the user label to apply to the container"`
|
||||
|
||||
// SELinux role label
|
||||
Role string `json:"role,omitempty" description:"the role label to apply to the container"`
|
||||
|
||||
// SELinux type label
|
||||
Type string `json:"type,omitempty" description:"the type label to apply to the container"`
|
||||
|
||||
// SELinux level label.
|
||||
Level string `json:"level,omitempty" description:"the level label to apply to the container"`
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package v1
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
|
@ -237,9 +238,22 @@ func init() {
|
|||
return err
|
||||
}
|
||||
out.TerminationMessagePath = in.TerminationMessagePath
|
||||
out.Privileged = in.Privileged
|
||||
out.ImagePullPolicy = newer.PullPolicy(in.ImagePullPolicy)
|
||||
if err := s.Convert(&in.Capabilities, &out.Capabilities, 0); err != nil {
|
||||
|
||||
if in.SecurityContext != nil {
|
||||
if in.SecurityContext.Capabilities != nil {
|
||||
if !reflect.DeepEqual(in.SecurityContext.Capabilities.Add, in.Capabilities.Add) ||
|
||||
!reflect.DeepEqual(in.SecurityContext.Capabilities.Drop, in.Capabilities.Drop) {
|
||||
return fmt.Errorf("container capability settings do not match security context settings, cannot convert")
|
||||
}
|
||||
}
|
||||
if in.SecurityContext.Privileged != nil {
|
||||
if in.Privileged != *in.SecurityContext.Privileged {
|
||||
return fmt.Errorf("container privileged settings do not match security context settings, cannot convert")
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := s.Convert(&in.SecurityContext, &out.SecurityContext, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -297,11 +311,19 @@ func init() {
|
|||
return err
|
||||
}
|
||||
out.TerminationMessagePath = in.TerminationMessagePath
|
||||
out.Privileged = in.Privileged
|
||||
out.ImagePullPolicy = PullPolicy(in.ImagePullPolicy)
|
||||
if err := s.Convert(&in.Capabilities, &out.Capabilities, 0); err != nil {
|
||||
if err := s.Convert(&in.SecurityContext, &out.SecurityContext, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now that we've converted set the container field from security context
|
||||
if out.SecurityContext != nil && out.SecurityContext.Privileged != nil {
|
||||
out.Privileged = *out.SecurityContext.Privileged
|
||||
}
|
||||
// now that we've converted set the container field from security context
|
||||
if out.SecurityContext != nil && out.SecurityContext.Capabilities != nil {
|
||||
out.Capabilities = *out.SecurityContext.Capabilities
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *ContainerPort, out *newer.ContainerPort, s conversion.Scope) error {
|
||||
|
|
|
@ -45,3 +45,62 @@ func TestNodeConversion(t *testing.T) {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadSecurityContextConversion(t *testing.T) {
|
||||
priv := false
|
||||
testCases := map[string]struct {
|
||||
c *current.Container
|
||||
err string
|
||||
}{
|
||||
// this use case must use true for the container and false for the sc. Otherwise the defaulter
|
||||
// will assume privileged was left undefined (since it is the default value) and copy the
|
||||
// sc setting upwards
|
||||
"mismatched privileged": {
|
||||
c: ¤t.Container{
|
||||
Privileged: true,
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &priv,
|
||||
},
|
||||
},
|
||||
err: "container privileged settings do not match security context settings, cannot convert",
|
||||
},
|
||||
"mismatched caps add": {
|
||||
c: ¤t.Container{
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: "container capability settings do not match security context settings, cannot convert",
|
||||
},
|
||||
"mismatched caps drop": {
|
||||
c: ¤t.Container{
|
||||
Capabilities: current.Capabilities{
|
||||
Drop: []current.CapabilityType{"foo"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: "container capability settings do not match security context settings, cannot convert",
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range testCases {
|
||||
got := newer.Container{}
|
||||
err := newer.Scheme.Convert(v.c, &got)
|
||||
if err == nil {
|
||||
t.Errorf("expected error for case %s but got none", k)
|
||||
} else {
|
||||
if err.Error() != v.err {
|
||||
t.Errorf("unexpected error for case %s. Expected: %s but got: %s", k, v.err, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ package v1
|
|||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
)
|
||||
|
@ -66,6 +68,7 @@ func init() {
|
|||
if obj.TerminationMessagePath == "" {
|
||||
obj.TerminationMessagePath = TerminationMessagePathDefault
|
||||
}
|
||||
defaultSecurityContext(obj)
|
||||
},
|
||||
func(obj *ServiceSpec) {
|
||||
if obj.SessionAffinity == "" {
|
||||
|
@ -156,3 +159,44 @@ func defaultHostNetworkPorts(containers *[]Container) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// defaultSecurityContext performs the downward and upward merges of a pod definition
|
||||
func defaultSecurityContext(container *Container) {
|
||||
if container.SecurityContext == nil {
|
||||
glog.V(4).Infof("creating security context for container %s", container.Name)
|
||||
container.SecurityContext = &SecurityContext{}
|
||||
}
|
||||
// if there are no capabilities defined on the SecurityContext then copy the container settings
|
||||
if container.SecurityContext.Capabilities == nil {
|
||||
glog.V(4).Infof("downward merge of container.Capabilities for container %s", container.Name)
|
||||
container.SecurityContext.Capabilities = &container.Capabilities
|
||||
} else {
|
||||
// if there are capabilities defined on the security context and the container setting is
|
||||
// empty then assume that it was left off the pod definition and ensure that the container
|
||||
// settings match the security context settings (checked by the convert functions). If
|
||||
// there are settings in both then don't touch it, the converter will error if they don't
|
||||
// match
|
||||
if len(container.Capabilities.Add) == 0 {
|
||||
glog.V(4).Infof("upward merge of container.Capabilities.Add for container %s", container.Name)
|
||||
container.Capabilities.Add = container.SecurityContext.Capabilities.Add
|
||||
}
|
||||
if len(container.Capabilities.Drop) == 0 {
|
||||
glog.V(4).Infof("upward merge of container.Capabilities.Drop for container %s", container.Name)
|
||||
container.Capabilities.Drop = container.SecurityContext.Capabilities.Drop
|
||||
}
|
||||
}
|
||||
// if there are no privileged settings on the security context then copy the container settings
|
||||
if container.SecurityContext.Privileged == nil {
|
||||
glog.V(4).Infof("downward merge of container.Privileged for container %s", container.Name)
|
||||
container.SecurityContext.Privileged = &container.Privileged
|
||||
} else {
|
||||
// we don't have a good way to know if container.Privileged was set or just defaulted to false
|
||||
// so the best we can do here is check if the securityContext is set to true and the
|
||||
// container is set to false and assume that the Privileged field was left off the container
|
||||
// definition and not an intentional mismatch
|
||||
if *container.SecurityContext.Privileged && !container.Privileged {
|
||||
glog.V(4).Infof("upward merge of container.Privileged for container %s", container.Name)
|
||||
container.Privileged = *container.SecurityContext.Privileged
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -349,3 +349,104 @@ func TestSetDefaultObjectFieldSelectorAPIVersion(t *testing.T) {
|
|||
t.Errorf("Expected default APIVersion v1, got: %v", apiVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefaultSecurityContext(t *testing.T) {
|
||||
priv := false
|
||||
privTrue := true
|
||||
testCases := map[string]struct {
|
||||
c current.Container
|
||||
}{
|
||||
"downward defaulting caps": {
|
||||
c: current.Container{
|
||||
Privileged: false,
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &priv,
|
||||
},
|
||||
},
|
||||
},
|
||||
"downward defaulting priv": {
|
||||
c: current.Container{
|
||||
Privileged: false,
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"upward defaulting caps": {
|
||||
c: current.Container{
|
||||
Privileged: false,
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &priv,
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"biz"},
|
||||
Drop: []current.CapabilityType{"baz"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"upward defaulting priv": {
|
||||
c: current.Container{
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &privTrue,
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pod := ¤t.Pod{
|
||||
Spec: current.PodSpec{},
|
||||
}
|
||||
|
||||
for k, v := range testCases {
|
||||
pod.Spec.Containers = []current.Container{v.c}
|
||||
obj := roundTrip(t, runtime.Object(pod))
|
||||
defaultedPod := obj.(*current.Pod)
|
||||
c := defaultedPod.Spec.Containers[0]
|
||||
if isEqual, issues := areSecurityContextAndContainerEqual(&c); !isEqual {
|
||||
t.Errorf("test case %s expected the security context to have the same values as the container but found %#v", k, issues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func areSecurityContextAndContainerEqual(c *current.Container) (bool, []string) {
|
||||
issues := make([]string, 0)
|
||||
equal := true
|
||||
|
||||
if c.SecurityContext == nil || c.SecurityContext.Privileged == nil || c.SecurityContext.Capabilities == nil {
|
||||
equal = false
|
||||
issues = append(issues, "Expected non nil settings for SecurityContext")
|
||||
return equal, issues
|
||||
}
|
||||
if *c.SecurityContext.Privileged != c.Privileged {
|
||||
equal = false
|
||||
issues = append(issues, "The defaulted SecurityContext.Privileged value did not match the container value")
|
||||
}
|
||||
if !reflect.DeepEqual(c.Capabilities.Add, c.Capabilities.Add) {
|
||||
equal = false
|
||||
issues = append(issues, "The defaulted SecurityContext.Capabilities.Add did not match the container settings")
|
||||
}
|
||||
if !reflect.DeepEqual(c.Capabilities.Drop, c.Capabilities.Drop) {
|
||||
equal = false
|
||||
issues = append(issues, "The defaulted SecurityContext.Capabilities.Drop did not match the container settings")
|
||||
}
|
||||
return equal, issues
|
||||
}
|
||||
|
|
|
@ -636,12 +636,14 @@ type Container struct {
|
|||
Lifecycle *Lifecycle `json:"lifecycle,omitempty" description:"actions that the management system should take in response to container lifecycle events; cannot be updated"`
|
||||
// Optional: Defaults to /dev/termination-log
|
||||
TerminationMessagePath string `json:"terminationMessagePath,omitempty" description:"path at which the file to which the container's termination message will be written is mounted into the container's filesystem; message written is intended to be brief final status, such as an assertion failure message; defaults to /dev/termination-log; cannot be updated"`
|
||||
// Optional: Default to false.
|
||||
Privileged bool `json:"privileged,omitempty" description:"whether or not the container is granted privileged status; defaults to false; cannot be updated"`
|
||||
// Deprecated - see SecurityContext. Optional: Default to false.
|
||||
Privileged bool `json:"privileged,omitempty" description:"hether or not the container is granted privileged status; defaults to false; cannot be updated; deprecated; See SecurityContext"`
|
||||
// Optional: Policy for pulling images for this container
|
||||
ImagePullPolicy PullPolicy `json:"imagePullPolicy" description:"image pull policy; one of PullAlways, PullNever, PullIfNotPresent; defaults to PullAlways if :latest tag is specified, or PullIfNotPresent otherwise; cannot be updated"`
|
||||
// Optional: Capabilities for container.
|
||||
Capabilities Capabilities `json:"capabilities,omitempty" description:"capabilities for container; cannot be updated"`
|
||||
// Deprecated - see SecurityContext. Optional: Capabilities for container.
|
||||
Capabilities Capabilities `json:"capabilities,omitempty" description:"capabilities for container; cannot be updated; deprecated; See SecurityContext"`
|
||||
// Optional: SecurityContext defines the security options the pod should be run with
|
||||
SecurityContext *SecurityContext `json:"securityContext,omitempty" description:"security options the pod should run with"`
|
||||
}
|
||||
|
||||
// Handler defines a specific action that should be taken
|
||||
|
@ -1735,3 +1737,39 @@ type ComponentStatusList struct {
|
|||
|
||||
Items []ComponentStatus `json:"items" description:"list of component status objects"`
|
||||
}
|
||||
|
||||
// SecurityContext holds security configuration that will be applied to a container. SecurityContext
|
||||
// contains duplication of some existing fields from the Container resource. These duplicate fields
|
||||
// will be populated based on the Container configuration if they are not set. Defining them on
|
||||
// both the Container AND the SecurityContext will result in an error.
|
||||
type SecurityContext struct {
|
||||
// Capabilities are the capabilities to add/drop when running the container
|
||||
// Must match Container.Capabilities or be unset. Will be defaulted to Container.Capabilities if left unset
|
||||
Capabilities *Capabilities `json:"capabilities,omitempty" description:"the linux capabilites that should be added or removed"`
|
||||
|
||||
// Run the container in privileged mode
|
||||
// Must match Container.Privileged or be unset. Will be defaulted to Container.Privileged if left unset
|
||||
Privileged *bool `json:"privileged,omitempty" description:"run the container in privileged mode"`
|
||||
|
||||
// SELinuxOptions are the labels to be applied to the container
|
||||
// and volumes
|
||||
SELinuxOptions *SELinuxOptions `json:"seLinuxOptions,omitempty" description:"options that control the SELinux labels applied"`
|
||||
|
||||
// RunAsUser is the UID to run the entrypoint of the container process.
|
||||
RunAsUser *int64 `json:"runAsUser,omitempty" description:"the user id that runs the first process in the container"`
|
||||
}
|
||||
|
||||
// SELinuxOptions are the labels to be applied to the container
|
||||
type SELinuxOptions struct {
|
||||
// SELinux user label
|
||||
User string `json:"user,omitempty" description:"the user label to apply to the container"`
|
||||
|
||||
// SELinux role label
|
||||
Role string `json:"role,omitempty" description:"the role label to apply to the container"`
|
||||
|
||||
// SELinux type label
|
||||
Type string `json:"type,omitempty" description:"the type label to apply to the container"`
|
||||
|
||||
// SELinux level label.
|
||||
Level string `json:"level,omitempty" description:"the level label to apply to the container"`
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package v1beta1
|
|||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
|
@ -579,15 +580,20 @@ func init() {
|
|||
if err := s.Convert(&in.TerminationMessagePath, &out.TerminationMessagePath, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Privileged, &out.Privileged, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.ImagePullPolicy, &out.ImagePullPolicy, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Capabilities, &out.Capabilities, 0); err != nil {
|
||||
if err := s.Convert(&in.SecurityContext, &out.SecurityContext, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
// now that we've converted set the container field from security context
|
||||
if out.SecurityContext != nil && out.SecurityContext.Privileged != nil {
|
||||
out.Privileged = *out.SecurityContext.Privileged
|
||||
}
|
||||
// now that we've converted set the container field from security context
|
||||
if out.SecurityContext != nil && out.SecurityContext.Capabilities != nil {
|
||||
out.Capabilities = *out.SecurityContext.Capabilities
|
||||
}
|
||||
return nil
|
||||
},
|
||||
// Internal API does not support CPU to be specified via an explicit field.
|
||||
|
@ -665,13 +671,23 @@ func init() {
|
|||
if err := s.Convert(&in.TerminationMessagePath, &out.TerminationMessagePath, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Privileged, &out.Privileged, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.ImagePullPolicy, &out.ImagePullPolicy, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Capabilities, &out.Capabilities, 0); err != nil {
|
||||
if in.SecurityContext != nil {
|
||||
if in.SecurityContext.Capabilities != nil {
|
||||
if !reflect.DeepEqual(in.SecurityContext.Capabilities.Add, in.Capabilities.Add) ||
|
||||
!reflect.DeepEqual(in.SecurityContext.Capabilities.Drop, in.Capabilities.Drop) {
|
||||
return fmt.Errorf("container capability settings do not match security context settings, cannot convert")
|
||||
}
|
||||
}
|
||||
if in.SecurityContext.Privileged != nil {
|
||||
if in.Privileged != *in.SecurityContext.Privileged {
|
||||
return fmt.Errorf("container privileged settings do not match security context settings, cannot convert")
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := s.Convert(&in.SecurityContext, &out.SecurityContext, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -749,3 +749,63 @@ func TestSecretVolumeSourceConversion(t *testing.T) {
|
|||
t.Errorf("Expected %v; got %v", given, got2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadSecurityContextConversion(t *testing.T) {
|
||||
priv := false
|
||||
testCases := map[string]struct {
|
||||
c *current.Container
|
||||
err string
|
||||
}{
|
||||
// this use case must use true for the container and false for the sc. Otherwise the defaulter
|
||||
// will assume privileged was left undefined (since it is the default value) and copy the
|
||||
// sc setting upwards
|
||||
"mismatched privileged": {
|
||||
c: ¤t.Container{
|
||||
Privileged: true,
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &priv,
|
||||
},
|
||||
},
|
||||
err: "container privileged settings do not match security context settings, cannot convert",
|
||||
},
|
||||
"mismatched caps add": {
|
||||
c: ¤t.Container{
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: "container capability settings do not match security context settings, cannot convert",
|
||||
},
|
||||
"mismatched caps drop": {
|
||||
c: ¤t.Container{
|
||||
Capabilities: current.Capabilities{
|
||||
Drop: []current.CapabilityType{"foo"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: "container capability settings do not match security context settings, cannot convert",
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range testCases {
|
||||
got := newer.Container{}
|
||||
err := Convert(v.c, &got)
|
||||
if err == nil {
|
||||
t.Errorf("expected error for case %s but got none", k)
|
||||
} else {
|
||||
if err.Error() != v.err {
|
||||
t.Errorf("unexpected error for case %s. Expected: %s but got: %s", k, v.err, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ func init() {
|
|||
if obj.TerminationMessagePath == "" {
|
||||
obj.TerminationMessagePath = TerminationMessagePathDefault
|
||||
}
|
||||
defaultSecurityContext(obj)
|
||||
},
|
||||
func(obj *RestartPolicy) {
|
||||
if util.AllPtrFieldsNil(obj) {
|
||||
|
@ -194,3 +195,44 @@ func defaultHostNetworkPorts(containers *[]Container) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// defaultSecurityContext performs the downward and upward merges of a pod definition
|
||||
func defaultSecurityContext(container *Container) {
|
||||
if container.SecurityContext == nil {
|
||||
glog.V(4).Infof("creating security context for container %s", container.Name)
|
||||
container.SecurityContext = &SecurityContext{}
|
||||
}
|
||||
// if there are no capabilities defined on the SecurityContext then copy the container settings
|
||||
if container.SecurityContext.Capabilities == nil {
|
||||
glog.V(4).Infof("downward merge of container.Capabilities for container %s", container.Name)
|
||||
container.SecurityContext.Capabilities = &container.Capabilities
|
||||
} else {
|
||||
// if there are capabilities defined on the security context and the container setting is
|
||||
// empty then assume that it was left off the pod definition and ensure that the container
|
||||
// settings match the security context settings (checked by the convert functions). If
|
||||
// there are settings in both then don't touch it, the converter will error if they don't
|
||||
// match
|
||||
if len(container.Capabilities.Add) == 0 {
|
||||
glog.V(4).Infof("upward merge of container.Capabilities.Add for container %s", container.Name)
|
||||
container.Capabilities.Add = container.SecurityContext.Capabilities.Add
|
||||
}
|
||||
if len(container.Capabilities.Drop) == 0 {
|
||||
glog.V(4).Infof("upward merge of container.Capabilities.Drop for container %s", container.Name)
|
||||
container.Capabilities.Drop = container.SecurityContext.Capabilities.Drop
|
||||
}
|
||||
}
|
||||
// if there are no privileged settings on the security context then copy the container settings
|
||||
if container.SecurityContext.Privileged == nil {
|
||||
glog.V(4).Infof("downward merge of container.Privileged for container %s", container.Name)
|
||||
container.SecurityContext.Privileged = &container.Privileged
|
||||
} else {
|
||||
// we don't have a good way to know if container.Privileged was set or just defaulted to false
|
||||
// so the best we can do here is check if the securityContext is set to true and the
|
||||
// container is set to false and assume that the Privileged field was left off the container
|
||||
// definition and not an intentional mismatch
|
||||
if *container.SecurityContext.Privileged && !container.Privileged {
|
||||
glog.V(4).Infof("upward merge of container.Privileged for container %s", container.Name)
|
||||
container.Privileged = *container.SecurityContext.Privileged
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -340,3 +340,106 @@ func TestSetDefaultObjectFieldSelectorAPIVersion(t *testing.T) {
|
|||
t.Errorf("Expected default APIVersion v1beta1, got: %v", apiVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefaultSecurityContext(t *testing.T) {
|
||||
priv := false
|
||||
privTrue := true
|
||||
testCases := map[string]struct {
|
||||
c current.Container
|
||||
}{
|
||||
"downward defaulting caps": {
|
||||
c: current.Container{
|
||||
Privileged: false,
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &priv,
|
||||
},
|
||||
},
|
||||
},
|
||||
"downward defaulting priv": {
|
||||
c: current.Container{
|
||||
Privileged: false,
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"upward defaulting caps": {
|
||||
c: current.Container{
|
||||
Privileged: false,
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &priv,
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"biz"},
|
||||
Drop: []current.CapabilityType{"baz"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"upward defaulting priv": {
|
||||
c: current.Container{
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &privTrue,
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pod := ¤t.Pod{
|
||||
DesiredState: current.PodState{
|
||||
Manifest: current.ContainerManifest{},
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range testCases {
|
||||
pod.DesiredState.Manifest.Containers = []current.Container{v.c}
|
||||
obj := roundTrip(t, runtime.Object(pod))
|
||||
defaultedPod := obj.(*current.Pod)
|
||||
c := defaultedPod.DesiredState.Manifest.Containers[0]
|
||||
if isEqual, issues := areSecurityContextAndContainerEqual(&c); !isEqual {
|
||||
t.Errorf("test case %s expected the security context to have the same values as the container but found %#v", k, issues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func areSecurityContextAndContainerEqual(c *current.Container) (bool, []string) {
|
||||
issues := make([]string, 0)
|
||||
equal := true
|
||||
|
||||
if c.SecurityContext == nil || c.SecurityContext.Privileged == nil || c.SecurityContext.Capabilities == nil {
|
||||
equal = false
|
||||
issues = append(issues, "Expected non nil settings for SecurityContext")
|
||||
return equal, issues
|
||||
}
|
||||
if *c.SecurityContext.Privileged != c.Privileged {
|
||||
equal = false
|
||||
issues = append(issues, "The defaulted SecurityContext.Privileged value did not match the container value")
|
||||
}
|
||||
if !reflect.DeepEqual(c.Capabilities.Add, c.Capabilities.Add) {
|
||||
equal = false
|
||||
issues = append(issues, "The defaulted SecurityContext.Capabilities.Add did not match the container settings")
|
||||
}
|
||||
if !reflect.DeepEqual(c.Capabilities.Drop, c.Capabilities.Drop) {
|
||||
equal = false
|
||||
issues = append(issues, "The defaulted SecurityContext.Capabilities.Drop did not match the container settings")
|
||||
}
|
||||
return equal, issues
|
||||
}
|
||||
|
|
|
@ -525,12 +525,14 @@ type Container struct {
|
|||
Lifecycle *Lifecycle `json:"lifecycle,omitempty" description:"actions that the management system should take in response to container lifecycle events; cannot be updated"`
|
||||
// Optional: Defaults to /dev/termination-log
|
||||
TerminationMessagePath string `json:"terminationMessagePath,omitempty" description:"path at which the file to which the container's termination message will be written is mounted into the container's filesystem; message written is intended to be brief final status, such as an assertion failure message; defaults to /dev/termination-log; cannot be updated"`
|
||||
// Optional: Default to false.
|
||||
Privileged bool `json:"privileged,omitempty" description:"whether or not the container is granted privileged status; defaults to false; cannot be updated"`
|
||||
// Deprecated - see SecurityContext. Optional: Default to false.
|
||||
Privileged bool `json:"privileged,omitempty" description:"whether or not the container is granted privileged status; defaults to false; cannot be updated; deprecated; See SecurityContext"`
|
||||
// Optional: Policy for pulling images for this container
|
||||
ImagePullPolicy PullPolicy `json:"imagePullPolicy" description:"image pull policy; one of PullAlways, PullNever, PullIfNotPresent; defaults to PullAlways if :latest tag is specified, or PullIfNotPresent otherwise; cannot be updated"`
|
||||
// Optional: Capabilities for container.
|
||||
Capabilities Capabilities `json:"capabilities,omitempty" description:"capabilities for container; cannot be updated"`
|
||||
// Deprecated - see SecurityContext. Optional: Capabilities for container.
|
||||
Capabilities Capabilities `json:"capabilities,omitempty" description:"capabilities for container; cannot be updated; deprecated; See SecurityContext"`
|
||||
// Optional: SecurityContext defines the security options the pod should be run with
|
||||
SecurityContext *SecurityContext `json:"securityContext,omitempty" description:"security options the pod should run with"`
|
||||
}
|
||||
|
||||
// Handler defines a specific action that should be taken
|
||||
|
@ -1655,3 +1657,39 @@ type ComponentStatusList struct {
|
|||
|
||||
Items []ComponentStatus `json:"items" description:"list of component status objects"`
|
||||
}
|
||||
|
||||
// SecurityContext holds security configuration that will be applied to a container. SecurityContext
|
||||
// contains duplication of some existing fields from the Container resource. These duplicate fields
|
||||
// will be populated based on the Container configuration if they are not set. Defining them on
|
||||
// both the Container AND the SecurityContext will result in an error.
|
||||
type SecurityContext struct {
|
||||
// Capabilities are the capabilities to add/drop when running the container
|
||||
// Must match Container.Capabilities or be unset. Will be defaulted to Container.Capabilities if left unset
|
||||
Capabilities *Capabilities `json:"capabilities,omitempty" description:"the linux capabilites that should be added or removed"`
|
||||
|
||||
// Run the container in privileged mode
|
||||
// Must match Container.Privileged or be unset. Will be defaulted to Container.Privileged if left unset
|
||||
Privileged *bool `json:"privileged,omitempty" description:"run the container in privileged mode"`
|
||||
|
||||
// SELinuxOptions are the labels to be applied to the container
|
||||
// and volumes
|
||||
SELinuxOptions *SELinuxOptions `json:"seLinuxOptions,omitempty" description:"options that control the SELinux labels applied"`
|
||||
|
||||
// RunAsUser is the UID to run the entrypoint of the container process.
|
||||
RunAsUser *int64 `json:"runAsUser,omitempty" description:"the user id that runs the first process in the container"`
|
||||
}
|
||||
|
||||
// SELinuxOptions are the labels to be applied to the container.
|
||||
type SELinuxOptions struct {
|
||||
// SELinux user label
|
||||
User string `json:"user,omitempty" description:"the user label to apply to the container"`
|
||||
|
||||
// SELinux role label
|
||||
Role string `json:"role,omitempty" description:"the role label to apply to the container"`
|
||||
|
||||
// SELinux type label
|
||||
Type string `json:"type,omitempty" description:"the type label to apply to the container"`
|
||||
|
||||
// SELinux level label.
|
||||
Level string `json:"level,omitempty" description:"the level label to apply to the container"`
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package v1beta2
|
|||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
|
@ -357,15 +358,20 @@ func init() {
|
|||
if err := s.Convert(&in.TerminationMessagePath, &out.TerminationMessagePath, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Privileged, &out.Privileged, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.ImagePullPolicy, &out.ImagePullPolicy, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Capabilities, &out.Capabilities, 0); err != nil {
|
||||
if err := s.Convert(&in.SecurityContext, &out.SecurityContext, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
// now that we've converted set the container field from security context
|
||||
if out.SecurityContext != nil && out.SecurityContext.Privileged != nil {
|
||||
out.Privileged = *out.SecurityContext.Privileged
|
||||
}
|
||||
// now that we've converted set the container field from security context
|
||||
if out.SecurityContext != nil && out.SecurityContext.Capabilities != nil {
|
||||
out.Capabilities = *out.SecurityContext.Capabilities
|
||||
}
|
||||
return nil
|
||||
},
|
||||
// Internal API does not support CPU to be specified via an explicit field.
|
||||
|
@ -445,13 +451,23 @@ func init() {
|
|||
if err := s.Convert(&in.TerminationMessagePath, &out.TerminationMessagePath, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Privileged, &out.Privileged, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.ImagePullPolicy, &out.ImagePullPolicy, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Capabilities, &out.Capabilities, 0); err != nil {
|
||||
if in.SecurityContext != nil {
|
||||
if in.SecurityContext.Capabilities != nil {
|
||||
if !reflect.DeepEqual(in.SecurityContext.Capabilities.Add, in.Capabilities.Add) ||
|
||||
!reflect.DeepEqual(in.SecurityContext.Capabilities.Drop, in.Capabilities.Drop) {
|
||||
return fmt.Errorf("container capability settings do not match security context settings, cannot convert")
|
||||
}
|
||||
}
|
||||
if in.SecurityContext.Privileged != nil {
|
||||
if in.Privileged != *in.SecurityContext.Privileged {
|
||||
return fmt.Errorf("container privileged settings do not match security context settings, cannot convert")
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := s.Convert(&in.SecurityContext, &out.SecurityContext, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -564,3 +564,63 @@ func TestSecretVolumeSourceConversion(t *testing.T) {
|
|||
t.Errorf("Expected %v; got %v", given, got2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadSecurityContextConversion(t *testing.T) {
|
||||
priv := false
|
||||
testCases := map[string]struct {
|
||||
c *current.Container
|
||||
err string
|
||||
}{
|
||||
// this use case must use true for the container and false for the sc. Otherwise the defaulter
|
||||
// will assume privileged was left undefined (since it is the default value) and copy the
|
||||
// sc setting upwards
|
||||
"mismatched privileged": {
|
||||
c: ¤t.Container{
|
||||
Privileged: true,
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &priv,
|
||||
},
|
||||
},
|
||||
err: "container privileged settings do not match security context settings, cannot convert",
|
||||
},
|
||||
"mismatched caps add": {
|
||||
c: ¤t.Container{
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: "container capability settings do not match security context settings, cannot convert",
|
||||
},
|
||||
"mismatched caps drop": {
|
||||
c: ¤t.Container{
|
||||
Capabilities: current.Capabilities{
|
||||
Drop: []current.CapabilityType{"foo"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: "container capability settings do not match security context settings, cannot convert",
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range testCases {
|
||||
got := newer.Container{}
|
||||
err := newer.Scheme.Convert(v.c, &got)
|
||||
if err == nil {
|
||||
t.Errorf("expected error for case %s but got none", k)
|
||||
} else {
|
||||
if err.Error() != v.err {
|
||||
t.Errorf("unexpected error for case %s. Expected: %s but got: %s", k, v.err, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ func init() {
|
|||
if obj.TerminationMessagePath == "" {
|
||||
obj.TerminationMessagePath = TerminationMessagePathDefault
|
||||
}
|
||||
defaultSecurityContext(obj)
|
||||
},
|
||||
func(obj *RestartPolicy) {
|
||||
if util.AllPtrFieldsNil(obj) {
|
||||
|
@ -195,3 +196,44 @@ func defaultHostNetworkPorts(containers *[]Container) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// defaultSecurityContext performs the downward and upward merges of a pod definition
|
||||
func defaultSecurityContext(container *Container) {
|
||||
if container.SecurityContext == nil {
|
||||
glog.V(4).Infof("creating security context for container %s", container.Name)
|
||||
container.SecurityContext = &SecurityContext{}
|
||||
}
|
||||
// if there are no capabilities defined on the SecurityContext then copy the container settings
|
||||
if container.SecurityContext.Capabilities == nil {
|
||||
glog.V(4).Infof("downward merge of container.Capabilities for container %s", container.Name)
|
||||
container.SecurityContext.Capabilities = &container.Capabilities
|
||||
} else {
|
||||
// if there are capabilities defined on the security context and the container setting is
|
||||
// empty then assume that it was left off the pod definition and ensure that the container
|
||||
// settings match the security context settings (checked by the convert functions). If
|
||||
// there are settings in both then don't touch it, the converter will error if they don't
|
||||
// match
|
||||
if len(container.Capabilities.Add) == 0 {
|
||||
glog.V(4).Infof("upward merge of container.Capabilities.Add for container %s", container.Name)
|
||||
container.Capabilities.Add = container.SecurityContext.Capabilities.Add
|
||||
}
|
||||
if len(container.Capabilities.Drop) == 0 {
|
||||
glog.V(4).Infof("upward merge of container.Capabilities.Drop for container %s", container.Name)
|
||||
container.Capabilities.Drop = container.SecurityContext.Capabilities.Drop
|
||||
}
|
||||
}
|
||||
// if there are no privileged settings on the security context then copy the container settings
|
||||
if container.SecurityContext.Privileged == nil {
|
||||
glog.V(4).Infof("downward merge of container.Privileged for container %s", container.Name)
|
||||
container.SecurityContext.Privileged = &container.Privileged
|
||||
} else {
|
||||
// we don't have a good way to know if container.Privileged was set or just defaulted to false
|
||||
// so the best we can do here is check if the securityContext is set to true and the
|
||||
// container is set to false and assume that the Privileged field was left off the container
|
||||
// definition and not an intentional mismatch
|
||||
if *container.SecurityContext.Privileged && !container.Privileged {
|
||||
glog.V(4).Infof("upward merge of container.Privileged for container %s", container.Name)
|
||||
container.Privileged = *container.SecurityContext.Privileged
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -339,3 +339,106 @@ func TestSetDefaultObjectFieldSelectorAPIVersion(t *testing.T) {
|
|||
t.Errorf("Expected default APIVersion v1beta2, got: %v", apiVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefaultSecurityContext(t *testing.T) {
|
||||
priv := false
|
||||
privTrue := true
|
||||
testCases := map[string]struct {
|
||||
c current.Container
|
||||
}{
|
||||
"downward defaulting caps": {
|
||||
c: current.Container{
|
||||
Privileged: false,
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &priv,
|
||||
},
|
||||
},
|
||||
},
|
||||
"downward defaulting priv": {
|
||||
c: current.Container{
|
||||
Privileged: false,
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"upward defaulting caps": {
|
||||
c: current.Container{
|
||||
Privileged: false,
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &priv,
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"biz"},
|
||||
Drop: []current.CapabilityType{"baz"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"upward defaulting priv": {
|
||||
c: current.Container{
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &privTrue,
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pod := ¤t.Pod{
|
||||
DesiredState: current.PodState{
|
||||
Manifest: current.ContainerManifest{},
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range testCases {
|
||||
pod.DesiredState.Manifest.Containers = []current.Container{v.c}
|
||||
obj := roundTrip(t, runtime.Object(pod))
|
||||
defaultedPod := obj.(*current.Pod)
|
||||
c := defaultedPod.DesiredState.Manifest.Containers[0]
|
||||
if isEqual, issues := areSecurityContextAndContainerEqual(&c); !isEqual {
|
||||
t.Errorf("test case %s expected the security context to have the same values as the container but found %#v", k, issues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func areSecurityContextAndContainerEqual(c *current.Container) (bool, []string) {
|
||||
issues := make([]string, 0)
|
||||
equal := true
|
||||
|
||||
if c.SecurityContext == nil || c.SecurityContext.Privileged == nil || c.SecurityContext.Capabilities == nil {
|
||||
equal = false
|
||||
issues = append(issues, "Expected non nil settings for SecurityContext")
|
||||
return equal, issues
|
||||
}
|
||||
if *c.SecurityContext.Privileged != c.Privileged {
|
||||
equal = false
|
||||
issues = append(issues, "The defaulted SecurityContext.Privileged value did not match the container value")
|
||||
}
|
||||
if !reflect.DeepEqual(c.Capabilities.Add, c.Capabilities.Add) {
|
||||
equal = false
|
||||
issues = append(issues, "The defaulted SecurityContext.Capabilities.Add did not match the container settings")
|
||||
}
|
||||
if !reflect.DeepEqual(c.Capabilities.Drop, c.Capabilities.Drop) {
|
||||
equal = false
|
||||
issues = append(issues, "The defaulted SecurityContext.Capabilities.Drop did not match the container settings")
|
||||
}
|
||||
return equal, issues
|
||||
}
|
||||
|
|
|
@ -513,12 +513,14 @@ type Container struct {
|
|||
Lifecycle *Lifecycle `json:"lifecycle,omitempty" description:"actions that the management system should take in response to container lifecycle events; cannot be updated"`
|
||||
// Optional: Defaults to /dev/termination-log
|
||||
TerminationMessagePath string `json:"terminationMessagePath,omitempty" description:"path at which the file to which the container's termination message will be written is mounted into the container's filesystem; message written is intended to be brief final status, such as an assertion failure message; defaults to /dev/termination-log; cannot be updated"`
|
||||
// Optional: Default to false.
|
||||
Privileged bool `json:"privileged,omitempty" description:"whether or not the container is granted privileged status; defaults to false; cannot be updated"`
|
||||
// Deprecated - see SecurityContext. Optional: Default to false.
|
||||
Privileged bool `json:"privileged,omitempty" description:"whether or not the container is granted privileged status; defaults to false; cannot be updated; deprecated; See SecurityContext"`
|
||||
// Optional: Policy for pulling images for this container
|
||||
ImagePullPolicy PullPolicy `json:"imagePullPolicy" description:"image pull policy; one of PullAlways, PullNever, PullIfNotPresent; defaults to PullAlways if :latest tag is specified, or PullIfNotPresent otherwise; cannot be updated"`
|
||||
// Optional: Capabilities for container.
|
||||
Capabilities Capabilities `json:"capabilities,omitempty" description:"capabilities for container; cannot be updated"`
|
||||
// Deprecated - see SecurityContext. Optional: Capabilities for container.
|
||||
Capabilities Capabilities `json:"capabilities,omitempty" description:"capabilities for container; cannot be updated; deprecated; See SecurityContext"`
|
||||
// Optional: SecurityContext defines the security options the pod should be run with
|
||||
SecurityContext *SecurityContext `json:"securityContext,omitempty" description:"security options the pod should run with"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -1717,3 +1719,39 @@ type ComponentStatusList struct {
|
|||
|
||||
Items []ComponentStatus `json:"items" description:"list of component status objects"`
|
||||
}
|
||||
|
||||
// SecurityContext holds security configuration that will be applied to a container. SecurityContext
|
||||
// contains duplication of some existing fields from the Container resource. These duplicate fields
|
||||
// will be populated based on the Container configuration if they are not set. Defining them on
|
||||
// both the Container AND the SecurityContext will result in an error.
|
||||
type SecurityContext struct {
|
||||
// Capabilities are the capabilities to add/drop when running the container
|
||||
// Must match Container.Capabilities or be unset. Will be defaulted to Container.Capabilities if left unset
|
||||
Capabilities *Capabilities `json:"capabilities,omitempty" description:"the linux capabilites that should be added or removed"`
|
||||
|
||||
// Run the container in privileged mode
|
||||
// Must match Container.Privileged or be unset. Will be defaulted to Container.Privileged if left unset
|
||||
Privileged *bool `json:"privileged,omitempty" description:"run the container in privileged mode"`
|
||||
|
||||
// SELinuxOptions are the labels to be applied to the container
|
||||
// and volumes
|
||||
SELinuxOptions *SELinuxOptions `json:"seLinuxOptions,omitempty" description:"options that control the SELinux labels applied"`
|
||||
|
||||
// RunAsUser is the UID to run the entrypoint of the container process.
|
||||
RunAsUser *int64 `json:"runAsUser,omitempty" description:"the user id that runs the first process in the container"`
|
||||
}
|
||||
|
||||
// SELinuxOptions are the labels to be applied to the container.
|
||||
type SELinuxOptions struct {
|
||||
// SELinux user label
|
||||
User string `json:"user,omitempty" description:"the user label to apply to the container"`
|
||||
|
||||
// SELinux role label
|
||||
Role string `json:"role,omitempty" description:"the role label to apply to the container"`
|
||||
|
||||
// SELinux type label
|
||||
Type string `json:"type,omitempty" description:"the type label to apply to the container"`
|
||||
|
||||
// SELinux level label.
|
||||
Level string `json:"level,omitempty" description:"the level label to apply to the container"`
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -45,3 +45,63 @@ func TestNodeConversion(t *testing.T) {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadSecurityContextConversion(t *testing.T) {
|
||||
priv := false
|
||||
testCases := map[string]struct {
|
||||
c *current.Container
|
||||
err string
|
||||
}{
|
||||
// this use case must use true for the container and false for the sc. Otherwise the defaulter
|
||||
// will assume privileged was left undefined (since it is the default value) and copy the
|
||||
// sc setting upwards
|
||||
"mismatched privileged": {
|
||||
c: ¤t.Container{
|
||||
Privileged: true,
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &priv,
|
||||
},
|
||||
},
|
||||
err: "container privileged settings do not match security context settings, cannot convert",
|
||||
},
|
||||
"mismatched caps add": {
|
||||
c: ¤t.Container{
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: "container capability settings do not match security context settings, cannot convert",
|
||||
},
|
||||
"mismatched caps drop": {
|
||||
c: ¤t.Container{
|
||||
Capabilities: current.Capabilities{
|
||||
Drop: []current.CapabilityType{"foo"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: "container capability settings do not match security context settings, cannot convert",
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range testCases {
|
||||
got := newer.Container{}
|
||||
err := newer.Scheme.Convert(v.c, &got)
|
||||
if err == nil {
|
||||
t.Errorf("expected error for case %s but got none", k)
|
||||
} else {
|
||||
if err.Error() != v.err {
|
||||
t.Errorf("unexpected error for case %s. Expected: %s but got: %s", k, v.err, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -66,6 +67,7 @@ func init() {
|
|||
if obj.TerminationMessagePath == "" {
|
||||
obj.TerminationMessagePath = TerminationMessagePathDefault
|
||||
}
|
||||
defaultSecurityContext(obj)
|
||||
},
|
||||
func(obj *ServiceSpec) {
|
||||
if obj.SessionAffinity == "" {
|
||||
|
@ -156,3 +158,44 @@ func defaultHostNetworkPorts(containers *[]Container) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// defaultSecurityContext performs the downward and upward merges of a pod definition
|
||||
func defaultSecurityContext(container *Container) {
|
||||
if container.SecurityContext == nil {
|
||||
glog.V(4).Infof("creating security context for container %s", container.Name)
|
||||
container.SecurityContext = &SecurityContext{}
|
||||
}
|
||||
// if there are no capabilities defined on the SecurityContext then copy the container settings
|
||||
if container.SecurityContext.Capabilities == nil {
|
||||
glog.V(4).Infof("downward merge of container.Capabilities for container %s", container.Name)
|
||||
container.SecurityContext.Capabilities = &container.Capabilities
|
||||
} else {
|
||||
// if there are capabilities defined on the security context and the container setting is
|
||||
// empty then assume that it was left off the pod definition and ensure that the container
|
||||
// settings match the security context settings (checked by the convert functions). If
|
||||
// there are settings in both then don't touch it, the converter will error if they don't
|
||||
// match
|
||||
if len(container.Capabilities.Add) == 0 {
|
||||
glog.V(4).Infof("upward merge of container.Capabilities.Add for container %s", container.Name)
|
||||
container.Capabilities.Add = container.SecurityContext.Capabilities.Add
|
||||
}
|
||||
if len(container.Capabilities.Drop) == 0 {
|
||||
glog.V(4).Infof("upward merge of container.Capabilities.Drop for container %s", container.Name)
|
||||
container.Capabilities.Drop = container.SecurityContext.Capabilities.Drop
|
||||
}
|
||||
}
|
||||
// if there are no privileged settings on the security context then copy the container settings
|
||||
if container.SecurityContext.Privileged == nil {
|
||||
glog.V(4).Infof("downward merge of container.Privileged for container %s", container.Name)
|
||||
container.SecurityContext.Privileged = &container.Privileged
|
||||
} else {
|
||||
// we don't have a good way to know if container.Privileged was set or just defaulted to false
|
||||
// so the best we can do here is check if the securityContext is set to true and the
|
||||
// container is set to false and assume that the Privileged field was left off the container
|
||||
// definition and not an intentional mismatch
|
||||
if *container.SecurityContext.Privileged && !container.Privileged {
|
||||
glog.V(4).Infof("upward merge of container.Privileged for container %s", container.Name)
|
||||
container.Privileged = *container.SecurityContext.Privileged
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -349,3 +349,104 @@ func TestSetDefaultObjectFieldSelectorAPIVersion(t *testing.T) {
|
|||
t.Errorf("Expected default APIVersion v1beta3, got: %v", apiVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefaultSecurityContext(t *testing.T) {
|
||||
priv := false
|
||||
privTrue := true
|
||||
testCases := map[string]struct {
|
||||
c current.Container
|
||||
}{
|
||||
"downward defaulting caps": {
|
||||
c: current.Container{
|
||||
Privileged: false,
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &priv,
|
||||
},
|
||||
},
|
||||
},
|
||||
"downward defaulting priv": {
|
||||
c: current.Container{
|
||||
Privileged: false,
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"upward defaulting caps": {
|
||||
c: current.Container{
|
||||
Privileged: false,
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &priv,
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"biz"},
|
||||
Drop: []current.CapabilityType{"baz"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"upward defaulting priv": {
|
||||
c: current.Container{
|
||||
Capabilities: current.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
SecurityContext: ¤t.SecurityContext{
|
||||
Privileged: &privTrue,
|
||||
Capabilities: ¤t.Capabilities{
|
||||
Add: []current.CapabilityType{"foo"},
|
||||
Drop: []current.CapabilityType{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pod := ¤t.Pod{
|
||||
Spec: current.PodSpec{},
|
||||
}
|
||||
|
||||
for k, v := range testCases {
|
||||
pod.Spec.Containers = []current.Container{v.c}
|
||||
obj := roundTrip(t, runtime.Object(pod))
|
||||
defaultedPod := obj.(*current.Pod)
|
||||
c := defaultedPod.Spec.Containers[0]
|
||||
if isEqual, issues := areSecurityContextAndContainerEqual(&c); !isEqual {
|
||||
t.Errorf("test case %s expected the security context to have the same values as the container but found %#v", k, issues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func areSecurityContextAndContainerEqual(c *current.Container) (bool, []string) {
|
||||
issues := make([]string, 0)
|
||||
equal := true
|
||||
|
||||
if c.SecurityContext == nil || c.SecurityContext.Privileged == nil || c.SecurityContext.Capabilities == nil {
|
||||
equal = false
|
||||
issues = append(issues, "Expected non nil settings for SecurityContext")
|
||||
return equal, issues
|
||||
}
|
||||
if *c.SecurityContext.Privileged != c.Privileged {
|
||||
equal = false
|
||||
issues = append(issues, "The defaulted SecurityContext.Privileged value did not match the container value")
|
||||
}
|
||||
if !reflect.DeepEqual(c.Capabilities.Add, c.Capabilities.Add) {
|
||||
equal = false
|
||||
issues = append(issues, "The defaulted SecurityContext.Capabilities.Add did not match the container settings")
|
||||
}
|
||||
if !reflect.DeepEqual(c.Capabilities.Drop, c.Capabilities.Drop) {
|
||||
equal = false
|
||||
issues = append(issues, "The defaulted SecurityContext.Capabilities.Drop did not match the container settings")
|
||||
}
|
||||
return equal, issues
|
||||
}
|
||||
|
|
|
@ -636,12 +636,14 @@ type Container struct {
|
|||
Lifecycle *Lifecycle `json:"lifecycle,omitempty" description:"actions that the management system should take in response to container lifecycle events; cannot be updated"`
|
||||
// Optional: Defaults to /dev/termination-log
|
||||
TerminationMessagePath string `json:"terminationMessagePath,omitempty" description:"path at which the file to which the container's termination message will be written is mounted into the container's filesystem; message written is intended to be brief final status, such as an assertion failure message; defaults to /dev/termination-log; cannot be updated"`
|
||||
// Optional: Default to false.
|
||||
Privileged bool `json:"privileged,omitempty" description:"whether or not the container is granted privileged status; defaults to false; cannot be updated"`
|
||||
// Deprecated - see SecurityContext. Optional: Default to false.
|
||||
Privileged bool `json:"privileged,omitempty" description:"whether or not the container is granted privileged status; defaults to false; cannot be updated; deprecated; See SecurityContext."`
|
||||
// Optional: Policy for pulling images for this container
|
||||
ImagePullPolicy PullPolicy `json:"imagePullPolicy" description:"image pull policy; one of PullAlways, PullNever, PullIfNotPresent; defaults to PullAlways if :latest tag is specified, or PullIfNotPresent otherwise; cannot be updated"`
|
||||
// Optional: Capabilities for container.
|
||||
Capabilities Capabilities `json:"capabilities,omitempty" description:"capabilities for container; cannot be updated"`
|
||||
// Deprecated - see SecurityContext. Optional: Capabilities for container.
|
||||
Capabilities Capabilities `json:"capabilities,omitempty" description:"capabilities for container; cannot be updated; deprecated; See SecurityContext."`
|
||||
// Optional: SecurityContext defines the security options the pod should be run with
|
||||
SecurityContext *SecurityContext `json:"securityContext,omitempty" description:"security options the pod should run with"`
|
||||
}
|
||||
|
||||
// Handler defines a specific action that should be taken
|
||||
|
@ -1735,3 +1737,39 @@ type ComponentStatusList struct {
|
|||
|
||||
Items []ComponentStatus `json:"items" description:"list of component status objects"`
|
||||
}
|
||||
|
||||
// SecurityContext holds security configuration that will be applied to a container. SecurityContext
|
||||
// contains duplication of some existing fields from the Container resource. These duplicate fields
|
||||
// will be populated based on the Container configuration if they are not set. Defining them on
|
||||
// both the Container AND the SecurityContext will result in an error.
|
||||
type SecurityContext struct {
|
||||
// Capabilities are the capabilities to add/drop when running the container
|
||||
// Must match Container.Capabilities or be unset. Will be defaulted to Container.Capabilities if left unset
|
||||
Capabilities *Capabilities `json:"capabilities,omitempty" description:"the linux capabilites that should be added or removed"`
|
||||
|
||||
// Run the container in privileged mode
|
||||
// Must match Container.Privileged or be unset. Will be defaulted to Container.Privileged if left unset
|
||||
Privileged *bool `json:"privileged,omitempty" description:"run the container in privileged mode"`
|
||||
|
||||
// SELinuxOptions are the labels to be applied to the container
|
||||
// and volumes
|
||||
SELinuxOptions *SELinuxOptions `json:"seLinuxOptions,omitempty" description:"options that control the SELinux labels applied"`
|
||||
|
||||
// RunAsUser is the UID to run the entrypoint of the container process.
|
||||
RunAsUser *int64 `json:"runAsUser,omitempty" description:"the user id that runs the first process in the container"`
|
||||
}
|
||||
|
||||
// SELinuxOptions are the labels to be applied to the container.
|
||||
type SELinuxOptions struct {
|
||||
// SELinux user label
|
||||
User string `json:"user,omitempty" description:"the user label to apply to the container"`
|
||||
|
||||
// SELinux role label
|
||||
Role string `json:"role,omitempty" description:"the role label to apply to the container"`
|
||||
|
||||
// SELinux type label
|
||||
Type string `json:"type,omitempty" description:"the type label to apply to the container"`
|
||||
|
||||
// SELinux level label.
|
||||
Level string `json:"level,omitempty" description:"the level label to apply to the container"`
|
||||
}
|
||||
|
|
|
@ -776,15 +776,12 @@ func validateContainers(containers []api.Container, volumes util.StringSet) errs
|
|||
allNames := util.StringSet{}
|
||||
for i, ctr := range containers {
|
||||
cErrs := errs.ValidationErrorList{}
|
||||
capabilities := capabilities.Get()
|
||||
if len(ctr.Name) == 0 {
|
||||
cErrs = append(cErrs, errs.NewFieldRequired("name"))
|
||||
} else if !util.IsDNS1123Label(ctr.Name) {
|
||||
cErrs = append(cErrs, errs.NewFieldInvalid("name", ctr.Name, dns1123LabelErrorMsg))
|
||||
} else if allNames.Has(ctr.Name) {
|
||||
cErrs = append(cErrs, errs.NewFieldDuplicate("name", ctr.Name))
|
||||
} else if ctr.Privileged && !capabilities.AllowPrivileged {
|
||||
cErrs = append(cErrs, errs.NewFieldForbidden("privileged", ctr.Privileged))
|
||||
} else {
|
||||
allNames.Insert(ctr.Name)
|
||||
}
|
||||
|
@ -801,6 +798,7 @@ func validateContainers(containers []api.Container, volumes util.StringSet) errs
|
|||
cErrs = append(cErrs, validateVolumeMounts(ctr.VolumeMounts, volumes).Prefix("volumeMounts")...)
|
||||
cErrs = append(cErrs, validatePullPolicy(&ctr).Prefix("pullPolicy")...)
|
||||
cErrs = append(cErrs, ValidateResourceRequirements(&ctr.Resources).Prefix("resources")...)
|
||||
cErrs = append(cErrs, ValidateSecurityContext(ctr.SecurityContext).Prefix("securityContext")...)
|
||||
allErrs = append(allErrs, cErrs.PrefixIndex(i)...)
|
||||
}
|
||||
// Check for colliding ports across all containers.
|
||||
|
@ -1481,3 +1479,25 @@ func ValidateEndpointsUpdate(oldEndpoints, newEndpoints *api.Endpoints) errs.Val
|
|||
allErrs = append(allErrs, validateEndpointSubsets(newEndpoints.Subsets).Prefix("subsets")...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateSecurityContext ensure the security context contains valid settings
|
||||
func ValidateSecurityContext(sc *api.SecurityContext) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
//this should only be true for testing since SecurityContext is defaulted by the api
|
||||
if sc == nil {
|
||||
return allErrs
|
||||
}
|
||||
|
||||
if sc.Privileged != nil {
|
||||
if *sc.Privileged && !capabilities.Get().AllowPrivileged {
|
||||
allErrs = append(allErrs, errs.NewFieldForbidden("privileged", sc.Privileged))
|
||||
}
|
||||
}
|
||||
|
||||
if sc.RunAsUser != nil {
|
||||
if *sc.RunAsUser < 0 {
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid("runAsUser", *sc.RunAsUser, "runAsUser cannot be negative"))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
|
|
@ -901,7 +901,7 @@ func TestValidateContainers(t *testing.T) {
|
|||
},
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
},
|
||||
{Name: "abc-1234", Image: "image", Privileged: true, ImagePullPolicy: "IfNotPresent"},
|
||||
{Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", SecurityContext: fakeValidSecurityContext(true)},
|
||||
}
|
||||
if errs := validateContainers(successCase, volumes); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
|
@ -1015,7 +1015,7 @@ func TestValidateContainers(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"privilege disabled": {
|
||||
{Name: "abc", Image: "image", Privileged: true},
|
||||
{Name: "abc", Image: "image", SecurityContext: fakeValidSecurityContext(true)},
|
||||
},
|
||||
"invalid compute resource": {
|
||||
{
|
||||
|
@ -3180,3 +3180,89 @@ func TestValidateEndpoints(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSecurityContext(t *testing.T) {
|
||||
priv := false
|
||||
var runAsUser int64 = 1
|
||||
fullValidSC := func() *api.SecurityContext {
|
||||
return &api.SecurityContext{
|
||||
Privileged: &priv,
|
||||
Capabilities: &api.Capabilities{
|
||||
Add: []api.CapabilityType{"foo"},
|
||||
Drop: []api.CapabilityType{"bar"},
|
||||
},
|
||||
SELinuxOptions: &api.SELinuxOptions{
|
||||
User: "user",
|
||||
Role: "role",
|
||||
Type: "type",
|
||||
Level: "level",
|
||||
},
|
||||
RunAsUser: &runAsUser,
|
||||
}
|
||||
}
|
||||
|
||||
//setup data
|
||||
allSettings := fullValidSC()
|
||||
noCaps := fullValidSC()
|
||||
noCaps.Capabilities = nil
|
||||
|
||||
noSELinux := fullValidSC()
|
||||
noSELinux.SELinuxOptions = nil
|
||||
|
||||
noPrivRequest := fullValidSC()
|
||||
noPrivRequest.Privileged = nil
|
||||
|
||||
noRunAsUser := fullValidSC()
|
||||
noRunAsUser.RunAsUser = nil
|
||||
|
||||
successCases := map[string]struct {
|
||||
sc *api.SecurityContext
|
||||
}{
|
||||
"all settings": {allSettings},
|
||||
"no capabilities": {noCaps},
|
||||
"no selinux": {noSELinux},
|
||||
"no priv request": {noPrivRequest},
|
||||
"no run as user": {noRunAsUser},
|
||||
}
|
||||
for k, v := range successCases {
|
||||
if errs := ValidateSecurityContext(v.sc); len(errs) != 0 {
|
||||
t.Errorf("Expected success for %s, got %v", k, errs)
|
||||
}
|
||||
}
|
||||
|
||||
privRequestWithGlobalDeny := fullValidSC()
|
||||
requestPrivileged := true
|
||||
privRequestWithGlobalDeny.Privileged = &requestPrivileged
|
||||
|
||||
negativeRunAsUser := fullValidSC()
|
||||
var negativeUser int64 = -1
|
||||
negativeRunAsUser.RunAsUser = &negativeUser
|
||||
|
||||
errorCases := map[string]struct {
|
||||
sc *api.SecurityContext
|
||||
errorType fielderrors.ValidationErrorType
|
||||
errorDetail string
|
||||
}{
|
||||
"request privileged when capabilities forbids": {
|
||||
sc: privRequestWithGlobalDeny,
|
||||
errorType: "FieldValueForbidden",
|
||||
errorDetail: "",
|
||||
},
|
||||
"negative RunAsUser": {
|
||||
sc: negativeRunAsUser,
|
||||
errorType: "FieldValueInvalid",
|
||||
errorDetail: "runAsUser cannot be negative",
|
||||
},
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
if errs := ValidateSecurityContext(v.sc); len(errs) == 0 || errs[0].(*errors.ValidationError).Type != v.errorType || errs[0].(*errors.ValidationError).Detail != v.errorDetail {
|
||||
t.Errorf("Expected error type %s with detail %s for %s, got %v", v.errorType, v.errorDetail, k, errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fakeValidSecurityContext(priv bool) *api.SecurityContext {
|
||||
return &api.SecurityContext{
|
||||
Privileged: &priv,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import (
|
|||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||
|
@ -112,6 +113,7 @@ func newReplicationController(replicas int) *api.ReplicationController {
|
|||
Image: "foo/bar",
|
||||
TerminationMessagePath: api.TerminationMessagePathDefault,
|
||||
ImagePullPolicy: api.PullIfNotPresent,
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
},
|
||||
},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
@ -46,6 +47,7 @@ func TestDecodeSinglePod(t *testing.T) {
|
|||
Image: "test/image",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
@ -108,6 +110,7 @@ func TestDecodePodList(t *testing.T) {
|
|||
Image: "test/image",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||
)
|
||||
|
||||
|
@ -62,7 +63,14 @@ func CreateValidPod(name, namespace, source string) *api.Pod {
|
|||
Spec: api.PodSpec{
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: "ctr",
|
||||
Image: "image",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||
)
|
||||
|
||||
|
@ -105,7 +106,8 @@ func TestReadContainerManifestFromFile(t *testing.T) {
|
|||
Name: "image",
|
||||
Image: "test/image",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always"}},
|
||||
ImagePullPolicy: "Always",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
@ -131,7 +133,8 @@ func TestReadContainerManifestFromFile(t *testing.T) {
|
|||
Name: "image",
|
||||
Image: "test/image",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always"}},
|
||||
ImagePullPolicy: "Always",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
@ -182,7 +185,7 @@ func TestReadPodsFromFile(t *testing.T) {
|
|||
Namespace: "mynamespace",
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{Name: "image", Image: "test/image"}},
|
||||
Containers: []api.Container{{Name: "image", Image: "test/image", SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
},
|
||||
expected: CreatePodUpdate(kubelet.SET, kubelet.FileSource, &api.Pod{
|
||||
|
@ -200,7 +203,8 @@ func TestReadPodsFromFile(t *testing.T) {
|
|||
Name: "image",
|
||||
Image: "test/image",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "IfNotPresent"}},
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
@ -216,7 +220,7 @@ func TestReadPodsFromFile(t *testing.T) {
|
|||
UID: "12345",
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{Name: "image", Image: "test/image"}},
|
||||
Containers: []api.Container{{Name: "image", Image: "test/image", SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
},
|
||||
expected: CreatePodUpdate(kubelet.SET, kubelet.FileSource, &api.Pod{
|
||||
|
@ -234,7 +238,8 @@ func TestReadPodsFromFile(t *testing.T) {
|
|||
Name: "image",
|
||||
Image: "test/image",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "IfNotPresent"}},
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
|
||||
)
|
||||
|
@ -151,7 +152,8 @@ func TestExtractManifestFromHTTP(t *testing.T) {
|
|||
Name: "1",
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always"}},
|
||||
ImagePullPolicy: "Always",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
@ -177,7 +179,8 @@ func TestExtractManifestFromHTTP(t *testing.T) {
|
|||
Name: "ctr",
|
||||
Image: "image",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "IfNotPresent"}},
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
@ -203,7 +206,8 @@ func TestExtractManifestFromHTTP(t *testing.T) {
|
|||
Name: "1",
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always"}},
|
||||
ImagePullPolicy: "Always",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
@ -233,7 +237,8 @@ func TestExtractManifestFromHTTP(t *testing.T) {
|
|||
Name: "1",
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always"}},
|
||||
ImagePullPolicy: "Always",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
},
|
||||
&api.Pod{
|
||||
|
@ -252,7 +257,8 @@ func TestExtractManifestFromHTTP(t *testing.T) {
|
|||
Name: "1",
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "IfNotPresent"}},
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
@ -344,7 +350,8 @@ func TestExtractPodsFromHTTP(t *testing.T) {
|
|||
Name: "1",
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always"}},
|
||||
ImagePullPolicy: "Always",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
@ -396,7 +403,8 @@ func TestExtractPodsFromHTTP(t *testing.T) {
|
|||
Name: "1",
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always"}},
|
||||
ImagePullPolicy: "Always",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
},
|
||||
&api.Pod{
|
||||
|
@ -415,7 +423,8 @@ func TestExtractPodsFromHTTP(t *testing.T) {
|
|||
Name: "2",
|
||||
Image: "bar",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "IfNotPresent"}},
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
|
|
@ -38,6 +38,7 @@ import (
|
|||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/prober"
|
||||
kubeletTypes "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/types"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/probe"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
|
@ -524,6 +525,8 @@ func (dm *DockerManager) runContainer(pod *api.Pod, container *api.Container, op
|
|||
|
||||
glog.V(3).Infof("Container %v/%v/%v: setting entrypoint \"%v\" and command \"%v\"", pod.Namespace, pod.Name, container.Name, dockerOpts.Config.Entrypoint, dockerOpts.Config.Cmd)
|
||||
|
||||
securityContextProvider := securitycontext.NewSimpleSecurityContextProvider()
|
||||
securityContextProvider.ModifyContainerConfig(pod, container, dockerOpts.Config)
|
||||
dockerContainer, err := dm.client.CreateContainer(dockerOpts)
|
||||
if err != nil {
|
||||
if ref != nil {
|
||||
|
@ -554,22 +557,15 @@ func (dm *DockerManager) runContainer(pod *api.Pod, container *api.Container, op
|
|||
}
|
||||
}
|
||||
|
||||
privileged := false
|
||||
if capabilities.Get().AllowPrivileged {
|
||||
privileged = container.Privileged
|
||||
} else if container.Privileged {
|
||||
if !capabilities.Get().AllowPrivileged && securitycontext.HasPrivilegedRequest(container) {
|
||||
return "", fmt.Errorf("container requested privileged mode, but it is disallowed globally.")
|
||||
}
|
||||
|
||||
capAdd, capDrop := makeCapabilites(container.Capabilities.Add, container.Capabilities.Drop)
|
||||
hc := &docker.HostConfig{
|
||||
PortBindings: portBindings,
|
||||
Binds: opts.Binds,
|
||||
NetworkMode: opts.NetMode,
|
||||
IpcMode: opts.IpcMode,
|
||||
Privileged: privileged,
|
||||
CapAdd: capAdd,
|
||||
CapDrop: capDrop,
|
||||
}
|
||||
if len(opts.DNS) > 0 {
|
||||
hc.DNS = opts.DNS
|
||||
|
@ -580,6 +576,7 @@ func (dm *DockerManager) runContainer(pod *api.Pod, container *api.Container, op
|
|||
if len(opts.CgroupParent) > 0 {
|
||||
hc.CgroupParent = opts.CgroupParent
|
||||
}
|
||||
securityContextProvider.ModifyHostConfig(pod, container, hc)
|
||||
|
||||
if err = dm.client.StartContainer(dockerContainer.ID, hc); err != nil {
|
||||
if ref != nil {
|
||||
|
@ -637,20 +634,6 @@ func makePortsAndBindings(container *api.Container) (map[docker.Port]struct{}, m
|
|||
return exposedPorts, portBindings
|
||||
}
|
||||
|
||||
func makeCapabilites(capAdd []api.CapabilityType, capDrop []api.CapabilityType) ([]string, []string) {
|
||||
var (
|
||||
addCaps []string
|
||||
dropCaps []string
|
||||
)
|
||||
for _, cap := range capAdd {
|
||||
addCaps = append(addCaps, string(cap))
|
||||
}
|
||||
for _, cap := range capDrop {
|
||||
dropCaps = append(dropCaps, string(cap))
|
||||
}
|
||||
return addCaps, dropCaps
|
||||
}
|
||||
|
||||
// A helper function to get the KubeletContainerName and hash from a docker
|
||||
// container.
|
||||
func getDockerContainerNameInfo(c *docker.APIContainers) (*KubeletContainerName, uint64, error) {
|
||||
|
|
|
@ -38,6 +38,7 @@ import (
|
|||
kubecontainer "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/container"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/prober"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/probe"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
|
||||
|
@ -187,23 +188,29 @@ func rawValue(value string) *json.RawMessage {
|
|||
}
|
||||
|
||||
// setIsolators overrides the isolators of the pod manifest if necessary.
|
||||
// TODO need an apply config in security context for rkt
|
||||
func setIsolators(app *appctypes.App, c *api.Container) error {
|
||||
if len(c.Capabilities.Add) > 0 || len(c.Capabilities.Drop) > 0 || len(c.Resources.Limits) > 0 || len(c.Resources.Requests) > 0 {
|
||||
hasCapRequests := securitycontext.HasCapabilitiesRequest(c)
|
||||
if hasCapRequests || len(c.Resources.Limits) > 0 || len(c.Resources.Requests) > 0 {
|
||||
app.Isolators = []appctypes.Isolator{}
|
||||
}
|
||||
|
||||
// Retained capabilities/privileged.
|
||||
privileged := false
|
||||
if capabilities.Get().AllowPrivileged {
|
||||
privileged = c.Privileged
|
||||
} else if c.Privileged {
|
||||
return fmt.Errorf("privileged is disallowed globally")
|
||||
if !capabilities.Get().AllowPrivileged && securitycontext.HasPrivilegedRequest(c) {
|
||||
return fmt.Errorf("container requested privileged mode, but it is disallowed globally.")
|
||||
} else {
|
||||
if c.SecurityContext != nil && c.SecurityContext.Privileged != nil {
|
||||
privileged = *c.SecurityContext.Privileged
|
||||
}
|
||||
}
|
||||
var addCaps string
|
||||
if privileged {
|
||||
addCaps = getAllCapabilities()
|
||||
} else {
|
||||
addCaps = getCapabilities(c.Capabilities.Add)
|
||||
if hasCapRequests {
|
||||
addCaps = getCapabilities(c.SecurityContext.Capabilities.Add)
|
||||
}
|
||||
}
|
||||
if len(addCaps) > 0 {
|
||||
// TODO(yifan): Replace with constructor, see:
|
||||
|
@ -216,7 +223,10 @@ func setIsolators(app *appctypes.App, c *api.Container) error {
|
|||
}
|
||||
|
||||
// Removed capabilities.
|
||||
dropCaps := getCapabilities(c.Capabilities.Drop)
|
||||
var dropCaps string
|
||||
if hasCapRequests {
|
||||
dropCaps = getCapabilities(c.SecurityContext.Capabilities.Drop)
|
||||
}
|
||||
if len(dropCaps) > 0 {
|
||||
// TODO(yifan): Replace with constructor, see:
|
||||
// https://github.com/appc/spec/issues/268
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools/etcdtest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
|
@ -68,6 +69,7 @@ func validNewPod() *api.Pod {
|
|||
ImagePullPolicy: api.PullAlways,
|
||||
|
||||
TerminationMessagePath: api.TerminationMessagePathDefault,
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1108,8 +1110,9 @@ func TestEtcdUpdateScheduled(t *testing.T) {
|
|||
Host: "machine",
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: "foobar",
|
||||
Image: "foo:v1",
|
||||
Name: "foobar",
|
||||
Image: "foo:v1",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1131,6 +1134,7 @@ func TestEtcdUpdateScheduled(t *testing.T) {
|
|||
Image: "foo:v2",
|
||||
ImagePullPolicy: api.PullIfNotPresent,
|
||||
TerminationMessagePath: api.TerminationMessagePathDefault,
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
},
|
||||
},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
|
@ -1169,7 +1173,8 @@ func TestEtcdUpdateStatus(t *testing.T) {
|
|||
Host: "machine",
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "foo:v1",
|
||||
Image: "foo:v1",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 securitycontext contains security context api implementations
|
||||
package securitycontext
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 securitycontext
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
// ValidSecurityContextWithContainerDefaults creates a valid security context provider based on
|
||||
// empty container defaults. Used for testing.
|
||||
func ValidSecurityContextWithContainerDefaults() *api.SecurityContext {
|
||||
priv := false
|
||||
return &api.SecurityContext{
|
||||
Capabilities: &api.Capabilities{},
|
||||
Privileged: &priv,
|
||||
}
|
||||
}
|
||||
|
||||
// NewFakeSecurityContextProvider creates a new, no-op security context provider.
|
||||
func NewFakeSecurityContextProvider() SecurityContextProvider {
|
||||
return FakeSecurityContextProvider{}
|
||||
}
|
||||
|
||||
type FakeSecurityContextProvider struct{}
|
||||
|
||||
func (p FakeSecurityContextProvider) ModifyContainerConfig(pod *api.Pod, container *api.Container, config *docker.Config) {
|
||||
}
|
||||
func (p FakeSecurityContextProvider) ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *docker.HostConfig) {
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 securitycontext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
// NewSimpleSecurityContextProvider creates a new SimpleSecurityContextProvider.
|
||||
func NewSimpleSecurityContextProvider() SecurityContextProvider {
|
||||
return SimpleSecurityContextProvider{}
|
||||
}
|
||||
|
||||
// SimpleSecurityContextProvider is the default implementation of a SecurityContextProvider.
|
||||
type SimpleSecurityContextProvider struct{}
|
||||
|
||||
// ModifyContainerConfig is called before the Docker createContainer call.
|
||||
// The security context provider can make changes to the Config with which
|
||||
// the container is created.
|
||||
func (p SimpleSecurityContextProvider) ModifyContainerConfig(pod *api.Pod, container *api.Container, config *docker.Config) {
|
||||
if container.SecurityContext == nil {
|
||||
return
|
||||
}
|
||||
if container.SecurityContext.RunAsUser != nil {
|
||||
config.User = strconv.FormatInt(*container.SecurityContext.RunAsUser, 10)
|
||||
}
|
||||
}
|
||||
|
||||
// ModifyHostConfig is called before the Docker runContainer call.
|
||||
// The security context provider can make changes to the HostConfig, affecting
|
||||
// security options, whether the container is privileged, volume binds, etc.
|
||||
// An error is returned if it's not possible to secure the container as requested
|
||||
// with a security context.
|
||||
func (p SimpleSecurityContextProvider) ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *docker.HostConfig) {
|
||||
if container.SecurityContext == nil {
|
||||
return
|
||||
}
|
||||
if container.SecurityContext.Privileged != nil {
|
||||
hostConfig.Privileged = *container.SecurityContext.Privileged
|
||||
}
|
||||
|
||||
if container.SecurityContext.Capabilities != nil {
|
||||
add, drop := makeCapabilites(container.SecurityContext.Capabilities.Add, container.SecurityContext.Capabilities.Drop)
|
||||
hostConfig.CapAdd = add
|
||||
hostConfig.CapDrop = drop
|
||||
}
|
||||
|
||||
if container.SecurityContext.SELinuxOptions != nil {
|
||||
hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelUser, container.SecurityContext.SELinuxOptions.User)
|
||||
hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelRole, container.SecurityContext.SELinuxOptions.Role)
|
||||
hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelType, container.SecurityContext.SELinuxOptions.Type)
|
||||
hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelLevel, container.SecurityContext.SELinuxOptions.Level)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if len(value) > 0 {
|
||||
config = append(config, fmt.Sprintf("%s:%s", name, value))
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// makeCapabilites creates string slices from CapabilityType slices
|
||||
func makeCapabilites(capAdd []api.CapabilityType, capDrop []api.CapabilityType) ([]string, []string) {
|
||||
var (
|
||||
addCaps []string
|
||||
dropCaps []string
|
||||
)
|
||||
for _, cap := range capAdd {
|
||||
addCaps = append(addCaps, string(cap))
|
||||
}
|
||||
for _, cap := range capDrop {
|
||||
dropCaps = append(dropCaps, string(cap))
|
||||
}
|
||||
return addCaps, dropCaps
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 securitycontext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
func TestModifyContainerConfig(t *testing.T) {
|
||||
var uid int64 = 1
|
||||
testCases := map[string]struct {
|
||||
securityContext *api.SecurityContext
|
||||
expected *docker.Config
|
||||
}{
|
||||
"modify config, value set for user": {
|
||||
securityContext: &api.SecurityContext{
|
||||
RunAsUser: &uid,
|
||||
},
|
||||
expected: &docker.Config{
|
||||
User: strconv.FormatInt(uid, 10),
|
||||
},
|
||||
},
|
||||
"modify config, nil user value": {
|
||||
securityContext: &api.SecurityContext{},
|
||||
expected: &docker.Config{},
|
||||
},
|
||||
}
|
||||
|
||||
provider := NewSimpleSecurityContextProvider()
|
||||
dummyContainer := &api.Container{}
|
||||
for k, v := range testCases {
|
||||
dummyContainer.SecurityContext = v.securityContext
|
||||
dockerCfg := &docker.Config{}
|
||||
provider.ModifyContainerConfig(nil, dummyContainer, dockerCfg)
|
||||
if !reflect.DeepEqual(v.expected, dockerCfg) {
|
||||
t.Errorf("unexpected modification of docker config for %s. Expected: %#v Got: %#v", k, v.expected, dockerCfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestModifyHostConfig(t *testing.T) {
|
||||
nilPrivSC := fullValidSecurityContext()
|
||||
nilPrivSC.Privileged = nil
|
||||
nilPrivHC := fullValidHostConfig()
|
||||
nilPrivHC.Privileged = false
|
||||
|
||||
nilCapsSC := fullValidSecurityContext()
|
||||
nilCapsSC.Capabilities = nil
|
||||
nilCapsHC := fullValidHostConfig()
|
||||
nilCapsHC.CapAdd = *new([]string)
|
||||
nilCapsHC.CapDrop = *new([]string)
|
||||
|
||||
nilSELinuxSC := fullValidSecurityContext()
|
||||
nilSELinuxSC.SELinuxOptions = nil
|
||||
nilSELinuxHC := fullValidHostConfig()
|
||||
nilSELinuxHC.SecurityOpt = *new([]string)
|
||||
|
||||
seLinuxLabelsSC := fullValidSecurityContext()
|
||||
seLinuxLabelsHC := fullValidHostConfig()
|
||||
|
||||
testCases := map[string]struct {
|
||||
securityContext *api.SecurityContext
|
||||
expected *docker.HostConfig
|
||||
}{
|
||||
"full settings": {
|
||||
securityContext: fullValidSecurityContext(),
|
||||
expected: fullValidHostConfig(),
|
||||
},
|
||||
"nil privileged": {
|
||||
securityContext: nilPrivSC,
|
||||
expected: nilPrivHC,
|
||||
},
|
||||
"nil capabilities": {
|
||||
securityContext: nilCapsSC,
|
||||
expected: nilCapsHC,
|
||||
},
|
||||
"nil selinux options": {
|
||||
securityContext: nilSELinuxSC,
|
||||
expected: nilSELinuxHC,
|
||||
},
|
||||
"selinux labels": {
|
||||
securityContext: seLinuxLabelsSC,
|
||||
expected: seLinuxLabelsHC,
|
||||
},
|
||||
}
|
||||
|
||||
provider := NewSimpleSecurityContextProvider()
|
||||
dummyContainer := &api.Container{}
|
||||
for k, v := range testCases {
|
||||
dummyContainer.SecurityContext = v.securityContext
|
||||
dockerCfg := &docker.HostConfig{}
|
||||
provider.ModifyHostConfig(nil, dummyContainer, dockerCfg)
|
||||
if !reflect.DeepEqual(v.expected, dockerCfg) {
|
||||
t.Errorf("unexpected modification of host config for %s. Expected: %#v Got: %#v", k, v.expected, dockerCfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestModifySecurityOption(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
config []string
|
||||
optName string
|
||||
optVal string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "Empty val",
|
||||
config: []string{"a:b", "c:d"},
|
||||
optName: "optA",
|
||||
optVal: "",
|
||||
expected: []string{"a:b", "c:d"},
|
||||
},
|
||||
{
|
||||
name: "Valid",
|
||||
config: []string{"a:b", "c:d"},
|
||||
optName: "e",
|
||||
optVal: "f",
|
||||
expected: []string{"a:b", "c:d", "e:f"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
actual := modifySecurityOption(tc.config, tc.optName, tc.optVal)
|
||||
if !reflect.DeepEqual(tc.expected, actual) {
|
||||
t.Errorf("Failed to apply options correctly for tc: %S. Expected: %v but got %v", tc.name, tc.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fullValidSecurityContext() *api.SecurityContext {
|
||||
priv := true
|
||||
return &api.SecurityContext{
|
||||
Privileged: &priv,
|
||||
Capabilities: &api.Capabilities{
|
||||
Add: []api.CapabilityType{"addCapA", "addCapB"},
|
||||
Drop: []api.CapabilityType{"dropCapA", "dropCapB"},
|
||||
},
|
||||
SELinuxOptions: &api.SELinuxOptions{
|
||||
User: "user",
|
||||
Role: "role",
|
||||
Type: "type",
|
||||
Level: "level",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func fullValidHostConfig() *docker.HostConfig {
|
||||
return &docker.HostConfig{
|
||||
Privileged: true,
|
||||
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"),
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 securitycontext
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
type SecurityContextProvider interface {
|
||||
// ModifyContainerConfig is called before the Docker createContainer call.
|
||||
// The security context provider can make changes to the Config with which
|
||||
// the container is created.
|
||||
ModifyContainerConfig(pod *api.Pod, container *api.Container, config *docker.Config)
|
||||
|
||||
// ModifyHostConfig is called before the Docker runContainer call.
|
||||
// The security context provider can make changes to the HostConfig, affecting
|
||||
// security options, whether the container is privileged, volume binds, etc.
|
||||
// An error is returned if it's not possible to secure the container as requested
|
||||
// with a security context.
|
||||
ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *docker.HostConfig)
|
||||
}
|
||||
|
||||
const (
|
||||
dockerLabelUser string = "label:user"
|
||||
dockerLabelRole string = "label:role"
|
||||
dockerLabelType string = "label:type"
|
||||
dockerLabelLevel string = "label:level"
|
||||
dockerLabelDisable string = "label:disable"
|
||||
)
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 securitycontext
|
||||
|
||||
import "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
|
||||
// HasPrivilegedRequest returns the value of SecurityContext.Privileged, taking into account
|
||||
// the possibility of nils
|
||||
func HasPrivilegedRequest(container *api.Container) bool {
|
||||
if container.SecurityContext == nil {
|
||||
return false
|
||||
}
|
||||
if container.SecurityContext.Privileged == nil {
|
||||
return false
|
||||
}
|
||||
return *container.SecurityContext.Privileged
|
||||
}
|
||||
|
||||
// HasCapabilitiesRequest returns true if Adds or Drops are defined in the security context
|
||||
// capabilities, taking into account nils
|
||||
func HasCapabilitiesRequest(container *api.Container) bool {
|
||||
if container.SecurityContext == nil {
|
||||
return false
|
||||
}
|
||||
if container.SecurityContext.Capabilities == nil {
|
||||
return false
|
||||
}
|
||||
return len(container.SecurityContext.Capabilities.Add) > 0 || len(container.SecurityContext.Capabilities.Drop) > 0
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 scdeny
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("SecurityContextDeny", func(client client.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewSecurityContextDeny(client), nil
|
||||
})
|
||||
}
|
||||
|
||||
// plugin contains the client used by the SecurityContextDeny admission controller
|
||||
type plugin struct {
|
||||
client client.Interface
|
||||
}
|
||||
|
||||
// NewSecurityContextDeny creates a new instance of the SecurityContextDeny admission controller
|
||||
func NewSecurityContextDeny(client client.Interface) admission.Interface {
|
||||
return &plugin{client}
|
||||
}
|
||||
|
||||
// Admit will deny any SecurityContext that defines options that were not previously available in the api.Container
|
||||
// struct (Capabilities and Privileged)
|
||||
func (p *plugin) Admit(a admission.Attributes) (err error) {
|
||||
if a.GetOperation() == "DELETE" {
|
||||
return nil
|
||||
}
|
||||
if a.GetResource() != string(api.ResourcePods) {
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := a.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
for _, v := range pod.Spec.Containers {
|
||||
if v.SecurityContext != nil {
|
||||
if v.SecurityContext.SELinuxOptions != nil {
|
||||
return apierrors.NewForbidden(a.GetResource(), pod.Name, fmt.Errorf("SecurityContext.SELinuxOptions is forbidden"))
|
||||
}
|
||||
if v.SecurityContext.RunAsUser != nil {
|
||||
return apierrors.NewForbidden(a.GetResource(), pod.Name, fmt.Errorf("SecurityContext.RunAsUser is forbidden"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 scdeny
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// ensures the SecurityContext is denied if it defines anything more than Caps or Privileged
|
||||
func TestAdmission(t *testing.T) {
|
||||
handler := NewSecurityContextDeny(nil)
|
||||
|
||||
var runAsUser int64 = 1
|
||||
priv := true
|
||||
successCases := map[string]*api.SecurityContext{
|
||||
"no sc": nil,
|
||||
"empty sc": {},
|
||||
"valid sc": {Privileged: &priv, Capabilities: &api.Capabilities{}},
|
||||
}
|
||||
|
||||
pod := api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for k, v := range successCases {
|
||||
pod.Spec.Containers[0].SecurityContext = v
|
||||
err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", "foo", string(api.ResourcePods), "ignored"))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler for case %s", k)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := map[string]*api.SecurityContext{
|
||||
"run as user": {RunAsUser: &runAsUser},
|
||||
"se linux optons": {SELinuxOptions: &api.SELinuxOptions{}},
|
||||
"mixed settings": {Privileged: &priv, RunAsUser: &runAsUser, SELinuxOptions: &api.SELinuxOptions{}},
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
pod.Spec.Containers[0].SecurityContext = v
|
||||
err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", "foo", string(api.ResourcePods), "ignored"))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error returned from admission handler for case %s", k)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue