security context initial implementation - squash
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user