1/* 2Copyright 2021 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package policy 18 19import ( 20 "strings" 21 22 corev1 "k8s.io/api/core/v1" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/pod-security-admission/api" 25) 26 27type Check struct { 28 // ID is the unique ID of the check. 29 ID string 30 // Level is the policy level this check belongs to. 31 // Must be Baseline or Restricted. 32 // Baseline checks are evaluated for baseline and restricted namespaces. 33 // Restricted checks are only evaluated for restricted namespaces. 34 Level api.Level 35 // Versions contains one or more revisions of the check that apply to different versions. 36 // If the check is not yet assigned to a version, this must be a single-item list with a MinimumVersion of "". 37 // Otherwise, MinimumVersion of items must represent strictly increasing versions. 38 Versions []VersionedCheck 39} 40 41type VersionedCheck struct { 42 // MinimumVersion is the first policy version this check applies to. 43 // If unset, this check is not yet assigned to a policy version. 44 // If set, must not be "latest". 45 MinimumVersion api.Version 46 // CheckPod determines if the pod is allowed. 47 CheckPod CheckPodFn 48} 49 50type CheckPodFn func(*metav1.ObjectMeta, *corev1.PodSpec) CheckResult 51 52// CheckResult contains the result of checking a pod and indicates whether the pod is allowed, 53// and if not, why it was forbidden. 54// 55// Example output for (false, "host ports", "8080, 9090"): 56// When checking all pods in a namespace: 57// disallowed by policy "baseline": host ports, privileged containers, non-default capabilities 58// When checking an individual pod: 59// disallowed by policy "baseline": host ports (8080, 9090), privileged containers, non-default capabilities (CAP_NET_RAW) 60type CheckResult struct { 61 // Allowed indicates if the check allowed the pod. 62 Allowed bool 63 // ForbiddenReason must be set if Allowed is false. 64 // ForbiddenReason should be as succinct as possible and is always output. 65 // Examples: 66 // - "host ports" 67 // - "privileged containers" 68 // - "non-default capabilities" 69 ForbiddenReason string 70 // ForbiddenDetail should only be set if Allowed is false, and is optional. 71 // ForbiddenDetail can include specific values that were disallowed and is used when checking an individual object. 72 // Examples: 73 // - list specific invalid host ports: "8080, 9090" 74 // - list specific invalid containers: "container1, container2" 75 // - list specific non-default capabilities: "CAP_NET_RAW" 76 ForbiddenDetail string 77} 78 79// AggergateCheckResult holds the aggregate result of running CheckPod across multiple checks. 80type AggregateCheckResult struct { 81 // Allowed indicates if all checks allowed the pod. 82 Allowed bool 83 // ForbiddenReasons is a slice of the forbidden reasons from all the forbidden checks. It should not include empty strings. 84 // ForbiddenReasons and ForbiddenDetails must have the same number of elements, and the indexes are for the same check. 85 ForbiddenReasons []string 86 // ForbiddenDetails is a slice of the forbidden details from all the forbidden checks. It may include empty strings. 87 // ForbiddenReasons and ForbiddenDetails must have the same number of elements, and the indexes are for the same check. 88 ForbiddenDetails []string 89} 90 91// ForbiddenReason returns a comma-separated string of of the forbidden reasons. 92// Example: host ports, privileged containers, non-default capabilities 93func (a *AggregateCheckResult) ForbiddenReason() string { 94 return strings.Join(a.ForbiddenReasons, ", ") 95} 96 97// ForbiddenDetail returns a detailed forbidden message, with non-empty details formatted in 98// parentheses with the associated reason. 99// Example: host ports (8080, 9090), privileged containers, non-default capabilities (NET_RAW) 100func (a *AggregateCheckResult) ForbiddenDetail() string { 101 var b strings.Builder 102 for i := 0; i < len(a.ForbiddenReasons); i++ { 103 b.WriteString(a.ForbiddenReasons[i]) 104 if a.ForbiddenDetails[i] != "" { 105 b.WriteString(" (") 106 b.WriteString(a.ForbiddenDetails[i]) 107 b.WriteString(")") 108 } 109 if i != len(a.ForbiddenReasons)-1 { 110 b.WriteString(", ") 111 } 112 } 113 return b.String() 114} 115 116// UnknownForbiddenReason is used as the placeholder forbidden reason for checks that incorrectly disallow without providing a reason. 117const UnknownForbiddenReason = "unknown forbidden reason" 118 119// AggregateCheckPod runs all the checks and aggregates the forbidden results into a single CheckResult. 120// The aggregated reason is a comma-separated 121func AggregateCheckResults(results []CheckResult) AggregateCheckResult { 122 var ( 123 reasons []string 124 details []string 125 ) 126 for _, result := range results { 127 if !result.Allowed { 128 if len(result.ForbiddenReason) == 0 { 129 reasons = append(reasons, UnknownForbiddenReason) 130 } else { 131 reasons = append(reasons, result.ForbiddenReason) 132 } 133 details = append(details, result.ForbiddenDetail) 134 } 135 } 136 return AggregateCheckResult{ 137 Allowed: len(reasons) == 0, 138 ForbiddenReasons: reasons, 139 ForbiddenDetails: details, 140 } 141} 142 143var ( 144 defaultChecks []func() Check 145 experimentalChecks []func() Check 146) 147 148func addCheck(f func() Check) { 149 // add to experimental or versioned list 150 c := f() 151 if len(c.Versions) == 1 && c.Versions[0].MinimumVersion == (api.Version{}) { 152 experimentalChecks = append(experimentalChecks, f) 153 } else { 154 defaultChecks = append(defaultChecks, f) 155 } 156} 157 158// DefaultChecks returns checks that are expected to be enabled by default. 159// The results are mutually exclusive with ExperimentalChecks. 160// It returns a new copy of checks on each invocation and is expected to be called once at setup time. 161func DefaultChecks() []Check { 162 retval := make([]Check, 0, len(defaultChecks)) 163 for _, f := range defaultChecks { 164 retval = append(retval, f()) 165 } 166 return retval 167} 168 169// ExperimentalChecks returns checks that have not yet been assigned to policy versions. 170// The results are mutually exclusive with DefaultChecks. 171// It returns a new copy of checks on each invocation and is expected to be called once at setup time. 172func ExperimentalChecks() []Check { 173 retval := make([]Check, 0, len(experimentalChecks)) 174 for _, f := range experimentalChecks { 175 retval = append(retval, f()) 176 } 177 return retval 178} 179