mirror of https://github.com/k3s-io/k3s
Revert "Revert "Security context - types, kubelet, admission""
parent
db6586bdab
commit
875e83a741
|
@ -7501,7 +7501,7 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"$ref": "v1beta1.Capabilities",
|
"$ref": "v1beta1.Capabilities",
|
||||||
"description": "capabilities for container; cannot be updated"
|
"description": "capabilities for container; cannot be updated; deprecated; See SecurityContext"
|
||||||
},
|
},
|
||||||
"command": {
|
"command": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
@ -7563,7 +7563,7 @@
|
||||||
},
|
},
|
||||||
"privileged": {
|
"privileged": {
|
||||||
"type": "boolean",
|
"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": {
|
"readinessProbe": {
|
||||||
"$ref": "v1beta1.LivenessProbe",
|
"$ref": "v1beta1.LivenessProbe",
|
||||||
|
@ -7573,6 +7573,10 @@
|
||||||
"$ref": "v1beta1.ResourceRequirements",
|
"$ref": "v1beta1.ResourceRequirements",
|
||||||
"description": "Compute Resources required by this container; cannot be updated"
|
"description": "Compute Resources required by this container; cannot be updated"
|
||||||
},
|
},
|
||||||
|
"securityContext": {
|
||||||
|
"$ref": "v1beta1.SecurityContext",
|
||||||
|
"description": "security options the pod should run with"
|
||||||
|
},
|
||||||
"terminationMessagePath": {
|
"terminationMessagePath": {
|
||||||
"type": "string",
|
"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"
|
"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"
|
"description": "restart policy for all containers within the pod; one of RestartPolicyAlways, RestartPolicyOnFailure, RestartPolicyNever"
|
||||||
},
|
},
|
||||||
"terminationGracePeriodSeconds": {
|
"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"
|
"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": {
|
"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"
|
"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": {
|
"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"
|
"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": {
|
"id": {
|
||||||
|
@ -9817,6 +9823,27 @@
|
||||||
"id": "v1beta1.RestartPolicyOnFailure",
|
"id": "v1beta1.RestartPolicyOnFailure",
|
||||||
"properties": {}
|
"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": {
|
"v1beta1.Secret": {
|
||||||
"id": "v1beta1.Secret",
|
"id": "v1beta1.Secret",
|
||||||
"properties": {
|
"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": {
|
"v1beta1.Service": {
|
||||||
"id": "v1beta1.Service",
|
"id": "v1beta1.Service",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -7501,7 +7501,7 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"$ref": "v1beta2.Capabilities",
|
"$ref": "v1beta2.Capabilities",
|
||||||
"description": "capabilities for container; cannot be updated"
|
"description": "capabilities for container; cannot be updated; deprecated; See SecurityContext"
|
||||||
},
|
},
|
||||||
"command": {
|
"command": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
@ -7563,7 +7563,7 @@
|
||||||
},
|
},
|
||||||
"privileged": {
|
"privileged": {
|
||||||
"type": "boolean",
|
"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": {
|
"readinessProbe": {
|
||||||
"$ref": "v1beta2.LivenessProbe",
|
"$ref": "v1beta2.LivenessProbe",
|
||||||
|
@ -7573,6 +7573,10 @@
|
||||||
"$ref": "v1beta2.ResourceRequirements",
|
"$ref": "v1beta2.ResourceRequirements",
|
||||||
"description": "Compute Resources required by this container; cannot be updated"
|
"description": "Compute Resources required by this container; cannot be updated"
|
||||||
},
|
},
|
||||||
|
"securityContext": {
|
||||||
|
"$ref": "v1beta2.SecurityContext",
|
||||||
|
"description": "security options the pod should run with"
|
||||||
|
},
|
||||||
"terminationMessagePath": {
|
"terminationMessagePath": {
|
||||||
"type": "string",
|
"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"
|
"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"
|
"description": "restart policy for all containers within the pod; one of RestartPolicyAlways, RestartPolicyOnFailure, RestartPolicyNever"
|
||||||
},
|
},
|
||||||
"terminationGracePeriodSeconds": {
|
"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"
|
"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": {
|
"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"
|
"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": {
|
"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"
|
"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": {
|
"id": {
|
||||||
|
@ -9231,10 +9237,10 @@
|
||||||
"v1beta2.PersistentVolumeSpec": {
|
"v1beta2.PersistentVolumeSpec": {
|
||||||
"id": "v1beta2.PersistentVolumeSpec",
|
"id": "v1beta2.PersistentVolumeSpec",
|
||||||
"required": [
|
"required": [
|
||||||
"glusterfs",
|
|
||||||
"persistentDisk",
|
"persistentDisk",
|
||||||
"awsElasticBlockStore",
|
"awsElasticBlockStore",
|
||||||
"hostPath"
|
"hostPath",
|
||||||
|
"glusterfs"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"accessModes": {
|
"accessModes": {
|
||||||
|
@ -9806,6 +9812,27 @@
|
||||||
"id": "v1beta2.RestartPolicyOnFailure",
|
"id": "v1beta2.RestartPolicyOnFailure",
|
||||||
"properties": {}
|
"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": {
|
"v1beta2.Secret": {
|
||||||
"id": "v1beta2.Secret",
|
"id": "v1beta2.Secret",
|
||||||
"properties": {
|
"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": {
|
"v1beta2.Service": {
|
||||||
"id": "v1beta2.Service",
|
"id": "v1beta2.Service",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -8458,7 +8458,7 @@
|
||||||
},
|
},
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"$ref": "v1beta3.Capabilities",
|
"$ref": "v1beta3.Capabilities",
|
||||||
"description": "capabilities for container; cannot be updated"
|
"description": "capabilities for container; cannot be updated; deprecated; See SecurityContext."
|
||||||
},
|
},
|
||||||
"command": {
|
"command": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
@ -8503,7 +8503,7 @@
|
||||||
},
|
},
|
||||||
"privileged": {
|
"privileged": {
|
||||||
"type": "boolean",
|
"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": {
|
"readinessProbe": {
|
||||||
"$ref": "v1beta3.Probe",
|
"$ref": "v1beta3.Probe",
|
||||||
|
@ -8513,6 +8513,10 @@
|
||||||
"$ref": "v1beta3.ResourceRequirements",
|
"$ref": "v1beta3.ResourceRequirements",
|
||||||
"description": "Compute Resources required by this container; cannot be updated"
|
"description": "Compute Resources required by this container; cannot be updated"
|
||||||
},
|
},
|
||||||
|
"securityContext": {
|
||||||
|
"$ref": "v1beta3.SecurityContext",
|
||||||
|
"description": "security options the pod should run with"
|
||||||
|
},
|
||||||
"terminationMessagePath": {
|
"terminationMessagePath": {
|
||||||
"type": "string",
|
"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"
|
"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"
|
"description": "version of the schema the object should have"
|
||||||
},
|
},
|
||||||
"gracePeriodSeconds": {
|
"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"
|
"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": {
|
"kind": {
|
||||||
|
@ -9888,7 +9893,8 @@
|
||||||
"description": "restart policy for all containers within the pod; one of RestartPolicyAlways, RestartPolicyOnFailure, RestartPolicyNever"
|
"description": "restart policy for all containers within the pod; one of RestartPolicyAlways, RestartPolicyOnFailure, RestartPolicyNever"
|
||||||
},
|
},
|
||||||
"terminationGracePeriodSeconds": {
|
"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"
|
"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": {
|
"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": {
|
"v1beta3.Secret": {
|
||||||
"id": "v1beta3.Secret",
|
"id": "v1beta3.Secret",
|
||||||
"properties": {
|
"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": {
|
"v1beta3.Service": {
|
||||||
"id": "v1beta3.Service",
|
"id": "v1beta3.Service",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -10480,15 +10529,15 @@
|
||||||
"id": "v1beta3.Volume",
|
"id": "v1beta3.Volume",
|
||||||
"required": [
|
"required": [
|
||||||
"name",
|
"name",
|
||||||
|
"gcePersistentDisk",
|
||||||
"awsElasticBlockStore",
|
"awsElasticBlockStore",
|
||||||
"gitRepo",
|
"gitRepo",
|
||||||
"secret",
|
|
||||||
"nfs",
|
"nfs",
|
||||||
"glusterfs",
|
"iscsi",
|
||||||
"hostPath",
|
"hostPath",
|
||||||
"emptyDir",
|
"secret",
|
||||||
"gcePersistentDisk",
|
"glusterfs",
|
||||||
"iscsi"
|
"emptyDir"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"awsElasticBlockStore": {
|
"awsElasticBlockStore": {
|
||||||
|
|
|
@ -72,4 +72,4 @@ DNS_DOMAIN="kubernetes.local"
|
||||||
DNS_REPLICAS=1
|
DNS_REPLICAS=1
|
||||||
|
|
||||||
# Admission Controllers to invoke prior to persisting objects in cluster
|
# 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}"
|
ENABLE_CLUSTER_MONITORING="${KUBE_ENABLE_CLUSTER_MONITORING:-true}"
|
||||||
|
|
||||||
# Admission Controllers to invoke prior to persisting objects in cluster
|
# 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
|
DNS_REPLICAS=1
|
||||||
|
|
||||||
# Admission Controllers to invoke prior to persisting objects in cluster
|
# 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_DOMAIN="kubernetes.local"
|
||||||
DNS_REPLICAS=1
|
DNS_REPLICAS=1
|
||||||
|
|
||||||
ADMISSION_CONTROL=NamespaceAutoProvision,LimitRanger,ResourceQuota
|
ADMISSION_CONTROL=NamespaceAutoProvision,LimitRanger,SecurityContextDeny,ResourceQuota
|
||||||
|
|
|
@ -50,7 +50,7 @@ MASTER_USER=vagrant
|
||||||
MASTER_PASSWD=vagrant
|
MASTER_PASSWD=vagrant
|
||||||
|
|
||||||
# Admission Controllers to invoke prior to persisting objects in cluster
|
# 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.
|
# Optional: Install node monitoring.
|
||||||
ENABLE_NODE_MONITORING=true
|
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/exists"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/namespace/lifecycle"
|
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/namespace/lifecycle"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/resourcequota"
|
_ "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
|
kube::etcd::start
|
||||||
|
|
||||||
# Admission Controllers to invoke prior to persisting objects in cluster
|
# 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
|
APISERVER_LOG=/tmp/kube-apiserver.log
|
||||||
sudo -E "${GO_OUT}/kube-apiserver" \
|
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()
|
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) {
|
func(e *api.Event, c fuzz.Continue) {
|
||||||
c.FuzzNoCustom(e) // fuzz self without calling this function again
|
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
|
// 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"`
|
Lifecycle *Lifecycle `json:"lifecycle,omitempty"`
|
||||||
// Required.
|
// Required.
|
||||||
TerminationMessagePath string `json:"terminationMessagePath,omitempty"`
|
TerminationMessagePath string `json:"terminationMessagePath,omitempty"`
|
||||||
// Optional: Default to false.
|
|
||||||
Privileged bool `json:"privileged,omitempty"`
|
|
||||||
// Required: Policy for pulling images for this container
|
// Required: Policy for pulling images for this container
|
||||||
ImagePullPolicy PullPolicy `json:"imagePullPolicy"`
|
ImagePullPolicy PullPolicy `json:"imagePullPolicy"`
|
||||||
// Optional: Capabilities for container.
|
// Optional: SecurityContext defines the security options the pod should be run with
|
||||||
Capabilities Capabilities `json:"capabilities,omitempty"`
|
SecurityContext *SecurityContext `json:"securityContext,omitempty" description:"security options the pod should run with"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler defines a specific action that should be taken
|
// Handler defines a specific action that should be taken
|
||||||
|
@ -1876,3 +1874,37 @@ type ComponentStatusList struct {
|
||||||
|
|
||||||
Items []ComponentStatus `json:"items"`
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||||
|
@ -237,9 +238,22 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
out.TerminationMessagePath = in.TerminationMessagePath
|
out.TerminationMessagePath = in.TerminationMessagePath
|
||||||
out.Privileged = in.Privileged
|
|
||||||
out.ImagePullPolicy = newer.PullPolicy(in.ImagePullPolicy)
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -297,11 +311,19 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
out.TerminationMessagePath = in.TerminationMessagePath
|
out.TerminationMessagePath = in.TerminationMessagePath
|
||||||
out.Privileged = in.Privileged
|
|
||||||
out.ImagePullPolicy = PullPolicy(in.ImagePullPolicy)
|
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
|
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
|
return nil
|
||||||
},
|
},
|
||||||
func(in *ContainerPort, out *newer.ContainerPort, s conversion.Scope) error {
|
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)
|
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 (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
)
|
)
|
||||||
|
@ -66,6 +68,7 @@ func init() {
|
||||||
if obj.TerminationMessagePath == "" {
|
if obj.TerminationMessagePath == "" {
|
||||||
obj.TerminationMessagePath = TerminationMessagePathDefault
|
obj.TerminationMessagePath = TerminationMessagePathDefault
|
||||||
}
|
}
|
||||||
|
defaultSecurityContext(obj)
|
||||||
},
|
},
|
||||||
func(obj *ServiceSpec) {
|
func(obj *ServiceSpec) {
|
||||||
if obj.SessionAffinity == "" {
|
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)
|
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"`
|
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
|
// 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"`
|
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.
|
// 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"`
|
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
|
// 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"`
|
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.
|
// Deprecated - see SecurityContext. Optional: Capabilities for container.
|
||||||
Capabilities Capabilities `json:"capabilities,omitempty" description:"capabilities for container; cannot be updated"`
|
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
|
// 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"`
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
@ -579,15 +580,20 @@ func init() {
|
||||||
if err := s.Convert(&in.TerminationMessagePath, &out.TerminationMessagePath, 0); err != nil {
|
if err := s.Convert(&in.TerminationMessagePath, &out.TerminationMessagePath, 0); err != nil {
|
||||||
return err
|
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 {
|
if err := s.Convert(&in.ImagePullPolicy, &out.ImagePullPolicy, 0); err != nil {
|
||||||
return err
|
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
|
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
|
return nil
|
||||||
},
|
},
|
||||||
// Internal API does not support CPU to be specified via an explicit field.
|
// 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 {
|
if err := s.Convert(&in.TerminationMessagePath, &out.TerminationMessagePath, 0); err != nil {
|
||||||
return err
|
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 {
|
if err := s.Convert(&in.ImagePullPolicy, &out.ImagePullPolicy, 0); err != nil {
|
||||||
return err
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -749,3 +749,63 @@ func TestSecretVolumeSourceConversion(t *testing.T) {
|
||||||
t.Errorf("Expected %v; got %v", given, got2)
|
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 == "" {
|
if obj.TerminationMessagePath == "" {
|
||||||
obj.TerminationMessagePath = TerminationMessagePathDefault
|
obj.TerminationMessagePath = TerminationMessagePathDefault
|
||||||
}
|
}
|
||||||
|
defaultSecurityContext(obj)
|
||||||
},
|
},
|
||||||
func(obj *RestartPolicy) {
|
func(obj *RestartPolicy) {
|
||||||
if util.AllPtrFieldsNil(obj) {
|
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)
|
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"`
|
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
|
// 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"`
|
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.
|
// 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"`
|
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
|
// 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"`
|
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.
|
// Deprecated - see SecurityContext. Optional: Capabilities for container.
|
||||||
Capabilities Capabilities `json:"capabilities,omitempty" description:"capabilities for container; cannot be updated"`
|
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
|
// 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"`
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
@ -357,15 +358,20 @@ func init() {
|
||||||
if err := s.Convert(&in.TerminationMessagePath, &out.TerminationMessagePath, 0); err != nil {
|
if err := s.Convert(&in.TerminationMessagePath, &out.TerminationMessagePath, 0); err != nil {
|
||||||
return err
|
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 {
|
if err := s.Convert(&in.ImagePullPolicy, &out.ImagePullPolicy, 0); err != nil {
|
||||||
return err
|
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
|
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
|
return nil
|
||||||
},
|
},
|
||||||
// Internal API does not support CPU to be specified via an explicit field.
|
// 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 {
|
if err := s.Convert(&in.TerminationMessagePath, &out.TerminationMessagePath, 0); err != nil {
|
||||||
return err
|
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 {
|
if err := s.Convert(&in.ImagePullPolicy, &out.ImagePullPolicy, 0); err != nil {
|
||||||
return err
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -564,3 +564,63 @@ func TestSecretVolumeSourceConversion(t *testing.T) {
|
||||||
t.Errorf("Expected %v; got %v", given, got2)
|
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 == "" {
|
if obj.TerminationMessagePath == "" {
|
||||||
obj.TerminationMessagePath = TerminationMessagePathDefault
|
obj.TerminationMessagePath = TerminationMessagePathDefault
|
||||||
}
|
}
|
||||||
|
defaultSecurityContext(obj)
|
||||||
},
|
},
|
||||||
func(obj *RestartPolicy) {
|
func(obj *RestartPolicy) {
|
||||||
if util.AllPtrFieldsNil(obj) {
|
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)
|
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"`
|
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
|
// 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"`
|
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.
|
// 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"`
|
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
|
// 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"`
|
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.
|
// Deprecated - see SecurityContext. Optional: Capabilities for container.
|
||||||
Capabilities Capabilities `json:"capabilities,omitempty" description:"capabilities for container; cannot be updated"`
|
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 (
|
const (
|
||||||
|
@ -1717,3 +1719,39 @@ type ComponentStatusList struct {
|
||||||
|
|
||||||
Items []ComponentStatus `json:"items" description:"list of component status objects"`
|
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)
|
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/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -66,6 +67,7 @@ func init() {
|
||||||
if obj.TerminationMessagePath == "" {
|
if obj.TerminationMessagePath == "" {
|
||||||
obj.TerminationMessagePath = TerminationMessagePathDefault
|
obj.TerminationMessagePath = TerminationMessagePathDefault
|
||||||
}
|
}
|
||||||
|
defaultSecurityContext(obj)
|
||||||
},
|
},
|
||||||
func(obj *ServiceSpec) {
|
func(obj *ServiceSpec) {
|
||||||
if obj.SessionAffinity == "" {
|
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)
|
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"`
|
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
|
// 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"`
|
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.
|
// 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"`
|
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
|
// 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"`
|
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.
|
// Deprecated - see SecurityContext. Optional: Capabilities for container.
|
||||||
Capabilities Capabilities `json:"capabilities,omitempty" description:"capabilities for container; cannot be updated"`
|
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
|
// 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"`
|
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{}
|
allNames := util.StringSet{}
|
||||||
for i, ctr := range containers {
|
for i, ctr := range containers {
|
||||||
cErrs := errs.ValidationErrorList{}
|
cErrs := errs.ValidationErrorList{}
|
||||||
capabilities := capabilities.Get()
|
|
||||||
if len(ctr.Name) == 0 {
|
if len(ctr.Name) == 0 {
|
||||||
cErrs = append(cErrs, errs.NewFieldRequired("name"))
|
cErrs = append(cErrs, errs.NewFieldRequired("name"))
|
||||||
} else if !util.IsDNS1123Label(ctr.Name) {
|
} else if !util.IsDNS1123Label(ctr.Name) {
|
||||||
cErrs = append(cErrs, errs.NewFieldInvalid("name", ctr.Name, dns1123LabelErrorMsg))
|
cErrs = append(cErrs, errs.NewFieldInvalid("name", ctr.Name, dns1123LabelErrorMsg))
|
||||||
} else if allNames.Has(ctr.Name) {
|
} else if allNames.Has(ctr.Name) {
|
||||||
cErrs = append(cErrs, errs.NewFieldDuplicate("name", 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 {
|
} else {
|
||||||
allNames.Insert(ctr.Name)
|
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, validateVolumeMounts(ctr.VolumeMounts, volumes).Prefix("volumeMounts")...)
|
||||||
cErrs = append(cErrs, validatePullPolicy(&ctr).Prefix("pullPolicy")...)
|
cErrs = append(cErrs, validatePullPolicy(&ctr).Prefix("pullPolicy")...)
|
||||||
cErrs = append(cErrs, ValidateResourceRequirements(&ctr.Resources).Prefix("resources")...)
|
cErrs = append(cErrs, ValidateResourceRequirements(&ctr.Resources).Prefix("resources")...)
|
||||||
|
cErrs = append(cErrs, ValidateSecurityContext(ctr.SecurityContext).Prefix("securityContext")...)
|
||||||
allErrs = append(allErrs, cErrs.PrefixIndex(i)...)
|
allErrs = append(allErrs, cErrs.PrefixIndex(i)...)
|
||||||
}
|
}
|
||||||
// Check for colliding ports across all containers.
|
// 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")...)
|
allErrs = append(allErrs, validateEndpointSubsets(newEndpoints.Subsets).Prefix("subsets")...)
|
||||||
return allErrs
|
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",
|
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 {
|
if errs := validateContainers(successCase, volumes); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
|
@ -1015,7 +1015,7 @@ func TestValidateContainers(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"privilege disabled": {
|
"privilege disabled": {
|
||||||
{Name: "abc", Image: "image", Privileged: true},
|
{Name: "abc", Image: "image", SecurityContext: fakeValidSecurityContext(true)},
|
||||||
},
|
},
|
||||||
"invalid compute resource": {
|
"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/client/testclient"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||||
|
@ -112,6 +113,7 @@ func newReplicationController(replicas int) *api.ReplicationController {
|
||||||
Image: "foo/bar",
|
Image: "foo/bar",
|
||||||
TerminationMessagePath: api.TerminationMessagePathDefault,
|
TerminationMessagePath: api.TerminationMessagePathDefault,
|
||||||
ImagePullPolicy: api.PullIfNotPresent,
|
ImagePullPolicy: api.PullIfNotPresent,
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
RestartPolicy: api.RestartPolicyAlways,
|
RestartPolicy: api.RestartPolicyAlways,
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||||
|
|
||||||
"github.com/ghodss/yaml"
|
"github.com/ghodss/yaml"
|
||||||
)
|
)
|
||||||
|
@ -46,6 +47,7 @@ func TestDecodeSinglePod(t *testing.T) {
|
||||||
Image: "test/image",
|
Image: "test/image",
|
||||||
ImagePullPolicy: "IfNotPresent",
|
ImagePullPolicy: "IfNotPresent",
|
||||||
TerminationMessagePath: "/dev/termination-log",
|
TerminationMessagePath: "/dev/termination-log",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -108,6 +110,7 @@ func TestDecodePodList(t *testing.T) {
|
||||||
Image: "test/image",
|
Image: "test/image",
|
||||||
ImagePullPolicy: "IfNotPresent",
|
ImagePullPolicy: "IfNotPresent",
|
||||||
TerminationMessagePath: "/dev/termination-log",
|
TerminationMessagePath: "/dev/termination-log",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -62,7 +63,14 @@ func CreateValidPod(name, namespace, source string) *api.Pod {
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
RestartPolicy: api.RestartPolicyAlways,
|
RestartPolicy: api.RestartPolicyAlways,
|
||||||
DNSPolicy: api.DNSClusterFirst,
|
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/api/validation"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -105,7 +106,8 @@ func TestReadContainerManifestFromFile(t *testing.T) {
|
||||||
Name: "image",
|
Name: "image",
|
||||||
Image: "test/image",
|
Image: "test/image",
|
||||||
TerminationMessagePath: "/dev/termination-log",
|
TerminationMessagePath: "/dev/termination-log",
|
||||||
ImagePullPolicy: "Always"}},
|
ImagePullPolicy: "Always",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -131,7 +133,8 @@ func TestReadContainerManifestFromFile(t *testing.T) {
|
||||||
Name: "image",
|
Name: "image",
|
||||||
Image: "test/image",
|
Image: "test/image",
|
||||||
TerminationMessagePath: "/dev/termination-log",
|
TerminationMessagePath: "/dev/termination-log",
|
||||||
ImagePullPolicy: "Always"}},
|
ImagePullPolicy: "Always",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -182,7 +185,7 @@ func TestReadPodsFromFile(t *testing.T) {
|
||||||
Namespace: "mynamespace",
|
Namespace: "mynamespace",
|
||||||
},
|
},
|
||||||
Spec: api.PodSpec{
|
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{
|
expected: CreatePodUpdate(kubelet.SET, kubelet.FileSource, &api.Pod{
|
||||||
|
@ -200,7 +203,8 @@ func TestReadPodsFromFile(t *testing.T) {
|
||||||
Name: "image",
|
Name: "image",
|
||||||
Image: "test/image",
|
Image: "test/image",
|
||||||
TerminationMessagePath: "/dev/termination-log",
|
TerminationMessagePath: "/dev/termination-log",
|
||||||
ImagePullPolicy: "IfNotPresent"}},
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -216,7 +220,7 @@ func TestReadPodsFromFile(t *testing.T) {
|
||||||
UID: "12345",
|
UID: "12345",
|
||||||
},
|
},
|
||||||
Spec: api.PodSpec{
|
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{
|
expected: CreatePodUpdate(kubelet.SET, kubelet.FileSource, &api.Pod{
|
||||||
|
@ -234,7 +238,8 @@ func TestReadPodsFromFile(t *testing.T) {
|
||||||
Name: "image",
|
Name: "image",
|
||||||
Image: "test/image",
|
Image: "test/image",
|
||||||
TerminationMessagePath: "/dev/termination-log",
|
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/api/validation"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
|
||||||
)
|
)
|
||||||
|
@ -151,7 +152,8 @@ func TestExtractManifestFromHTTP(t *testing.T) {
|
||||||
Name: "1",
|
Name: "1",
|
||||||
Image: "foo",
|
Image: "foo",
|
||||||
TerminationMessagePath: "/dev/termination-log",
|
TerminationMessagePath: "/dev/termination-log",
|
||||||
ImagePullPolicy: "Always"}},
|
ImagePullPolicy: "Always",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -177,7 +179,8 @@ func TestExtractManifestFromHTTP(t *testing.T) {
|
||||||
Name: "ctr",
|
Name: "ctr",
|
||||||
Image: "image",
|
Image: "image",
|
||||||
TerminationMessagePath: "/dev/termination-log",
|
TerminationMessagePath: "/dev/termination-log",
|
||||||
ImagePullPolicy: "IfNotPresent"}},
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -203,7 +206,8 @@ func TestExtractManifestFromHTTP(t *testing.T) {
|
||||||
Name: "1",
|
Name: "1",
|
||||||
Image: "foo",
|
Image: "foo",
|
||||||
TerminationMessagePath: "/dev/termination-log",
|
TerminationMessagePath: "/dev/termination-log",
|
||||||
ImagePullPolicy: "Always"}},
|
ImagePullPolicy: "Always",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -233,7 +237,8 @@ func TestExtractManifestFromHTTP(t *testing.T) {
|
||||||
Name: "1",
|
Name: "1",
|
||||||
Image: "foo",
|
Image: "foo",
|
||||||
TerminationMessagePath: "/dev/termination-log",
|
TerminationMessagePath: "/dev/termination-log",
|
||||||
ImagePullPolicy: "Always"}},
|
ImagePullPolicy: "Always",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&api.Pod{
|
&api.Pod{
|
||||||
|
@ -252,7 +257,8 @@ func TestExtractManifestFromHTTP(t *testing.T) {
|
||||||
Name: "1",
|
Name: "1",
|
||||||
Image: "foo",
|
Image: "foo",
|
||||||
TerminationMessagePath: "/dev/termination-log",
|
TerminationMessagePath: "/dev/termination-log",
|
||||||
ImagePullPolicy: "IfNotPresent"}},
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -344,7 +350,8 @@ func TestExtractPodsFromHTTP(t *testing.T) {
|
||||||
Name: "1",
|
Name: "1",
|
||||||
Image: "foo",
|
Image: "foo",
|
||||||
TerminationMessagePath: "/dev/termination-log",
|
TerminationMessagePath: "/dev/termination-log",
|
||||||
ImagePullPolicy: "Always"}},
|
ImagePullPolicy: "Always",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -396,7 +403,8 @@ func TestExtractPodsFromHTTP(t *testing.T) {
|
||||||
Name: "1",
|
Name: "1",
|
||||||
Image: "foo",
|
Image: "foo",
|
||||||
TerminationMessagePath: "/dev/termination-log",
|
TerminationMessagePath: "/dev/termination-log",
|
||||||
ImagePullPolicy: "Always"}},
|
ImagePullPolicy: "Always",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&api.Pod{
|
&api.Pod{
|
||||||
|
@ -415,7 +423,8 @@ func TestExtractPodsFromHTTP(t *testing.T) {
|
||||||
Name: "2",
|
Name: "2",
|
||||||
Image: "bar",
|
Image: "bar",
|
||||||
TerminationMessagePath: "/dev/termination-log",
|
TerminationMessagePath: "/dev/termination-log",
|
||||||
ImagePullPolicy: "IfNotPresent"}},
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
|
@ -38,6 +38,7 @@ import (
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/prober"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/prober"
|
||||||
kubeletTypes "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/types"
|
kubeletTypes "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/types"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/probe"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/probe"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
docker "github.com/fsouza/go-dockerclient"
|
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)
|
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)
|
dockerContainer, err := dm.client.CreateContainer(dockerOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ref != 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 && securitycontext.HasPrivilegedRequest(container) {
|
||||||
if capabilities.Get().AllowPrivileged {
|
|
||||||
privileged = container.Privileged
|
|
||||||
} else if container.Privileged {
|
|
||||||
return "", fmt.Errorf("container requested privileged mode, but it is disallowed globally.")
|
return "", fmt.Errorf("container requested privileged mode, but it is disallowed globally.")
|
||||||
}
|
}
|
||||||
|
|
||||||
capAdd, capDrop := makeCapabilites(container.Capabilities.Add, container.Capabilities.Drop)
|
|
||||||
hc := &docker.HostConfig{
|
hc := &docker.HostConfig{
|
||||||
PortBindings: portBindings,
|
PortBindings: portBindings,
|
||||||
Binds: opts.Binds,
|
Binds: opts.Binds,
|
||||||
NetworkMode: opts.NetMode,
|
NetworkMode: opts.NetMode,
|
||||||
IpcMode: opts.IpcMode,
|
IpcMode: opts.IpcMode,
|
||||||
Privileged: privileged,
|
|
||||||
CapAdd: capAdd,
|
|
||||||
CapDrop: capDrop,
|
|
||||||
}
|
}
|
||||||
if len(opts.DNS) > 0 {
|
if len(opts.DNS) > 0 {
|
||||||
hc.DNS = opts.DNS
|
hc.DNS = opts.DNS
|
||||||
|
@ -580,6 +576,7 @@ func (dm *DockerManager) runContainer(pod *api.Pod, container *api.Container, op
|
||||||
if len(opts.CgroupParent) > 0 {
|
if len(opts.CgroupParent) > 0 {
|
||||||
hc.CgroupParent = opts.CgroupParent
|
hc.CgroupParent = opts.CgroupParent
|
||||||
}
|
}
|
||||||
|
securityContextProvider.ModifyHostConfig(pod, container, hc)
|
||||||
|
|
||||||
if err = dm.client.StartContainer(dockerContainer.ID, hc); err != nil {
|
if err = dm.client.StartContainer(dockerContainer.ID, hc); err != nil {
|
||||||
if ref != nil {
|
if ref != nil {
|
||||||
|
@ -637,20 +634,6 @@ func makePortsAndBindings(container *api.Container) (map[docker.Port]struct{}, m
|
||||||
return exposedPorts, portBindings
|
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
|
// A helper function to get the KubeletContainerName and hash from a docker
|
||||||
// container.
|
// container.
|
||||||
func getDockerContainerNameInfo(c *docker.APIContainers) (*KubeletContainerName, uint64, error) {
|
func getDockerContainerNameInfo(c *docker.APIContainers) (*KubeletContainerName, uint64, error) {
|
||||||
|
|
|
@ -38,6 +38,7 @@ import (
|
||||||
kubecontainer "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/container"
|
kubecontainer "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/container"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/prober"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/prober"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/probe"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/probe"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
|
"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.
|
// 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 {
|
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{}
|
app.Isolators = []appctypes.Isolator{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retained capabilities/privileged.
|
// Retained capabilities/privileged.
|
||||||
privileged := false
|
privileged := false
|
||||||
if capabilities.Get().AllowPrivileged {
|
if !capabilities.Get().AllowPrivileged && securitycontext.HasPrivilegedRequest(c) {
|
||||||
privileged = c.Privileged
|
return fmt.Errorf("container requested privileged mode, but it is disallowed globally.")
|
||||||
} else if c.Privileged {
|
} else {
|
||||||
return fmt.Errorf("privileged is disallowed globally")
|
if c.SecurityContext != nil && c.SecurityContext.Privileged != nil {
|
||||||
|
privileged = *c.SecurityContext.Privileged
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var addCaps string
|
var addCaps string
|
||||||
if privileged {
|
if privileged {
|
||||||
addCaps = getAllCapabilities()
|
addCaps = getAllCapabilities()
|
||||||
} else {
|
} else {
|
||||||
addCaps = getCapabilities(c.Capabilities.Add)
|
if hasCapRequests {
|
||||||
|
addCaps = getCapabilities(c.SecurityContext.Capabilities.Add)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(addCaps) > 0 {
|
if len(addCaps) > 0 {
|
||||||
// TODO(yifan): Replace with constructor, see:
|
// TODO(yifan): Replace with constructor, see:
|
||||||
|
@ -216,7 +223,10 @@ func setIsolators(app *appctypes.App, c *api.Container) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removed capabilities.
|
// Removed capabilities.
|
||||||
dropCaps := getCapabilities(c.Capabilities.Drop)
|
var dropCaps string
|
||||||
|
if hasCapRequests {
|
||||||
|
dropCaps = getCapabilities(c.SecurityContext.Capabilities.Drop)
|
||||||
|
}
|
||||||
if len(dropCaps) > 0 {
|
if len(dropCaps) > 0 {
|
||||||
// TODO(yifan): Replace with constructor, see:
|
// TODO(yifan): Replace with constructor, see:
|
||||||
// https://github.com/appc/spec/issues/268
|
// https://github.com/appc/spec/issues/268
|
||||||
|
|
|
@ -32,6 +32,7 @@ import (
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools/etcdtest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools/etcdtest"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
|
@ -68,6 +69,7 @@ func validNewPod() *api.Pod {
|
||||||
ImagePullPolicy: api.PullAlways,
|
ImagePullPolicy: api.PullAlways,
|
||||||
|
|
||||||
TerminationMessagePath: api.TerminationMessagePathDefault,
|
TerminationMessagePath: api.TerminationMessagePathDefault,
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1108,8 +1110,9 @@ func TestEtcdUpdateScheduled(t *testing.T) {
|
||||||
Host: "machine",
|
Host: "machine",
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Image: "foo:v1",
|
Image: "foo:v1",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1131,6 +1134,7 @@ func TestEtcdUpdateScheduled(t *testing.T) {
|
||||||
Image: "foo:v2",
|
Image: "foo:v2",
|
||||||
ImagePullPolicy: api.PullIfNotPresent,
|
ImagePullPolicy: api.PullIfNotPresent,
|
||||||
TerminationMessagePath: api.TerminationMessagePathDefault,
|
TerminationMessagePath: api.TerminationMessagePathDefault,
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
RestartPolicy: api.RestartPolicyAlways,
|
RestartPolicy: api.RestartPolicyAlways,
|
||||||
|
@ -1169,7 +1173,8 @@ func TestEtcdUpdateStatus(t *testing.T) {
|
||||||
Host: "machine",
|
Host: "machine",
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Image: "foo:v1",
|
Image: "foo:v1",
|
||||||
|
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package securitycontext contains security context api implementations
|
||||||
|
package securitycontext
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package securitycontext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
|
||||||
|
docker "github.com/fsouza/go-dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidSecurityContextWithContainerDefaults creates a valid security context provider based on
|
||||||
|
// empty container defaults. Used for testing.
|
||||||
|
func ValidSecurityContextWithContainerDefaults() *api.SecurityContext {
|
||||||
|
priv := false
|
||||||
|
return &api.SecurityContext{
|
||||||
|
Capabilities: &api.Capabilities{},
|
||||||
|
Privileged: &priv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFakeSecurityContextProvider creates a new, no-op security context provider.
|
||||||
|
func NewFakeSecurityContextProvider() SecurityContextProvider {
|
||||||
|
return FakeSecurityContextProvider{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FakeSecurityContextProvider struct{}
|
||||||
|
|
||||||
|
func (p FakeSecurityContextProvider) ModifyContainerConfig(pod *api.Pod, container *api.Container, config *docker.Config) {
|
||||||
|
}
|
||||||
|
func (p FakeSecurityContextProvider) ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *docker.HostConfig) {
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package securitycontext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
|
||||||
|
docker "github.com/fsouza/go-dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewSimpleSecurityContextProvider creates a new SimpleSecurityContextProvider.
|
||||||
|
func NewSimpleSecurityContextProvider() SecurityContextProvider {
|
||||||
|
return SimpleSecurityContextProvider{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleSecurityContextProvider is the default implementation of a SecurityContextProvider.
|
||||||
|
type SimpleSecurityContextProvider struct{}
|
||||||
|
|
||||||
|
// ModifyContainerConfig is called before the Docker createContainer call.
|
||||||
|
// The security context provider can make changes to the Config with which
|
||||||
|
// the container is created.
|
||||||
|
func (p SimpleSecurityContextProvider) ModifyContainerConfig(pod *api.Pod, container *api.Container, config *docker.Config) {
|
||||||
|
if container.SecurityContext == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if container.SecurityContext.RunAsUser != nil {
|
||||||
|
config.User = strconv.FormatInt(*container.SecurityContext.RunAsUser, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModifyHostConfig is called before the Docker runContainer call.
|
||||||
|
// The security context provider can make changes to the HostConfig, affecting
|
||||||
|
// security options, whether the container is privileged, volume binds, etc.
|
||||||
|
// An error is returned if it's not possible to secure the container as requested
|
||||||
|
// with a security context.
|
||||||
|
func (p SimpleSecurityContextProvider) ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *docker.HostConfig) {
|
||||||
|
if container.SecurityContext == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if container.SecurityContext.Privileged != nil {
|
||||||
|
hostConfig.Privileged = *container.SecurityContext.Privileged
|
||||||
|
}
|
||||||
|
|
||||||
|
if container.SecurityContext.Capabilities != nil {
|
||||||
|
add, drop := makeCapabilites(container.SecurityContext.Capabilities.Add, container.SecurityContext.Capabilities.Drop)
|
||||||
|
hostConfig.CapAdd = add
|
||||||
|
hostConfig.CapDrop = drop
|
||||||
|
}
|
||||||
|
|
||||||
|
if container.SecurityContext.SELinuxOptions != nil {
|
||||||
|
hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelUser, container.SecurityContext.SELinuxOptions.User)
|
||||||
|
hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelRole, container.SecurityContext.SELinuxOptions.Role)
|
||||||
|
hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelType, container.SecurityContext.SELinuxOptions.Type)
|
||||||
|
hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelLevel, container.SecurityContext.SELinuxOptions.Level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// modifySecurityOption adds the security option of name to the config array with value in the form
|
||||||
|
// of name:value
|
||||||
|
func modifySecurityOption(config []string, name, value string) []string {
|
||||||
|
if len(value) > 0 {
|
||||||
|
config = append(config, fmt.Sprintf("%s:%s", name, value))
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeCapabilites creates string slices from CapabilityType slices
|
||||||
|
func makeCapabilites(capAdd []api.CapabilityType, capDrop []api.CapabilityType) ([]string, []string) {
|
||||||
|
var (
|
||||||
|
addCaps []string
|
||||||
|
dropCaps []string
|
||||||
|
)
|
||||||
|
for _, cap := range capAdd {
|
||||||
|
addCaps = append(addCaps, string(cap))
|
||||||
|
}
|
||||||
|
for _, cap := range capDrop {
|
||||||
|
dropCaps = append(dropCaps, string(cap))
|
||||||
|
}
|
||||||
|
return addCaps, dropCaps
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package securitycontext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
|
||||||
|
docker "github.com/fsouza/go-dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestModifyContainerConfig(t *testing.T) {
|
||||||
|
var uid int64 = 1
|
||||||
|
testCases := map[string]struct {
|
||||||
|
securityContext *api.SecurityContext
|
||||||
|
expected *docker.Config
|
||||||
|
}{
|
||||||
|
"modify config, value set for user": {
|
||||||
|
securityContext: &api.SecurityContext{
|
||||||
|
RunAsUser: &uid,
|
||||||
|
},
|
||||||
|
expected: &docker.Config{
|
||||||
|
User: strconv.FormatInt(uid, 10),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"modify config, nil user value": {
|
||||||
|
securityContext: &api.SecurityContext{},
|
||||||
|
expected: &docker.Config{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := NewSimpleSecurityContextProvider()
|
||||||
|
dummyContainer := &api.Container{}
|
||||||
|
for k, v := range testCases {
|
||||||
|
dummyContainer.SecurityContext = v.securityContext
|
||||||
|
dockerCfg := &docker.Config{}
|
||||||
|
provider.ModifyContainerConfig(nil, dummyContainer, dockerCfg)
|
||||||
|
if !reflect.DeepEqual(v.expected, dockerCfg) {
|
||||||
|
t.Errorf("unexpected modification of docker config for %s. Expected: %#v Got: %#v", k, v.expected, dockerCfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModifyHostConfig(t *testing.T) {
|
||||||
|
nilPrivSC := fullValidSecurityContext()
|
||||||
|
nilPrivSC.Privileged = nil
|
||||||
|
nilPrivHC := fullValidHostConfig()
|
||||||
|
nilPrivHC.Privileged = false
|
||||||
|
|
||||||
|
nilCapsSC := fullValidSecurityContext()
|
||||||
|
nilCapsSC.Capabilities = nil
|
||||||
|
nilCapsHC := fullValidHostConfig()
|
||||||
|
nilCapsHC.CapAdd = *new([]string)
|
||||||
|
nilCapsHC.CapDrop = *new([]string)
|
||||||
|
|
||||||
|
nilSELinuxSC := fullValidSecurityContext()
|
||||||
|
nilSELinuxSC.SELinuxOptions = nil
|
||||||
|
nilSELinuxHC := fullValidHostConfig()
|
||||||
|
nilSELinuxHC.SecurityOpt = *new([]string)
|
||||||
|
|
||||||
|
seLinuxLabelsSC := fullValidSecurityContext()
|
||||||
|
seLinuxLabelsHC := fullValidHostConfig()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
securityContext *api.SecurityContext
|
||||||
|
expected *docker.HostConfig
|
||||||
|
}{
|
||||||
|
"full settings": {
|
||||||
|
securityContext: fullValidSecurityContext(),
|
||||||
|
expected: fullValidHostConfig(),
|
||||||
|
},
|
||||||
|
"nil privileged": {
|
||||||
|
securityContext: nilPrivSC,
|
||||||
|
expected: nilPrivHC,
|
||||||
|
},
|
||||||
|
"nil capabilities": {
|
||||||
|
securityContext: nilCapsSC,
|
||||||
|
expected: nilCapsHC,
|
||||||
|
},
|
||||||
|
"nil selinux options": {
|
||||||
|
securityContext: nilSELinuxSC,
|
||||||
|
expected: nilSELinuxHC,
|
||||||
|
},
|
||||||
|
"selinux labels": {
|
||||||
|
securityContext: seLinuxLabelsSC,
|
||||||
|
expected: seLinuxLabelsHC,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := NewSimpleSecurityContextProvider()
|
||||||
|
dummyContainer := &api.Container{}
|
||||||
|
for k, v := range testCases {
|
||||||
|
dummyContainer.SecurityContext = v.securityContext
|
||||||
|
dockerCfg := &docker.HostConfig{}
|
||||||
|
provider.ModifyHostConfig(nil, dummyContainer, dockerCfg)
|
||||||
|
if !reflect.DeepEqual(v.expected, dockerCfg) {
|
||||||
|
t.Errorf("unexpected modification of host config for %s. Expected: %#v Got: %#v", k, v.expected, dockerCfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModifySecurityOption(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
config []string
|
||||||
|
optName string
|
||||||
|
optVal string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Empty val",
|
||||||
|
config: []string{"a:b", "c:d"},
|
||||||
|
optName: "optA",
|
||||||
|
optVal: "",
|
||||||
|
expected: []string{"a:b", "c:d"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid",
|
||||||
|
config: []string{"a:b", "c:d"},
|
||||||
|
optName: "e",
|
||||||
|
optVal: "f",
|
||||||
|
expected: []string{"a:b", "c:d", "e:f"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
actual := modifySecurityOption(tc.config, tc.optName, tc.optVal)
|
||||||
|
if !reflect.DeepEqual(tc.expected, actual) {
|
||||||
|
t.Errorf("Failed to apply options correctly for tc: %S. Expected: %v but got %v", tc.name, tc.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fullValidSecurityContext() *api.SecurityContext {
|
||||||
|
priv := true
|
||||||
|
return &api.SecurityContext{
|
||||||
|
Privileged: &priv,
|
||||||
|
Capabilities: &api.Capabilities{
|
||||||
|
Add: []api.CapabilityType{"addCapA", "addCapB"},
|
||||||
|
Drop: []api.CapabilityType{"dropCapA", "dropCapB"},
|
||||||
|
},
|
||||||
|
SELinuxOptions: &api.SELinuxOptions{
|
||||||
|
User: "user",
|
||||||
|
Role: "role",
|
||||||
|
Type: "type",
|
||||||
|
Level: "level",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fullValidHostConfig() *docker.HostConfig {
|
||||||
|
return &docker.HostConfig{
|
||||||
|
Privileged: true,
|
||||||
|
CapAdd: []string{"addCapA", "addCapB"},
|
||||||
|
CapDrop: []string{"dropCapA", "dropCapB"},
|
||||||
|
SecurityOpt: []string{
|
||||||
|
fmt.Sprintf("%s:%s", dockerLabelUser, "user"),
|
||||||
|
fmt.Sprintf("%s:%s", dockerLabelRole, "role"),
|
||||||
|
fmt.Sprintf("%s:%s", dockerLabelType, "type"),
|
||||||
|
fmt.Sprintf("%s:%s", dockerLabelLevel, "level"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package securitycontext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
|
||||||
|
docker "github.com/fsouza/go-dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SecurityContextProvider interface {
|
||||||
|
// ModifyContainerConfig is called before the Docker createContainer call.
|
||||||
|
// The security context provider can make changes to the Config with which
|
||||||
|
// the container is created.
|
||||||
|
ModifyContainerConfig(pod *api.Pod, container *api.Container, config *docker.Config)
|
||||||
|
|
||||||
|
// ModifyHostConfig is called before the Docker runContainer call.
|
||||||
|
// The security context provider can make changes to the HostConfig, affecting
|
||||||
|
// security options, whether the container is privileged, volume binds, etc.
|
||||||
|
// An error is returned if it's not possible to secure the container as requested
|
||||||
|
// with a security context.
|
||||||
|
ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *docker.HostConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
dockerLabelUser string = "label:user"
|
||||||
|
dockerLabelRole string = "label:role"
|
||||||
|
dockerLabelType string = "label:type"
|
||||||
|
dockerLabelLevel string = "label:level"
|
||||||
|
dockerLabelDisable string = "label:disable"
|
||||||
|
)
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package securitycontext
|
||||||
|
|
||||||
|
import "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
|
||||||
|
// HasPrivilegedRequest returns the value of SecurityContext.Privileged, taking into account
|
||||||
|
// the possibility of nils
|
||||||
|
func HasPrivilegedRequest(container *api.Container) bool {
|
||||||
|
if container.SecurityContext == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if container.SecurityContext.Privileged == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *container.SecurityContext.Privileged
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasCapabilitiesRequest returns true if Adds or Drops are defined in the security context
|
||||||
|
// capabilities, taking into account nils
|
||||||
|
func HasCapabilitiesRequest(container *api.Container) bool {
|
||||||
|
if container.SecurityContext == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if container.SecurityContext.Capabilities == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return len(container.SecurityContext.Capabilities.Add) > 0 || len(container.SecurityContext.Capabilities.Drop) > 0
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package scdeny
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
admission.RegisterPlugin("SecurityContextDeny", func(client client.Interface, config io.Reader) (admission.Interface, error) {
|
||||||
|
return NewSecurityContextDeny(client), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// plugin contains the client used by the SecurityContextDeny admission controller
|
||||||
|
type plugin struct {
|
||||||
|
client client.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSecurityContextDeny creates a new instance of the SecurityContextDeny admission controller
|
||||||
|
func NewSecurityContextDeny(client client.Interface) admission.Interface {
|
||||||
|
return &plugin{client}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admit will deny any SecurityContext that defines options that were not previously available in the api.Container
|
||||||
|
// struct (Capabilities and Privileged)
|
||||||
|
func (p *plugin) Admit(a admission.Attributes) (err error) {
|
||||||
|
if a.GetOperation() == "DELETE" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if a.GetResource() != string(api.ResourcePods) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pod, ok := a.GetObject().(*api.Pod)
|
||||||
|
if !ok {
|
||||||
|
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||||
|
}
|
||||||
|
for _, v := range pod.Spec.Containers {
|
||||||
|
if v.SecurityContext != nil {
|
||||||
|
if v.SecurityContext.SELinuxOptions != nil {
|
||||||
|
return apierrors.NewForbidden(a.GetResource(), pod.Name, fmt.Errorf("SecurityContext.SELinuxOptions is forbidden"))
|
||||||
|
}
|
||||||
|
if v.SecurityContext.RunAsUser != nil {
|
||||||
|
return apierrors.NewForbidden(a.GetResource(), pod.Name, fmt.Errorf("SecurityContext.RunAsUser is forbidden"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package scdeny
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ensures the SecurityContext is denied if it defines anything more than Caps or Privileged
|
||||||
|
func TestAdmission(t *testing.T) {
|
||||||
|
handler := NewSecurityContextDeny(nil)
|
||||||
|
|
||||||
|
var runAsUser int64 = 1
|
||||||
|
priv := true
|
||||||
|
successCases := map[string]*api.SecurityContext{
|
||||||
|
"no sc": nil,
|
||||||
|
"empty sc": {},
|
||||||
|
"valid sc": {Privileged: &priv, Capabilities: &api.Capabilities{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
pod := api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
Containers: []api.Container{
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for k, v := range successCases {
|
||||||
|
pod.Spec.Containers[0].SecurityContext = v
|
||||||
|
err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", "foo", string(api.ResourcePods), "ignored"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error returned from admission handler for case %s", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorCases := map[string]*api.SecurityContext{
|
||||||
|
"run as user": {RunAsUser: &runAsUser},
|
||||||
|
"se linux optons": {SELinuxOptions: &api.SELinuxOptions{}},
|
||||||
|
"mixed settings": {Privileged: &priv, RunAsUser: &runAsUser, SELinuxOptions: &api.SELinuxOptions{}},
|
||||||
|
}
|
||||||
|
for k, v := range errorCases {
|
||||||
|
pod.Spec.Containers[0].SecurityContext = v
|
||||||
|
err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", "foo", string(api.ResourcePods), "ignored"))
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error returned from admission handler for case %s", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue