2018-01-09 18:57:14 +00:00
|
|
|
/*
|
|
|
|
Copyright 2014 The Kubernetes Authors.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package lifecycle
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"k8s.io/api/core/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/types"
|
|
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
2018-11-26 18:23:56 +00:00
|
|
|
"k8s.io/klog"
|
2018-01-09 18:57:14 +00:00
|
|
|
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
|
|
|
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
|
|
|
"k8s.io/kubernetes/pkg/kubelet/util/format"
|
|
|
|
"k8s.io/kubernetes/pkg/security/apparmor"
|
|
|
|
)
|
|
|
|
|
|
|
|
type HandlerRunner struct {
|
|
|
|
httpGetter kubetypes.HttpGetter
|
|
|
|
commandRunner kubecontainer.ContainerCommandRunner
|
|
|
|
containerManager podStatusProvider
|
|
|
|
}
|
|
|
|
|
|
|
|
type podStatusProvider interface {
|
|
|
|
GetPodStatus(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewHandlerRunner(httpGetter kubetypes.HttpGetter, commandRunner kubecontainer.ContainerCommandRunner, containerManager podStatusProvider) kubecontainer.HandlerRunner {
|
|
|
|
return &HandlerRunner{
|
|
|
|
httpGetter: httpGetter,
|
|
|
|
commandRunner: commandRunner,
|
|
|
|
containerManager: containerManager,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hr *HandlerRunner) Run(containerID kubecontainer.ContainerID, pod *v1.Pod, container *v1.Container, handler *v1.Handler) (string, error) {
|
|
|
|
switch {
|
|
|
|
case handler.Exec != nil:
|
|
|
|
var msg string
|
|
|
|
// TODO(tallclair): Pass a proper timeout value.
|
|
|
|
output, err := hr.commandRunner.RunInContainer(containerID, handler.Exec.Command, 0)
|
|
|
|
if err != nil {
|
|
|
|
msg = fmt.Sprintf("Exec lifecycle hook (%v) for Container %q in Pod %q failed - error: %v, message: %q", handler.Exec.Command, container.Name, format.Pod(pod), err, string(output))
|
2018-11-26 18:23:56 +00:00
|
|
|
klog.V(1).Infof(msg)
|
2018-01-09 18:57:14 +00:00
|
|
|
}
|
|
|
|
return msg, err
|
|
|
|
case handler.HTTPGet != nil:
|
|
|
|
msg, err := hr.runHTTPHandler(pod, container, handler)
|
|
|
|
if err != nil {
|
|
|
|
msg = fmt.Sprintf("Http lifecycle hook (%s) for Container %q in Pod %q failed - error: %v, message: %q", handler.HTTPGet.Path, container.Name, format.Pod(pod), err, msg)
|
2018-11-26 18:23:56 +00:00
|
|
|
klog.V(1).Infof(msg)
|
2018-01-09 18:57:14 +00:00
|
|
|
}
|
|
|
|
return msg, err
|
|
|
|
default:
|
|
|
|
err := fmt.Errorf("Invalid handler: %v", handler)
|
|
|
|
msg := fmt.Sprintf("Cannot run handler: %v", err)
|
2018-11-26 18:23:56 +00:00
|
|
|
klog.Errorf(msg)
|
2018-01-09 18:57:14 +00:00
|
|
|
return msg, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// resolvePort attempts to turn an IntOrString port reference into a concrete port number.
|
|
|
|
// If portReference has an int value, it is treated as a literal, and simply returns that value.
|
|
|
|
// If portReference is a string, an attempt is first made to parse it as an integer. If that fails,
|
|
|
|
// an attempt is made to find a port with the same name in the container spec.
|
|
|
|
// If a port with the same name is found, it's ContainerPort value is returned. If no matching
|
|
|
|
// port is found, an error is returned.
|
|
|
|
func resolvePort(portReference intstr.IntOrString, container *v1.Container) (int, error) {
|
|
|
|
if portReference.Type == intstr.Int {
|
|
|
|
return portReference.IntValue(), nil
|
|
|
|
}
|
|
|
|
portName := portReference.StrVal
|
|
|
|
port, err := strconv.Atoi(portName)
|
|
|
|
if err == nil {
|
|
|
|
return port, nil
|
|
|
|
}
|
|
|
|
for _, portSpec := range container.Ports {
|
|
|
|
if portSpec.Name == portName {
|
|
|
|
return int(portSpec.ContainerPort), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1, fmt.Errorf("couldn't find port: %v in %v", portReference, container)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hr *HandlerRunner) runHTTPHandler(pod *v1.Pod, container *v1.Container, handler *v1.Handler) (string, error) {
|
|
|
|
host := handler.HTTPGet.Host
|
|
|
|
if len(host) == 0 {
|
|
|
|
status, err := hr.containerManager.GetPodStatus(pod.UID, pod.Name, pod.Namespace)
|
|
|
|
if err != nil {
|
2018-11-26 18:23:56 +00:00
|
|
|
klog.Errorf("Unable to get pod info, event handlers may be invalid.")
|
2018-01-09 18:57:14 +00:00
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if status.IP == "" {
|
|
|
|
return "", fmt.Errorf("failed to find networking container: %v", status)
|
|
|
|
}
|
|
|
|
host = status.IP
|
|
|
|
}
|
|
|
|
var port int
|
|
|
|
if handler.HTTPGet.Port.Type == intstr.String && len(handler.HTTPGet.Port.StrVal) == 0 {
|
|
|
|
port = 80
|
|
|
|
} else {
|
|
|
|
var err error
|
|
|
|
port, err = resolvePort(handler.HTTPGet.Port, container)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
url := fmt.Sprintf("http://%s/%s", net.JoinHostPort(host, strconv.Itoa(port)), handler.HTTPGet.Path)
|
|
|
|
resp, err := hr.httpGetter.Get(url)
|
|
|
|
return getHttpRespBody(resp), err
|
|
|
|
}
|
|
|
|
|
|
|
|
func getHttpRespBody(resp *http.Response) string {
|
|
|
|
if resp == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
if bytes, err := ioutil.ReadAll(resp.Body); err == nil {
|
|
|
|
return string(bytes)
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewAppArmorAdmitHandler(validator apparmor.Validator) PodAdmitHandler {
|
|
|
|
return &appArmorAdmitHandler{
|
|
|
|
Validator: validator,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type appArmorAdmitHandler struct {
|
|
|
|
apparmor.Validator
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *appArmorAdmitHandler) Admit(attrs *PodAdmitAttributes) PodAdmitResult {
|
|
|
|
// If the pod is already running or terminated, no need to recheck AppArmor.
|
|
|
|
if attrs.Pod.Status.Phase != v1.PodPending {
|
|
|
|
return PodAdmitResult{Admit: true}
|
|
|
|
}
|
|
|
|
|
|
|
|
err := a.Validate(attrs.Pod)
|
|
|
|
if err == nil {
|
|
|
|
return PodAdmitResult{Admit: true}
|
|
|
|
}
|
|
|
|
return PodAdmitResult{
|
|
|
|
Admit: false,
|
|
|
|
Reason: "AppArmor",
|
|
|
|
Message: fmt.Sprintf("Cannot enforce AppArmor: %v", err),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewNoNewPrivsAdmitHandler(runtime kubecontainer.Runtime) PodAdmitHandler {
|
|
|
|
return &noNewPrivsAdmitHandler{
|
|
|
|
Runtime: runtime,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type noNewPrivsAdmitHandler struct {
|
|
|
|
kubecontainer.Runtime
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *noNewPrivsAdmitHandler) Admit(attrs *PodAdmitAttributes) PodAdmitResult {
|
|
|
|
// If the pod is already running or terminated, no need to recheck NoNewPrivs.
|
|
|
|
if attrs.Pod.Status.Phase != v1.PodPending {
|
|
|
|
return PodAdmitResult{Admit: true}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the containers in a pod do not require no-new-privs, admit it.
|
|
|
|
if !noNewPrivsRequired(attrs.Pod) {
|
|
|
|
return PodAdmitResult{Admit: true}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Always admit runtimes except docker.
|
|
|
|
if a.Runtime.Type() != kubetypes.DockerContainerRuntime {
|
|
|
|
return PodAdmitResult{Admit: true}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure docker api version is valid.
|
|
|
|
rversion, err := a.Runtime.APIVersion()
|
|
|
|
if err != nil {
|
|
|
|
return PodAdmitResult{
|
|
|
|
Admit: false,
|
|
|
|
Reason: "NoNewPrivs",
|
|
|
|
Message: fmt.Sprintf("Cannot enforce NoNewPrivs: %v", err),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
v, err := rversion.Compare("1.23.0")
|
|
|
|
if err != nil {
|
|
|
|
return PodAdmitResult{
|
|
|
|
Admit: false,
|
|
|
|
Reason: "NoNewPrivs",
|
|
|
|
Message: fmt.Sprintf("Cannot enforce NoNewPrivs: %v", err),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If the version is less than 1.23 it will return -1 above.
|
|
|
|
if v == -1 {
|
|
|
|
return PodAdmitResult{
|
|
|
|
Admit: false,
|
|
|
|
Reason: "NoNewPrivs",
|
|
|
|
Message: fmt.Sprintf("Cannot enforce NoNewPrivs: docker runtime API version %q must be greater than or equal to 1.23", rversion.String()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return PodAdmitResult{Admit: true}
|
|
|
|
}
|
|
|
|
|
|
|
|
func noNewPrivsRequired(pod *v1.Pod) bool {
|
|
|
|
// Iterate over pod containers and check if we added no-new-privs.
|
|
|
|
for _, c := range pod.Spec.Containers {
|
|
|
|
if c.SecurityContext != nil && c.SecurityContext.AllowPrivilegeEscalation != nil && !*c.SecurityContext.AllowPrivilegeEscalation {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2018-11-26 18:23:56 +00:00
|
|
|
|
|
|
|
func NewProcMountAdmitHandler(runtime kubecontainer.Runtime) PodAdmitHandler {
|
|
|
|
return &procMountAdmitHandler{
|
|
|
|
Runtime: runtime,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type procMountAdmitHandler struct {
|
|
|
|
kubecontainer.Runtime
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *procMountAdmitHandler) Admit(attrs *PodAdmitAttributes) PodAdmitResult {
|
|
|
|
// If the pod is already running or terminated, no need to recheck NoNewPrivs.
|
|
|
|
if attrs.Pod.Status.Phase != v1.PodPending {
|
|
|
|
return PodAdmitResult{Admit: true}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the containers in a pod only need the default ProcMountType, admit it.
|
|
|
|
if procMountIsDefault(attrs.Pod) {
|
|
|
|
return PodAdmitResult{Admit: true}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Always admit runtimes except docker.
|
|
|
|
if a.Runtime.Type() != kubetypes.DockerContainerRuntime {
|
|
|
|
return PodAdmitResult{Admit: true}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure docker api version is valid.
|
|
|
|
// Merged in https://github.com/moby/moby/pull/36644
|
|
|
|
rversion, err := a.Runtime.APIVersion()
|
|
|
|
if err != nil {
|
|
|
|
return PodAdmitResult{
|
|
|
|
Admit: false,
|
|
|
|
Reason: "ProcMount",
|
|
|
|
Message: fmt.Sprintf("Cannot enforce ProcMount: %v", err),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
v, err := rversion.Compare("1.38.0")
|
|
|
|
if err != nil {
|
|
|
|
return PodAdmitResult{
|
|
|
|
Admit: false,
|
|
|
|
Reason: "ProcMount",
|
|
|
|
Message: fmt.Sprintf("Cannot enforce ProcMount: %v", err),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If the version is less than 1.38 it will return -1 above.
|
|
|
|
if v == -1 {
|
|
|
|
return PodAdmitResult{
|
|
|
|
Admit: false,
|
|
|
|
Reason: "ProcMount",
|
|
|
|
Message: fmt.Sprintf("Cannot enforce ProcMount: docker runtime API version %q must be greater than or equal to 1.38", rversion.String()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return PodAdmitResult{Admit: true}
|
|
|
|
}
|
|
|
|
|
|
|
|
func procMountIsDefault(pod *v1.Pod) bool {
|
|
|
|
// Iterate over pod containers and check if we are using the DefaultProcMountType
|
|
|
|
// for all containers.
|
|
|
|
for _, c := range pod.Spec.Containers {
|
|
|
|
if c.SecurityContext != nil {
|
|
|
|
if c.SecurityContext.ProcMount != nil && *c.SecurityContext.ProcMount != v1.DefaultProcMount {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|