cri-o/server/utils.go
Antonio Murdaca ed34ff3255
server: validate labels size to avoid dos
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
2017-11-11 12:14:07 +01:00

212 lines
5.6 KiB
Go

package server
import (
"fmt"
"io"
"os"
"strings"
"time"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/kubernetes-incubator/cri-o/libkpod/sandbox"
"github.com/kubernetes-incubator/cri-o/server/metrics"
"github.com/opencontainers/runtime-tools/validate"
"github.com/syndtr/gocapability/capability"
)
const (
// According to http://man7.org/linux/man-pages/man5/resolv.conf.5.html:
// "The search list is currently limited to six domains with a total of 256 characters."
maxDNSSearches = 6
maxLabelSize = 4096
)
func copyFile(src, dest string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dest)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
return err
}
func removeFile(path string) error {
if _, err := os.Stat(path); err == nil {
if err := os.Remove(path); err != nil {
return err
}
}
return nil
}
func parseDNSOptions(servers, searches, options []string, path string) error {
nServers := len(servers)
nSearches := len(searches)
nOptions := len(options)
if nServers == 0 && nSearches == 0 && nOptions == 0 {
return copyFile("/etc/resolv.conf", path)
}
if nSearches > maxDNSSearches {
return fmt.Errorf("DNSOption.Searches has more than 6 domains")
}
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
if nSearches > 0 {
data := fmt.Sprintf("search %s\n", strings.Join(searches, " "))
_, err = f.Write([]byte(data))
if err != nil {
return err
}
}
if nServers > 0 {
data := fmt.Sprintf("nameserver %s\n", strings.Join(servers, "\nnameserver "))
_, err = f.Write([]byte(data))
if err != nil {
return err
}
}
if nOptions > 0 {
data := fmt.Sprintf("options %s\n", strings.Join(options, " "))
_, err = f.Write([]byte(data))
if err != nil {
return err
}
}
return nil
}
// TODO: remove sysctl extraction related code here, instead we import from k8s directly.
const (
// SysctlsPodAnnotationKey represents the key of sysctls which are set for the infrastructure
// container of a pod. The annotation value is a comma separated list of sysctl_name=value
// key-value pairs. Only a limited set of whitelisted and isolated sysctls is supported by
// the kubelet. Pods with other sysctls will fail to launch.
SysctlsPodAnnotationKey string = "security.alpha.kubernetes.io/sysctls"
// UnsafeSysctlsPodAnnotationKey represents the key of sysctls which are set for the infrastructure
// container of a pod. The annotation value is a comma separated list of sysctl_name=value
// key-value pairs. Unsafe sysctls must be explicitly enabled for a kubelet. They are properly
// namespaced to a pod or a container, but their isolation is usually unclear or weak. Their use
// is at-your-own-risk. Pods that attempt to set an unsafe sysctl that is not enabled for a kubelet
// will fail to launch.
UnsafeSysctlsPodAnnotationKey string = "security.alpha.kubernetes.io/unsafe-sysctls"
)
// Sysctl defines a kernel parameter to be set
type Sysctl struct {
// Name of a property to set
Name string `json:"name"`
// Value of a property to set
Value string `json:"value"`
}
// SysctlsFromPodAnnotations parses the sysctl annotations into a slice of safe Sysctls
// and a slice of unsafe Sysctls. This is only a convenience wrapper around
// SysctlsFromPodAnnotation.
func SysctlsFromPodAnnotations(a map[string]string) ([]Sysctl, []Sysctl, error) {
safe, err := SysctlsFromPodAnnotation(a[SysctlsPodAnnotationKey])
if err != nil {
return nil, nil, err
}
unsafe, err := SysctlsFromPodAnnotation(a[UnsafeSysctlsPodAnnotationKey])
if err != nil {
return nil, nil, err
}
return safe, unsafe, nil
}
// SysctlsFromPodAnnotation parses an annotation value into a slice of Sysctls.
func SysctlsFromPodAnnotation(annotation string) ([]Sysctl, error) {
if len(annotation) == 0 {
return nil, nil
}
kvs := strings.Split(annotation, ",")
sysctls := make([]Sysctl, len(kvs))
for i, kv := range kvs {
cs := strings.Split(kv, "=")
if len(cs) != 2 || len(cs[0]) == 0 {
return nil, fmt.Errorf("sysctl %q not of the format sysctl_name=value", kv)
}
sysctls[i].Name = cs[0]
sysctls[i].Value = cs[1]
}
return sysctls, nil
}
func newPodNetwork(sb *sandbox.Sandbox) ocicni.PodNetwork {
return ocicni.PodNetwork{
Name: sb.KubeName(),
Namespace: sb.Namespace(),
ID: sb.ID(),
NetNS: sb.NetNsPath(),
}
}
// inStringSlice checks whether a string is inside a string slice.
// Comparison is case insensitive.
func inStringSlice(ss []string, str string) bool {
for _, s := range ss {
if strings.ToLower(s) == strings.ToLower(str) {
return true
}
}
return false
}
// getOCICapabilitiesList returns a list of all available capabilities.
func getOCICapabilitiesList() []string {
var caps []string
for _, cap := range capability.List() {
if cap > validate.LastCap() {
continue
}
caps = append(caps, "CAP_"+strings.ToUpper(cap.String()))
}
return caps
}
func recordOperation(operation string, start time.Time) {
metrics.CRIOOperations.WithLabelValues(operation).Inc()
metrics.CRIOOperationsLatency.WithLabelValues(operation).Observe(metrics.SinceInMicroseconds(start))
}
// recordError records error for metric if an error occurred.
func recordError(operation string, err error) {
if err != nil {
// TODO(runcom): handle timeout from ctx as well
metrics.CRIOOperationsErrors.WithLabelValues(operation).Inc()
}
}
func validateLabels(labels map[string]string) error {
for k, v := range labels {
if (len(k) + len(v)) > maxLabelSize {
if len(k) > 10 {
k = k[:10]
}
return fmt.Errorf("label key and value greater than maximum size (%d bytes), key: %s", maxLabelSize, k)
}
}
return nil
}