mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-15 17:51:20 +00:00
f01f0d6dc4
Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
439 lines
8.9 KiB
Go
439 lines
8.9 KiB
Go
package pss
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
fuzz "github.com/AdamKorcz/go-fuzz-headers-1"
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
var (
|
|
allowedCapabilities = []corev1.Capability{"AUDIT_WRITE",
|
|
"CHOWN",
|
|
"DAC_OVERRIDE",
|
|
"FOWNER",
|
|
"FSETID",
|
|
"KILL",
|
|
"MKNOD",
|
|
"NET_BIND_SERVICE",
|
|
"SETFCAP",
|
|
"SETGID",
|
|
"SETPCAP",
|
|
"SETUID",
|
|
"SYS_CHROOT"}
|
|
allowedSELinuxTypes = []string{"container_t",
|
|
"container_init_t",
|
|
"container_kvm_t",
|
|
""}
|
|
allowed_sysctls = []string{
|
|
"kernel.shm_rmid_forced",
|
|
"net.ipv4.ip_local_port_range",
|
|
"net.ipv4.ip_unprivileged_port_start",
|
|
"net.ipv4.tcp_syncookies",
|
|
"net.ipv4.ping_group_range",
|
|
}
|
|
baselineV126Policy = []byte(`
|
|
{
|
|
"level": "baseline",
|
|
"version": "v1.26"
|
|
}`)
|
|
baselineLatestPolicy = []byte(`
|
|
{
|
|
"level": "baseline",
|
|
"version": "latest"
|
|
}`)
|
|
)
|
|
|
|
func shouldBlockSELinuxUser(opts *corev1.SELinuxOptions) bool {
|
|
if opts == nil {
|
|
return false
|
|
}
|
|
|
|
fieldName := "User"
|
|
value := reflect.ValueOf(opts)
|
|
field := value.Elem().FieldByName(fieldName)
|
|
|
|
if field.IsValid() {
|
|
seLinuxUser := opts.User
|
|
if seLinuxUser != "" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func shouldBlockSELinuxRole(opts *corev1.SELinuxOptions) bool {
|
|
if opts == nil {
|
|
return false
|
|
}
|
|
|
|
fieldName := "Role"
|
|
value := reflect.ValueOf(opts)
|
|
field := value.Elem().FieldByName(fieldName)
|
|
|
|
if field.IsValid() {
|
|
seLinuxUser := opts.Role
|
|
if seLinuxUser != "" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func shouldAllowBaseline(pod *corev1.Pod) (bool, error) {
|
|
|
|
spec := pod.Spec
|
|
|
|
if len(spec.Volumes) > 0 {
|
|
volumes := spec.Volumes
|
|
for _, volume := range volumes {
|
|
if volume.HostPath != nil {
|
|
return false, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(pod.ObjectMeta.Annotations) > 0 {
|
|
annotations := pod.ObjectMeta.Annotations
|
|
for k, v := range annotations {
|
|
if strings.HasPrefix(k, "container.apparmor.security.beta.kubernetes.io/") {
|
|
if v != "runtime/default" && !strings.HasPrefix(v, "localhost/") {
|
|
return false, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if spec.SecurityContext != nil {
|
|
sc := spec.SecurityContext
|
|
|
|
if sc.WindowsOptions != nil {
|
|
if sc.WindowsOptions.HostProcess != nil {
|
|
if *sc.WindowsOptions.HostProcess == true {
|
|
return false, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
if shouldBlockContainerSELinux(sc.SELinuxOptions) {
|
|
return false, nil
|
|
}
|
|
|
|
if sc.SeccompProfile != nil {
|
|
seccompType := sc.SeccompProfile.Type
|
|
defaultSeccomp := corev1.SeccompProfileTypeRuntimeDefault
|
|
localhostSeccomp := corev1.SeccompProfileTypeLocalhost
|
|
if seccompType != defaultSeccomp && seccompType != localhostSeccomp {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
fieldName := "Sysctls"
|
|
value := reflect.ValueOf(sc)
|
|
field := value.Elem().FieldByName(fieldName)
|
|
|
|
if field.IsValid() {
|
|
for _, sysctl := range sc.Sysctls {
|
|
if !slices.Contains(allowed_sysctls, sysctl.Name) {
|
|
return false, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if pod.Spec.Containers != nil || len(pod.Spec.Containers) != 0 {
|
|
|
|
containers := pod.Spec.Containers
|
|
for _, container := range containers {
|
|
|
|
if container.SecurityContext != nil {
|
|
if shouldBlockContainerSecurityContext(container.SecurityContext) {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
fieldName := "Ports"
|
|
value := reflect.ValueOf(container)
|
|
field := value.FieldByName(fieldName)
|
|
if field.IsValid() {
|
|
if shouldBlockContainerPorts(container.Ports) {
|
|
return false, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if pod.Spec.InitContainers != nil || len(pod.Spec.InitContainers) != 0 {
|
|
|
|
containers := pod.Spec.InitContainers
|
|
for _, container := range containers {
|
|
|
|
if container.SecurityContext != nil {
|
|
if shouldBlockContainerSecurityContext(container.SecurityContext) {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
fieldName := "Ports"
|
|
value := reflect.ValueOf(container)
|
|
field := value.FieldByName(fieldName)
|
|
if field.IsValid() {
|
|
if shouldBlockContainerPorts(container.Ports) {
|
|
return false, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if pod.Spec.EphemeralContainers != nil || len(pod.Spec.EphemeralContainers) != 0 {
|
|
containers := pod.Spec.EphemeralContainers
|
|
for _, container := range containers {
|
|
|
|
if container.SecurityContext != nil {
|
|
if shouldBlockContainerSecurityContext(container.SecurityContext) {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
fieldName := "Ports"
|
|
value := reflect.ValueOf(container)
|
|
field := value.FieldByName(fieldName)
|
|
if field.IsValid() {
|
|
if shouldBlockContainerPorts(container.Ports) {
|
|
return false, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if spec.SecurityContext != nil {
|
|
fieldName := "HostNetwork"
|
|
value := reflect.ValueOf(spec)
|
|
field := value.FieldByName(fieldName)
|
|
|
|
if field.IsValid() {
|
|
if spec.HostNetwork == true {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
fieldName = "HostPID"
|
|
field = value.FieldByName(fieldName)
|
|
|
|
if field.IsValid() {
|
|
if spec.HostPID == true {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
fieldName = "HostIPC"
|
|
field = value.FieldByName(fieldName)
|
|
|
|
if field.IsValid() {
|
|
if spec.HostIPC == true {
|
|
return false, nil
|
|
}
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func shouldBlockContainerSecurityContext(sc *corev1.SecurityContext) bool {
|
|
if sc.WindowsOptions != nil {
|
|
if sc.WindowsOptions.HostProcess != nil {
|
|
if *sc.WindowsOptions.HostProcess == true {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
if sc.Privileged != nil {
|
|
if *sc.Privileged == true {
|
|
return true
|
|
}
|
|
}
|
|
|
|
if sc.Capabilities != nil {
|
|
capabilities := sc.Capabilities
|
|
|
|
if shouldBlockBaselineCapabilities(capabilities) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
if sc.SELinuxOptions != nil {
|
|
seLinuxOptions := sc.SELinuxOptions
|
|
if shouldBlockContainerSELinux(seLinuxOptions) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
if sc.ProcMount != nil {
|
|
if *sc.ProcMount != corev1.DefaultProcMount {
|
|
return true
|
|
}
|
|
}
|
|
|
|
if sc.SeccompProfile != nil {
|
|
seccompType := sc.SeccompProfile.Type
|
|
defaultSeccomp := corev1.SeccompProfileTypeRuntimeDefault
|
|
localhostSeccomp := corev1.SeccompProfileTypeLocalhost
|
|
if seccompType != defaultSeccomp && seccompType != localhostSeccomp {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func shouldBlockContainerSELinux(opts *corev1.SELinuxOptions) bool {
|
|
if opts == nil {
|
|
return false
|
|
}
|
|
|
|
fieldName := "Type"
|
|
value := reflect.ValueOf(opts)
|
|
field := value.Elem().FieldByName(fieldName)
|
|
|
|
if field.IsValid() {
|
|
seLinuxType := opts.Type
|
|
if !slices.Contains(allowedSELinuxTypes, seLinuxType) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
if shouldBlockSELinuxUser(opts) {
|
|
return true
|
|
}
|
|
|
|
if shouldBlockSELinuxRole(opts) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func shouldBlockContainerPorts(ports []corev1.ContainerPort) bool {
|
|
if len(ports) > 0 {
|
|
for _, port := range ports {
|
|
|
|
fieldName := "HostPort"
|
|
value := reflect.ValueOf(port)
|
|
field := value.FieldByName(fieldName)
|
|
|
|
if field.IsValid() {
|
|
if port.HostPort != 0 {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func shouldBlockBaselineCapabilities(capabilities *corev1.Capabilities) bool {
|
|
fieldName := "Add"
|
|
value := reflect.ValueOf(capabilities)
|
|
field := value.Elem().FieldByName(fieldName)
|
|
|
|
if field.IsValid() {
|
|
if len(capabilities.Add) > 0 {
|
|
for _, capability := range capabilities.Add {
|
|
if !slices.Contains(allowedCapabilities, capability) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func getPod(ff *fuzz.ConsumeFuzzer) (*corev1.Pod, error) {
|
|
pod := &corev1.Pod{}
|
|
err := ff.GenerateStruct(pod)
|
|
pod.Kind = "Pod"
|
|
pod.APIVersion = "v1"
|
|
return pod, err
|
|
}
|
|
|
|
var (
|
|
baselineV124Rule, baselineLatestRule kyvernov1.PodSecurity
|
|
)
|
|
|
|
func init() {
|
|
err := json.Unmarshal(baselineV126Policy, &baselineV124Rule)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
err = json.Unmarshal(baselineLatestPolicy, &baselineLatestRule)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func FuzzBaselinePS(f *testing.F) {
|
|
f.Fuzz(func(t *testing.T, data []byte) {
|
|
ff := fuzz.NewConsumer(data)
|
|
|
|
pod, err := getPod(ff)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if len(pod.ObjectMeta.Annotations) > 0 {
|
|
for k, v := range pod.ObjectMeta.Annotations {
|
|
for _, r := range k {
|
|
if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') && (r < '0' || r > '9') && (r != '-' && r != '/' && r != '_' && r != ',' && r != '.') {
|
|
return
|
|
}
|
|
}
|
|
for _, r := range v {
|
|
if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') && (r < '0' || r > '9') && (r != '-' && r != '/' && r != '_' && r != ',' && r != '.') {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var allowPod bool
|
|
allowPod, _ = shouldAllowBaseline(pod)
|
|
if allowPod {
|
|
return
|
|
}
|
|
|
|
policyToCheck, err := ff.GetInt()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var rule kyvernov1.PodSecurity
|
|
|
|
switch policyToCheck % 2 {
|
|
case 0:
|
|
rule = baselineV124Rule
|
|
case 1:
|
|
rule = baselineLatestRule
|
|
}
|
|
|
|
levelVersion, _ := ParseVersion(rule.Level, rule.Version)
|
|
allowed, _ := EvaluatePod(levelVersion, rule.Exclude, pod)
|
|
if allowPod != allowed {
|
|
pJson, err := json.MarshalIndent(pod, "", "")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Println(string(pJson))
|
|
fmt.Println("policyToCheck: ", policyToCheck%2)
|
|
fmt.Println("allowed: ", allowed, "allowPod: ", allowPod)
|
|
panic("They don't correlate")
|
|
}
|
|
})
|
|
}
|