mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	Revert "Revert "Security context - types, kubelet, admission""
This commit is contained in:
		@@ -7501,7 +7501,7 @@
 | 
			
		||||
    "properties": {
 | 
			
		||||
     "capabilities": {
 | 
			
		||||
      "$ref": "v1beta1.Capabilities",
 | 
			
		||||
      "description": "capabilities for container; cannot be updated"
 | 
			
		||||
      "description": "capabilities for container; cannot be updated; deprecated; See SecurityContext"
 | 
			
		||||
     },
 | 
			
		||||
     "command": {
 | 
			
		||||
      "type": "array",
 | 
			
		||||
@@ -7563,7 +7563,7 @@
 | 
			
		||||
     },
 | 
			
		||||
     "privileged": {
 | 
			
		||||
      "type": "boolean",
 | 
			
		||||
      "description": "whether or not the container is granted privileged status; defaults to false; cannot be updated"
 | 
			
		||||
      "description": "whether or not the container is granted privileged status; defaults to false; cannot be updated; deprecated; See SecurityContext"
 | 
			
		||||
     },
 | 
			
		||||
     "readinessProbe": {
 | 
			
		||||
      "$ref": "v1beta1.LivenessProbe",
 | 
			
		||||
@@ -7573,6 +7573,10 @@
 | 
			
		||||
      "$ref": "v1beta1.ResourceRequirements",
 | 
			
		||||
      "description": "Compute Resources required by this container; cannot be updated"
 | 
			
		||||
     },
 | 
			
		||||
     "securityContext": {
 | 
			
		||||
      "$ref": "v1beta1.SecurityContext",
 | 
			
		||||
      "description": "security options the pod should run with"
 | 
			
		||||
     },
 | 
			
		||||
     "terminationMessagePath": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "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"
 | 
			
		||||
@@ -7623,7 +7627,8 @@
 | 
			
		||||
      "description": "restart policy for all containers within the pod; one of RestartPolicyAlways, RestartPolicyOnFailure, RestartPolicyNever"
 | 
			
		||||
     },
 | 
			
		||||
     "terminationGracePeriodSeconds": {
 | 
			
		||||
      "$ref": "int64",
 | 
			
		||||
      "type": "integer",
 | 
			
		||||
      "format": "int64",
 | 
			
		||||
      "description": "optional duration in seconds the pod needs to terminate gracefully; may be decreased in delete request; value must be non-negative integer; the value zero indicates delete immediately; if this value is not set, the default grace period will be used instead; the grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal; set this value longer than the expected cleanup time for your process"
 | 
			
		||||
     },
 | 
			
		||||
     "uuid": {
 | 
			
		||||
@@ -7700,7 +7705,8 @@
 | 
			
		||||
      "description": "an optional prefix to use to generate a unique name; has the same validation rules as name; optional, and is applied only name if is not specified"
 | 
			
		||||
     },
 | 
			
		||||
     "gracePeriodSeconds": {
 | 
			
		||||
      "$ref": "int64",
 | 
			
		||||
      "type": "integer",
 | 
			
		||||
      "format": "int64",
 | 
			
		||||
      "description": "the duration in seconds to wait before deleting this object; defaults to a per object value if not specified; zero means delete immediately"
 | 
			
		||||
     },
 | 
			
		||||
     "id": {
 | 
			
		||||
@@ -9817,6 +9823,27 @@
 | 
			
		||||
    "id": "v1beta1.RestartPolicyOnFailure",
 | 
			
		||||
    "properties": {}
 | 
			
		||||
   },
 | 
			
		||||
   "v1beta1.SELinuxOptions": {
 | 
			
		||||
    "id": "v1beta1.SELinuxOptions",
 | 
			
		||||
    "properties": {
 | 
			
		||||
     "level": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "description": "the level label to apply to the container"
 | 
			
		||||
     },
 | 
			
		||||
     "role": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "description": "the role label to apply to the container"
 | 
			
		||||
     },
 | 
			
		||||
     "type": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "description": "the type label to apply to the container"
 | 
			
		||||
     },
 | 
			
		||||
     "user": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "description": "the user label to apply to the container"
 | 
			
		||||
     }
 | 
			
		||||
    }
 | 
			
		||||
   },
 | 
			
		||||
   "v1beta1.Secret": {
 | 
			
		||||
    "id": "v1beta1.Secret",
 | 
			
		||||
    "properties": {
 | 
			
		||||
@@ -9945,6 +9972,28 @@
 | 
			
		||||
     }
 | 
			
		||||
    }
 | 
			
		||||
   },
 | 
			
		||||
   "v1beta1.SecurityContext": {
 | 
			
		||||
    "id": "v1beta1.SecurityContext",
 | 
			
		||||
    "properties": {
 | 
			
		||||
     "capabilities": {
 | 
			
		||||
      "$ref": "v1beta1.Capabilities",
 | 
			
		||||
      "description": "the linux capabilites that should be added or removed"
 | 
			
		||||
     },
 | 
			
		||||
     "privileged": {
 | 
			
		||||
      "type": "boolean",
 | 
			
		||||
      "description": "run the container in privileged mode"
 | 
			
		||||
     },
 | 
			
		||||
     "runAsUser": {
 | 
			
		||||
      "type": "integer",
 | 
			
		||||
      "format": "int64",
 | 
			
		||||
      "description": "the user id that runs the first process in the container"
 | 
			
		||||
     },
 | 
			
		||||
     "seLinuxOptions": {
 | 
			
		||||
      "$ref": "v1beta1.SELinuxOptions",
 | 
			
		||||
      "description": "options that control the SELinux labels applied"
 | 
			
		||||
     }
 | 
			
		||||
    }
 | 
			
		||||
   },
 | 
			
		||||
   "v1beta1.Service": {
 | 
			
		||||
    "id": "v1beta1.Service",
 | 
			
		||||
    "required": [
 | 
			
		||||
 
 | 
			
		||||
@@ -7501,7 +7501,7 @@
 | 
			
		||||
    "properties": {
 | 
			
		||||
     "capabilities": {
 | 
			
		||||
      "$ref": "v1beta2.Capabilities",
 | 
			
		||||
      "description": "capabilities for container; cannot be updated"
 | 
			
		||||
      "description": "capabilities for container; cannot be updated; deprecated; See SecurityContext"
 | 
			
		||||
     },
 | 
			
		||||
     "command": {
 | 
			
		||||
      "type": "array",
 | 
			
		||||
@@ -7563,7 +7563,7 @@
 | 
			
		||||
     },
 | 
			
		||||
     "privileged": {
 | 
			
		||||
      "type": "boolean",
 | 
			
		||||
      "description": "whether or not the container is granted privileged status; defaults to false; cannot be updated"
 | 
			
		||||
      "description": "whether or not the container is granted privileged status; defaults to false; cannot be updated; deprecated; See SecurityContext"
 | 
			
		||||
     },
 | 
			
		||||
     "readinessProbe": {
 | 
			
		||||
      "$ref": "v1beta2.LivenessProbe",
 | 
			
		||||
@@ -7573,6 +7573,10 @@
 | 
			
		||||
      "$ref": "v1beta2.ResourceRequirements",
 | 
			
		||||
      "description": "Compute Resources required by this container; cannot be updated"
 | 
			
		||||
     },
 | 
			
		||||
     "securityContext": {
 | 
			
		||||
      "$ref": "v1beta2.SecurityContext",
 | 
			
		||||
      "description": "security options the pod should run with"
 | 
			
		||||
     },
 | 
			
		||||
     "terminationMessagePath": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "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"
 | 
			
		||||
@@ -7623,7 +7627,8 @@
 | 
			
		||||
      "description": "restart policy for all containers within the pod; one of RestartPolicyAlways, RestartPolicyOnFailure, RestartPolicyNever"
 | 
			
		||||
     },
 | 
			
		||||
     "terminationGracePeriodSeconds": {
 | 
			
		||||
      "$ref": "int64",
 | 
			
		||||
      "type": "integer",
 | 
			
		||||
      "format": "int64",
 | 
			
		||||
      "description": "optional duration in seconds the pod needs to terminate gracefully; may be decreased in delete request; value must be non-negative integer; the value zero indicates delete immediately; if this value is not set, the default grace period will be used instead; the grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal; set this value longer than the expected cleanup time for your process"
 | 
			
		||||
     },
 | 
			
		||||
     "uuid": {
 | 
			
		||||
@@ -7700,7 +7705,8 @@
 | 
			
		||||
      "description": "an optional prefix to use to generate a unique name; has the same validation rules as name; optional, and is applied only name if is not specified"
 | 
			
		||||
     },
 | 
			
		||||
     "gracePeriodSeconds": {
 | 
			
		||||
      "$ref": "int64",
 | 
			
		||||
      "type": "integer",
 | 
			
		||||
      "format": "int64",
 | 
			
		||||
      "description": "the duration in seconds to wait before deleting this object; defaults to a per object value if not specified; zero means delete immediately"
 | 
			
		||||
     },
 | 
			
		||||
     "id": {
 | 
			
		||||
@@ -9231,10 +9237,10 @@
 | 
			
		||||
   "v1beta2.PersistentVolumeSpec": {
 | 
			
		||||
    "id": "v1beta2.PersistentVolumeSpec",
 | 
			
		||||
    "required": [
 | 
			
		||||
     "glusterfs",
 | 
			
		||||
     "persistentDisk",
 | 
			
		||||
     "awsElasticBlockStore",
 | 
			
		||||
     "hostPath"
 | 
			
		||||
     "hostPath",
 | 
			
		||||
     "glusterfs"
 | 
			
		||||
    ],
 | 
			
		||||
    "properties": {
 | 
			
		||||
     "accessModes": {
 | 
			
		||||
@@ -9806,6 +9812,27 @@
 | 
			
		||||
    "id": "v1beta2.RestartPolicyOnFailure",
 | 
			
		||||
    "properties": {}
 | 
			
		||||
   },
 | 
			
		||||
   "v1beta2.SELinuxOptions": {
 | 
			
		||||
    "id": "v1beta2.SELinuxOptions",
 | 
			
		||||
    "properties": {
 | 
			
		||||
     "level": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "description": "the level label to apply to the container"
 | 
			
		||||
     },
 | 
			
		||||
     "role": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "description": "the role label to apply to the container"
 | 
			
		||||
     },
 | 
			
		||||
     "type": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "description": "the type label to apply to the container"
 | 
			
		||||
     },
 | 
			
		||||
     "user": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "description": "the user label to apply to the container"
 | 
			
		||||
     }
 | 
			
		||||
    }
 | 
			
		||||
   },
 | 
			
		||||
   "v1beta2.Secret": {
 | 
			
		||||
    "id": "v1beta2.Secret",
 | 
			
		||||
    "properties": {
 | 
			
		||||
@@ -9934,6 +9961,28 @@
 | 
			
		||||
     }
 | 
			
		||||
    }
 | 
			
		||||
   },
 | 
			
		||||
   "v1beta2.SecurityContext": {
 | 
			
		||||
    "id": "v1beta2.SecurityContext",
 | 
			
		||||
    "properties": {
 | 
			
		||||
     "capabilities": {
 | 
			
		||||
      "$ref": "v1beta2.Capabilities",
 | 
			
		||||
      "description": "the linux capabilites that should be added or removed"
 | 
			
		||||
     },
 | 
			
		||||
     "privileged": {
 | 
			
		||||
      "type": "boolean",
 | 
			
		||||
      "description": "run the container in privileged mode"
 | 
			
		||||
     },
 | 
			
		||||
     "runAsUser": {
 | 
			
		||||
      "type": "integer",
 | 
			
		||||
      "format": "int64",
 | 
			
		||||
      "description": "the user id that runs the first process in the container"
 | 
			
		||||
     },
 | 
			
		||||
     "seLinuxOptions": {
 | 
			
		||||
      "$ref": "v1beta2.SELinuxOptions",
 | 
			
		||||
      "description": "options that control the SELinux labels applied"
 | 
			
		||||
     }
 | 
			
		||||
    }
 | 
			
		||||
   },
 | 
			
		||||
   "v1beta2.Service": {
 | 
			
		||||
    "id": "v1beta2.Service",
 | 
			
		||||
    "required": [
 | 
			
		||||
 
 | 
			
		||||
@@ -8458,7 +8458,7 @@
 | 
			
		||||
     },
 | 
			
		||||
     "capabilities": {
 | 
			
		||||
      "$ref": "v1beta3.Capabilities",
 | 
			
		||||
      "description": "capabilities for container; cannot be updated"
 | 
			
		||||
      "description": "capabilities for container; cannot be updated; deprecated; See SecurityContext."
 | 
			
		||||
     },
 | 
			
		||||
     "command": {
 | 
			
		||||
      "type": "array",
 | 
			
		||||
@@ -8503,7 +8503,7 @@
 | 
			
		||||
     },
 | 
			
		||||
     "privileged": {
 | 
			
		||||
      "type": "boolean",
 | 
			
		||||
      "description": "whether or not the container is granted privileged status; defaults to false; cannot be updated"
 | 
			
		||||
      "description": "whether or not the container is granted privileged status; defaults to false; cannot be updated; deprecated;  See SecurityContext."
 | 
			
		||||
     },
 | 
			
		||||
     "readinessProbe": {
 | 
			
		||||
      "$ref": "v1beta3.Probe",
 | 
			
		||||
@@ -8513,6 +8513,10 @@
 | 
			
		||||
      "$ref": "v1beta3.ResourceRequirements",
 | 
			
		||||
      "description": "Compute Resources required by this container; cannot be updated"
 | 
			
		||||
     },
 | 
			
		||||
     "securityContext": {
 | 
			
		||||
      "$ref": "v1beta3.SecurityContext",
 | 
			
		||||
      "description": "security options the pod should run with"
 | 
			
		||||
     },
 | 
			
		||||
     "terminationMessagePath": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "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"
 | 
			
		||||
@@ -8689,7 +8693,8 @@
 | 
			
		||||
      "description": "version of the schema the object should have"
 | 
			
		||||
     },
 | 
			
		||||
     "gracePeriodSeconds": {
 | 
			
		||||
      "$ref": "int64",
 | 
			
		||||
      "type": "integer",
 | 
			
		||||
      "format": "int64",
 | 
			
		||||
      "description": "the duration in seconds to wait before deleting this object; defaults to a per object value if not specified; zero means delete immediately"
 | 
			
		||||
     },
 | 
			
		||||
     "kind": {
 | 
			
		||||
@@ -9888,7 +9893,8 @@
 | 
			
		||||
      "description": "restart policy for all containers within the pod; one of RestartPolicyAlways, RestartPolicyOnFailure, RestartPolicyNever"
 | 
			
		||||
     },
 | 
			
		||||
     "terminationGracePeriodSeconds": {
 | 
			
		||||
      "$ref": "int64",
 | 
			
		||||
      "type": "integer",
 | 
			
		||||
      "format": "int64",
 | 
			
		||||
      "description": "optional duration in seconds the pod needs to terminate gracefully; may be decreased in delete request; value must be non-negative integer; the value zero indicates delete immediately; if this value is not set, the default grace period will be used instead; the grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal; set this value longer than the expected cleanup time for your process"
 | 
			
		||||
     },
 | 
			
		||||
     "volumes": {
 | 
			
		||||
@@ -10200,6 +10206,27 @@
 | 
			
		||||
     }
 | 
			
		||||
    }
 | 
			
		||||
   },
 | 
			
		||||
   "v1beta3.SELinuxOptions": {
 | 
			
		||||
    "id": "v1beta3.SELinuxOptions",
 | 
			
		||||
    "properties": {
 | 
			
		||||
     "level": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "description": "the level label to apply to the container"
 | 
			
		||||
     },
 | 
			
		||||
     "role": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "description": "the role label to apply to the container"
 | 
			
		||||
     },
 | 
			
		||||
     "type": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "description": "the type label to apply to the container"
 | 
			
		||||
     },
 | 
			
		||||
     "user": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "description": "the user label to apply to the container"
 | 
			
		||||
     }
 | 
			
		||||
    }
 | 
			
		||||
   },
 | 
			
		||||
   "v1beta3.Secret": {
 | 
			
		||||
    "id": "v1beta3.Secret",
 | 
			
		||||
    "properties": {
 | 
			
		||||
@@ -10264,6 +10291,28 @@
 | 
			
		||||
     }
 | 
			
		||||
    }
 | 
			
		||||
   },
 | 
			
		||||
   "v1beta3.SecurityContext": {
 | 
			
		||||
    "id": "v1beta3.SecurityContext",
 | 
			
		||||
    "properties": {
 | 
			
		||||
     "capabilities": {
 | 
			
		||||
      "$ref": "v1beta3.Capabilities",
 | 
			
		||||
      "description": "the linux capabilites that should be added or removed"
 | 
			
		||||
     },
 | 
			
		||||
     "privileged": {
 | 
			
		||||
      "type": "boolean",
 | 
			
		||||
      "description": "run the container in privileged mode"
 | 
			
		||||
     },
 | 
			
		||||
     "runAsUser": {
 | 
			
		||||
      "type": "integer",
 | 
			
		||||
      "format": "int64",
 | 
			
		||||
      "description": "the user id that runs the first process in the container"
 | 
			
		||||
     },
 | 
			
		||||
     "seLinuxOptions": {
 | 
			
		||||
      "$ref": "v1beta3.SELinuxOptions",
 | 
			
		||||
      "description": "options that control the SELinux labels applied"
 | 
			
		||||
     }
 | 
			
		||||
    }
 | 
			
		||||
   },
 | 
			
		||||
   "v1beta3.Service": {
 | 
			
		||||
    "id": "v1beta3.Service",
 | 
			
		||||
    "properties": {
 | 
			
		||||
@@ -10480,15 +10529,15 @@
 | 
			
		||||
    "id": "v1beta3.Volume",
 | 
			
		||||
    "required": [
 | 
			
		||||
     "name",
 | 
			
		||||
     "gcePersistentDisk",
 | 
			
		||||
     "awsElasticBlockStore",
 | 
			
		||||
     "gitRepo",
 | 
			
		||||
     "secret",
 | 
			
		||||
     "nfs",
 | 
			
		||||
     "glusterfs",
 | 
			
		||||
     "iscsi",
 | 
			
		||||
     "hostPath",
 | 
			
		||||
     "emptyDir",
 | 
			
		||||
     "gcePersistentDisk",
 | 
			
		||||
     "iscsi"
 | 
			
		||||
     "secret",
 | 
			
		||||
     "glusterfs",
 | 
			
		||||
     "emptyDir"
 | 
			
		||||
    ],
 | 
			
		||||
    "properties": {
 | 
			
		||||
     "awsElasticBlockStore": {
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -117,7 +117,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(),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
@@ -1110,6 +1112,7 @@ func TestEtcdUpdateScheduled(t *testing.T) {
 | 
			
		||||
				{
 | 
			
		||||
					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,
 | 
			
		||||
@@ -1170,6 +1174,7 @@ func TestEtcdUpdateStatus(t *testing.T) {
 | 
			
		||||
			Containers: []api.Container{
 | 
			
		||||
				{
 | 
			
		||||
					Image:           "foo:v1",
 | 
			
		||||
					SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								pkg/securitycontext/doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								pkg/securitycontext/doc.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										45
									
								
								pkg/securitycontext/fake.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								pkg/securitycontext/fake.go
									
									
									
									
									
										Normal file
									
								
							@@ -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) {
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										97
									
								
								pkg/securitycontext/provider.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								pkg/securitycontext/provider.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										181
									
								
								pkg/securitycontext/provider_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								pkg/securitycontext/provider_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -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"),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								pkg/securitycontext/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								pkg/securitycontext/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -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"
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										43
									
								
								pkg/securitycontext/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								pkg/securitycontext/util.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										70
									
								
								plugin/pkg/admission/securitycontext/scdeny/admission.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								plugin/pkg/admission/securitycontext/scdeny/admission.go
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user