mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 18:31:15 +00:00
Merge pull request #107698 from tallclair/psa-overrides
[PodSecurity] Deduplicate errors between baseline & restricted checks
This commit is contained in:
commit
6ab748eeec
@ -41,11 +41,13 @@ func init() {
|
|||||||
addCheck(CheckCapabilitiesBaseline)
|
addCheck(CheckCapabilitiesBaseline)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkCapabilitiesBaselineID CheckID = "capabilities_baseline"
|
||||||
|
|
||||||
// CheckCapabilitiesBaseline returns a baseline level check
|
// CheckCapabilitiesBaseline returns a baseline level check
|
||||||
// that limits the capabilities that can be added in 1.0+
|
// that limits the capabilities that can be added in 1.0+
|
||||||
func CheckCapabilitiesBaseline() Check {
|
func CheckCapabilitiesBaseline() Check {
|
||||||
return Check{
|
return Check{
|
||||||
ID: "capabilities_baseline",
|
ID: checkCapabilitiesBaselineID,
|
||||||
Level: api.LevelBaseline,
|
Level: api.LevelBaseline,
|
||||||
Versions: []VersionedCheck{
|
Versions: []VersionedCheck{
|
||||||
{
|
{
|
||||||
|
@ -62,8 +62,9 @@ func CheckCapabilitiesRestricted() Check {
|
|||||||
Level: api.LevelRestricted,
|
Level: api.LevelRestricted,
|
||||||
Versions: []VersionedCheck{
|
Versions: []VersionedCheck{
|
||||||
{
|
{
|
||||||
MinimumVersion: api.MajorMinorVersion(1, 22),
|
MinimumVersion: api.MajorMinorVersion(1, 22),
|
||||||
CheckPod: capabilitiesRestricted_1_22,
|
CheckPod: capabilitiesRestricted_1_22,
|
||||||
|
OverrideCheckIDs: []CheckID{checkCapabilitiesBaselineID},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -38,11 +38,13 @@ func init() {
|
|||||||
addCheck(CheckHostPathVolumes)
|
addCheck(CheckHostPathVolumes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkHostPathVolumesID CheckID = "hostPathVolumes"
|
||||||
|
|
||||||
// CheckHostPathVolumes returns a baseline level check
|
// CheckHostPathVolumes returns a baseline level check
|
||||||
// that requires hostPath=undefined/null in 1.0+
|
// that requires hostPath=undefined/null in 1.0+
|
||||||
func CheckHostPathVolumes() Check {
|
func CheckHostPathVolumes() Check {
|
||||||
return Check{
|
return Check{
|
||||||
ID: "hostPathVolumes",
|
ID: checkHostPathVolumesID,
|
||||||
Level: api.LevelBaseline,
|
Level: api.LevelBaseline,
|
||||||
Versions: []VersionedCheck{
|
Versions: []VersionedCheck{
|
||||||
{
|
{
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/pod-security-admission/api"
|
"k8s.io/pod-security-admission/api"
|
||||||
@ -71,7 +70,7 @@ func procMount_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) Chec
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// check if the value of the proc mount type is valid.
|
// check if the value of the proc mount type is valid.
|
||||||
if *container.SecurityContext.ProcMount != v1.DefaultProcMount {
|
if *container.SecurityContext.ProcMount != corev1.DefaultProcMount {
|
||||||
badContainers = append(badContainers, container.Name)
|
badContainers = append(badContainers, container.Name)
|
||||||
forbiddenProcMountTypes.Insert(string(*container.SecurityContext.ProcMount))
|
forbiddenProcMountTypes.Insert(string(*container.SecurityContext.ProcMount))
|
||||||
}
|
}
|
||||||
|
@ -76,8 +76,9 @@ func CheckRestrictedVolumes() Check {
|
|||||||
Level: api.LevelRestricted,
|
Level: api.LevelRestricted,
|
||||||
Versions: []VersionedCheck{
|
Versions: []VersionedCheck{
|
||||||
{
|
{
|
||||||
MinimumVersion: api.MajorMinorVersion(1, 0),
|
MinimumVersion: api.MajorMinorVersion(1, 0),
|
||||||
CheckPod: restrictedVolumes_1_0,
|
CheckPod: restrictedVolumes_1_0,
|
||||||
|
OverrideCheckIDs: []CheckID{checkHostPathVolumesID},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,8 @@ spec.initContainers[*].securityContext.seccompProfile.type
|
|||||||
const (
|
const (
|
||||||
annotationKeyPod = "seccomp.security.alpha.kubernetes.io/pod"
|
annotationKeyPod = "seccomp.security.alpha.kubernetes.io/pod"
|
||||||
annotationKeyContainerPrefix = "container.seccomp.security.alpha.kubernetes.io/"
|
annotationKeyContainerPrefix = "container.seccomp.security.alpha.kubernetes.io/"
|
||||||
|
|
||||||
|
checkSeccompBaselineID CheckID = "seccompProfile_baseline"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -57,7 +59,7 @@ func init() {
|
|||||||
|
|
||||||
func CheckSeccompBaseline() Check {
|
func CheckSeccompBaseline() Check {
|
||||||
return Check{
|
return Check{
|
||||||
ID: "seccompProfile_baseline",
|
ID: checkSeccompBaselineID,
|
||||||
Level: api.LevelBaseline,
|
Level: api.LevelBaseline,
|
||||||
Versions: []VersionedCheck{
|
Versions: []VersionedCheck{
|
||||||
{
|
{
|
||||||
|
@ -51,8 +51,9 @@ func CheckSeccompProfileRestricted() Check {
|
|||||||
Level: api.LevelRestricted,
|
Level: api.LevelRestricted,
|
||||||
Versions: []VersionedCheck{
|
Versions: []VersionedCheck{
|
||||||
{
|
{
|
||||||
MinimumVersion: api.MajorMinorVersion(1, 19),
|
MinimumVersion: api.MajorMinorVersion(1, 19),
|
||||||
CheckPod: seccompProfileRestricted_1_19,
|
CheckPod: seccompProfileRestricted_1_19,
|
||||||
|
OverrideCheckIDs: []CheckID{checkSeccompBaselineID},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
|
|
||||||
type Check struct {
|
type Check struct {
|
||||||
// ID is the unique ID of the check.
|
// ID is the unique ID of the check.
|
||||||
ID string
|
ID CheckID
|
||||||
// Level is the policy level this check belongs to.
|
// Level is the policy level this check belongs to.
|
||||||
// Must be Baseline or Restricted.
|
// Must be Baseline or Restricted.
|
||||||
// Baseline checks are evaluated for baseline and restricted namespaces.
|
// Baseline checks are evaluated for baseline and restricted namespaces.
|
||||||
@ -45,10 +45,15 @@ type VersionedCheck struct {
|
|||||||
MinimumVersion api.Version
|
MinimumVersion api.Version
|
||||||
// CheckPod determines if the pod is allowed.
|
// CheckPod determines if the pod is allowed.
|
||||||
CheckPod CheckPodFn
|
CheckPod CheckPodFn
|
||||||
|
// OverrideCheckIDs is an optional list of checks that should be skipped when this check is run.
|
||||||
|
// Overrides may only be set on restricted checks, and may only override baseline checks.
|
||||||
|
OverrideCheckIDs []CheckID
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckPodFn func(*metav1.ObjectMeta, *corev1.PodSpec) CheckResult
|
type CheckPodFn func(*metav1.ObjectMeta, *corev1.PodSpec) CheckResult
|
||||||
|
|
||||||
|
type CheckID string
|
||||||
|
|
||||||
// CheckResult contains the result of checking a pod and indicates whether the pod is allowed,
|
// CheckResult contains the result of checking a pod and indicates whether the pod is allowed,
|
||||||
// and if not, why it was forbidden.
|
// and if not, why it was forbidden.
|
||||||
//
|
//
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 policy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestValidChecks ensures that all registered checks are valid.
|
||||||
|
func TestValidChecks(t *testing.T) {
|
||||||
|
allChecks := append(DefaultChecks(), ExperimentalChecks()...)
|
||||||
|
|
||||||
|
assert.NoError(t, validateChecks(allChecks))
|
||||||
|
|
||||||
|
// Ensure that all overrides map to existing checks.
|
||||||
|
allIDs := map[CheckID]bool{}
|
||||||
|
for _, check := range allChecks {
|
||||||
|
allIDs[check.ID] = true
|
||||||
|
}
|
||||||
|
for _, check := range allChecks {
|
||||||
|
for _, c := range check.Versions {
|
||||||
|
for _, override := range c.OverrideCheckIDs {
|
||||||
|
assert.Contains(t, allIDs, override, "check %s overrides non-existent check %s", check.ID, override)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ package policy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -32,7 +33,7 @@ type Evaluator interface {
|
|||||||
|
|
||||||
// checkRegistry provides a default implementation of an Evaluator.
|
// checkRegistry provides a default implementation of an Evaluator.
|
||||||
type checkRegistry struct {
|
type checkRegistry struct {
|
||||||
// The checks are a map of check_ID -> sorted slice of versioned checks, newest first
|
// The checks are a map policy version to a slice of checks registered for that version.
|
||||||
baselineChecks, restrictedChecks map[api.Version][]CheckPodFn
|
baselineChecks, restrictedChecks map[api.Version][]CheckPodFn
|
||||||
// maxVersion is the maximum version that is cached, guaranteed to be at least
|
// maxVersion is the maximum version that is cached, guaranteed to be at least
|
||||||
// the max MinimumVersion of all registered checks.
|
// the max MinimumVersion of all registered checks.
|
||||||
@ -64,26 +65,29 @@ func (r *checkRegistry) EvaluatePod(lv api.LevelVersion, podMetadata *metav1.Obj
|
|||||||
if r.maxVersion.Older(lv.Version) {
|
if r.maxVersion.Older(lv.Version) {
|
||||||
lv.Version = r.maxVersion
|
lv.Version = r.maxVersion
|
||||||
}
|
}
|
||||||
results := []CheckResult{}
|
|
||||||
for _, check := range r.baselineChecks[lv.Version] {
|
var checks []CheckPodFn
|
||||||
results = append(results, check(podMetadata, podSpec))
|
|
||||||
}
|
|
||||||
if lv.Level == api.LevelBaseline {
|
if lv.Level == api.LevelBaseline {
|
||||||
return results
|
checks = r.baselineChecks[lv.Version]
|
||||||
|
} else {
|
||||||
|
// includes non-overridden baseline checks
|
||||||
|
checks = r.restrictedChecks[lv.Version]
|
||||||
}
|
}
|
||||||
for _, check := range r.restrictedChecks[lv.Version] {
|
|
||||||
|
var results []CheckResult
|
||||||
|
for _, check := range checks {
|
||||||
results = append(results, check(podMetadata, podSpec))
|
results = append(results, check(podMetadata, podSpec))
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateChecks(checks []Check) error {
|
func validateChecks(checks []Check) error {
|
||||||
ids := map[string]bool{}
|
ids := map[CheckID]api.Level{}
|
||||||
for _, check := range checks {
|
for _, check := range checks {
|
||||||
if ids[check.ID] {
|
if _, ok := ids[check.ID]; ok {
|
||||||
return fmt.Errorf("multiple checks registered for ID %s", check.ID)
|
return fmt.Errorf("multiple checks registered for ID %s", check.ID)
|
||||||
}
|
}
|
||||||
ids[check.ID] = true
|
ids[check.ID] = check.Level
|
||||||
if check.Level != api.LevelBaseline && check.Level != api.LevelRestricted {
|
if check.Level != api.LevelBaseline && check.Level != api.LevelRestricted {
|
||||||
return fmt.Errorf("check %s: invalid level %s", check.ID, check.Level)
|
return fmt.Errorf("check %s: invalid level %s", check.ID, check.Level)
|
||||||
}
|
}
|
||||||
@ -107,6 +111,23 @@ func validateChecks(checks []Check) error {
|
|||||||
maxVersion = c.MinimumVersion
|
maxVersion = c.MinimumVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Second pass to validate overrides.
|
||||||
|
for _, check := range checks {
|
||||||
|
for _, c := range check.Versions {
|
||||||
|
if len(c.OverrideCheckIDs) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if check.Level != api.LevelRestricted {
|
||||||
|
return fmt.Errorf("check %s: only restricted checks may set overrides", check.ID)
|
||||||
|
}
|
||||||
|
for _, override := range c.OverrideCheckIDs {
|
||||||
|
if overriddenLevel, ok := ids[override]; ok && overriddenLevel != api.LevelBaseline {
|
||||||
|
return fmt.Errorf("check %s: overrides %s check %s", check.ID, overriddenLevel, override)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,28 +140,87 @@ func populate(r *checkRegistry, validChecks []Check) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
restrictedVersionedChecks = map[api.Version]map[CheckID]VersionedCheck{}
|
||||||
|
baselineVersionedChecks = map[api.Version]map[CheckID]VersionedCheck{}
|
||||||
|
|
||||||
|
baselineIDs, restrictedIDs []CheckID
|
||||||
|
)
|
||||||
for _, c := range validChecks {
|
for _, c := range validChecks {
|
||||||
if c.Level == api.LevelRestricted {
|
if c.Level == api.LevelRestricted {
|
||||||
inflateVersions(c, r.restrictedChecks, r.maxVersion)
|
restrictedIDs = append(restrictedIDs, c.ID)
|
||||||
|
inflateVersions(c, restrictedVersionedChecks, r.maxVersion)
|
||||||
} else {
|
} else {
|
||||||
inflateVersions(c, r.baselineChecks, r.maxVersion)
|
baselineIDs = append(baselineIDs, c.ID)
|
||||||
|
inflateVersions(c, baselineVersionedChecks, r.maxVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort the IDs to maintain consistent error messages.
|
||||||
|
sort.Slice(restrictedIDs, func(i, j int) bool { return restrictedIDs[i] < restrictedIDs[j] })
|
||||||
|
sort.Slice(baselineIDs, func(i, j int) bool { return baselineIDs[i] < baselineIDs[j] })
|
||||||
|
orderedIDs := append(baselineIDs, restrictedIDs...) // Baseline checks first, then restricted.
|
||||||
|
|
||||||
|
for v := api.MajorMinorVersion(1, 0); v.Older(nextMinor(r.maxVersion)); v = nextMinor(v) {
|
||||||
|
// Aggregate all the overridden baseline check ids.
|
||||||
|
overrides := map[CheckID]bool{}
|
||||||
|
for _, c := range restrictedVersionedChecks[v] {
|
||||||
|
for _, override := range c.OverrideCheckIDs {
|
||||||
|
overrides[override] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add the filtered baseline checks to restricted.
|
||||||
|
for id, c := range baselineVersionedChecks[v] {
|
||||||
|
if overrides[id] {
|
||||||
|
continue // Overridden check: skip it.
|
||||||
|
}
|
||||||
|
if restrictedVersionedChecks[v] == nil {
|
||||||
|
restrictedVersionedChecks[v] = map[CheckID]VersionedCheck{}
|
||||||
|
}
|
||||||
|
restrictedVersionedChecks[v][id] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
r.restrictedChecks[v] = mapCheckPodFns(restrictedVersionedChecks[v], orderedIDs)
|
||||||
|
r.baselineChecks[v] = mapCheckPodFns(baselineVersionedChecks[v], orderedIDs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func inflateVersions(check Check, versions map[api.Version][]CheckPodFn, maxVersion api.Version) {
|
func inflateVersions(check Check, versions map[api.Version]map[CheckID]VersionedCheck, maxVersion api.Version) {
|
||||||
for i, c := range check.Versions {
|
for i, c := range check.Versions {
|
||||||
var nextVersion api.Version
|
var nextVersion api.Version
|
||||||
if i+1 < len(check.Versions) {
|
if i+1 < len(check.Versions) {
|
||||||
nextVersion = check.Versions[i+1].MinimumVersion
|
nextVersion = check.Versions[i+1].MinimumVersion
|
||||||
} else {
|
} else {
|
||||||
// Assumes only 1 Major version.
|
// Assumes only 1 Major version.
|
||||||
nextVersion = api.MajorMinorVersion(1, maxVersion.Minor()+1)
|
nextVersion = nextMinor(maxVersion)
|
||||||
}
|
}
|
||||||
// Iterate over all versions from the minimum of the current check, to the minimum of the
|
// Iterate over all versions from the minimum of the current check, to the minimum of the
|
||||||
// next check, or the maxVersion++.
|
// next check, or the maxVersion++.
|
||||||
for v := c.MinimumVersion; v.Older(nextVersion); v = api.MajorMinorVersion(1, v.Minor()+1) {
|
for v := c.MinimumVersion; v.Older(nextVersion); v = nextMinor(v) {
|
||||||
versions[v] = append(versions[v], check.Versions[i].CheckPod)
|
if versions[v] == nil {
|
||||||
|
versions[v] = map[CheckID]VersionedCheck{}
|
||||||
|
}
|
||||||
|
versions[v][check.ID] = check.Versions[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mapCheckPodFns converts the versioned check map to an ordered slice of CheckPodFn,
|
||||||
|
// using the order specified by orderedIDs. All checks must have a corresponding ID in orderedIDs.
|
||||||
|
func mapCheckPodFns(checks map[CheckID]VersionedCheck, orderedIDs []CheckID) []CheckPodFn {
|
||||||
|
fns := make([]CheckPodFn, 0, len(checks))
|
||||||
|
for _, id := range orderedIDs {
|
||||||
|
if check, ok := checks[id]; ok {
|
||||||
|
fns = append(fns, check.CheckPod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fns
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextMinor increments the minor version
|
||||||
|
func nextMinor(v api.Version) api.Version {
|
||||||
|
if v.Latest() {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return api.MajorMinorVersion(v.Major(), v.Minor()+1)
|
||||||
|
}
|
||||||
|
@ -35,16 +35,18 @@ func TestCheckRegistry(t *testing.T) {
|
|||||||
generateCheck("d", api.LevelBaseline, []string{"v1.11", "v1.15", "v1.20"}),
|
generateCheck("d", api.LevelBaseline, []string{"v1.11", "v1.15", "v1.20"}),
|
||||||
generateCheck("e", api.LevelRestricted, []string{"v1.0"}),
|
generateCheck("e", api.LevelRestricted, []string{"v1.0"}),
|
||||||
generateCheck("f", api.LevelRestricted, []string{"v1.12", "v1.16", "v1.21"}),
|
generateCheck("f", api.LevelRestricted, []string{"v1.12", "v1.16", "v1.21"}),
|
||||||
|
withOverrides(generateCheck("g", api.LevelRestricted, []string{"v1.10"}), []CheckID{"a"}),
|
||||||
|
withOverrides(generateCheck("h", api.LevelRestricted, []string{"v1.0"}), []CheckID{"b"}),
|
||||||
}
|
}
|
||||||
|
multiOverride := generateCheck("i", api.LevelRestricted, []string{"v1.10", "v1.21"})
|
||||||
|
multiOverride.Versions[0].OverrideCheckIDs = []CheckID{"c"}
|
||||||
|
multiOverride.Versions[1].OverrideCheckIDs = []CheckID{"d"}
|
||||||
|
checks = append(checks, multiOverride)
|
||||||
|
|
||||||
reg, err := NewEvaluator(checks)
|
reg, err := NewEvaluator(checks)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
levelCases := []struct {
|
levelCases := []registryTestCase{
|
||||||
level api.Level
|
|
||||||
version string
|
|
||||||
expectedReasons []string
|
|
||||||
}{
|
|
||||||
{api.LevelPrivileged, "v1.0", nil},
|
{api.LevelPrivileged, "v1.0", nil},
|
||||||
{api.LevelPrivileged, "latest", nil},
|
{api.LevelPrivileged, "latest", nil},
|
||||||
{api.LevelBaseline, "v1.0", []string{"a:v1.0", "c:v1.0"}},
|
{api.LevelBaseline, "v1.0", []string{"a:v1.0", "c:v1.0"}},
|
||||||
@ -53,29 +55,112 @@ func TestCheckRegistry(t *testing.T) {
|
|||||||
{api.LevelBaseline, "v1.10", []string{"a:v1.0", "b:v1.10", "c:v1.10"}},
|
{api.LevelBaseline, "v1.10", []string{"a:v1.0", "b:v1.10", "c:v1.10"}},
|
||||||
{api.LevelBaseline, "v1.11", []string{"a:v1.0", "b:v1.10", "c:v1.10", "d:v1.11"}},
|
{api.LevelBaseline, "v1.11", []string{"a:v1.0", "b:v1.10", "c:v1.10", "d:v1.11"}},
|
||||||
{api.LevelBaseline, "latest", []string{"a:v1.0", "b:v1.10", "c:v1.10", "d:v1.20"}},
|
{api.LevelBaseline, "latest", []string{"a:v1.0", "b:v1.10", "c:v1.10", "d:v1.20"}},
|
||||||
{api.LevelRestricted, "v1.0", []string{"a:v1.0", "c:v1.0", "e:v1.0"}},
|
{api.LevelRestricted, "v1.0", []string{"a:v1.0", "c:v1.0", "e:v1.0", "h:v1.0"}},
|
||||||
{api.LevelRestricted, "v1.4", []string{"a:v1.0", "c:v1.0", "e:v1.0"}},
|
{api.LevelRestricted, "v1.4", []string{"a:v1.0", "c:v1.0", "e:v1.0", "h:v1.0"}},
|
||||||
{api.LevelRestricted, "v1.5", []string{"a:v1.0", "c:v1.5", "e:v1.0"}},
|
{api.LevelRestricted, "v1.5", []string{"a:v1.0", "c:v1.5", "e:v1.0", "h:v1.0"}},
|
||||||
{api.LevelRestricted, "v1.10", []string{"a:v1.0", "b:v1.10", "c:v1.10", "e:v1.0"}},
|
{api.LevelRestricted, "v1.10", []string{"e:v1.0", "g:v1.10", "h:v1.0", "i:v1.10"}},
|
||||||
{api.LevelRestricted, "v1.11", []string{"a:v1.0", "b:v1.10", "c:v1.10", "d:v1.11", "e:v1.0"}},
|
{api.LevelRestricted, "v1.11", []string{"d:v1.11", "e:v1.0", "g:v1.10", "h:v1.0", "i:v1.10"}},
|
||||||
{api.LevelRestricted, "latest", []string{"a:v1.0", "b:v1.10", "c:v1.10", "d:v1.20", "e:v1.0", "f:v1.21"}},
|
{api.LevelRestricted, "latest", []string{"c:v1.10", "e:v1.0", "f:v1.21", "g:v1.10", "h:v1.0", "i:v1.21"}},
|
||||||
{api.LevelRestricted, "v1.10000", []string{"a:v1.0", "b:v1.10", "c:v1.10", "d:v1.20", "e:v1.0", "f:v1.21"}},
|
{api.LevelRestricted, "v1.10000", []string{"c:v1.10", "e:v1.0", "f:v1.21", "g:v1.10", "h:v1.0", "i:v1.21"}},
|
||||||
}
|
}
|
||||||
for _, test := range levelCases {
|
for _, test := range levelCases {
|
||||||
t.Run(fmt.Sprintf("%s:%s", test.level, test.version), func(t *testing.T) {
|
test.Run(t, reg)
|
||||||
results := reg.EvaluatePod(api.LevelVersion{test.level, versionOrPanic(test.version)}, nil, nil)
|
|
||||||
|
|
||||||
// Set extract the ForbiddenReasons from the results.
|
|
||||||
var actualReasons []string
|
|
||||||
for _, result := range results {
|
|
||||||
actualReasons = append(actualReasons, result.ForbiddenReason)
|
|
||||||
}
|
|
||||||
assert.ElementsMatch(t, test.expectedReasons, actualReasons)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateCheck(id string, level api.Level, versions []string) Check {
|
func TestCheckRegistry_NoBaseline(t *testing.T) {
|
||||||
|
checks := []Check{
|
||||||
|
generateCheck("e", api.LevelRestricted, []string{"v1.0"}),
|
||||||
|
generateCheck("f", api.LevelRestricted, []string{"v1.12", "v1.16", "v1.21"}),
|
||||||
|
withOverrides(generateCheck("g", api.LevelRestricted, []string{"v1.10"}), []CheckID{"a"}),
|
||||||
|
withOverrides(generateCheck("h", api.LevelRestricted, []string{"v1.0"}), []CheckID{"b"}),
|
||||||
|
}
|
||||||
|
|
||||||
|
reg, err := NewEvaluator(checks)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
levelCases := []registryTestCase{
|
||||||
|
{api.LevelPrivileged, "v1.0", nil},
|
||||||
|
{api.LevelPrivileged, "latest", nil},
|
||||||
|
{api.LevelBaseline, "v1.0", nil},
|
||||||
|
{api.LevelBaseline, "v1.10", nil},
|
||||||
|
{api.LevelBaseline, "latest", nil},
|
||||||
|
{api.LevelRestricted, "v1.0", []string{"e:v1.0", "h:v1.0"}},
|
||||||
|
{api.LevelRestricted, "v1.10", []string{"e:v1.0", "g:v1.10", "h:v1.0"}},
|
||||||
|
{api.LevelRestricted, "latest", []string{"e:v1.0", "f:v1.21", "g:v1.10", "h:v1.0"}},
|
||||||
|
{api.LevelRestricted, "v1.10000", []string{"e:v1.0", "f:v1.21", "g:v1.10", "h:v1.0"}},
|
||||||
|
}
|
||||||
|
for _, test := range levelCases {
|
||||||
|
test.Run(t, reg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckRegistry_NoRestricted(t *testing.T) {
|
||||||
|
checks := []Check{
|
||||||
|
generateCheck("a", api.LevelBaseline, []string{"v1.0"}),
|
||||||
|
generateCheck("b", api.LevelBaseline, []string{"v1.10"}),
|
||||||
|
generateCheck("c", api.LevelBaseline, []string{"v1.0", "v1.5", "v1.10"}),
|
||||||
|
generateCheck("d", api.LevelBaseline, []string{"v1.11", "v1.15", "v1.20"}),
|
||||||
|
}
|
||||||
|
|
||||||
|
reg, err := NewEvaluator(checks)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
levelCases := []registryTestCase{
|
||||||
|
{api.LevelBaseline, "v1.0", []string{"a:v1.0", "c:v1.0"}},
|
||||||
|
{api.LevelBaseline, "v1.4", []string{"a:v1.0", "c:v1.0"}},
|
||||||
|
{api.LevelBaseline, "v1.5", []string{"a:v1.0", "c:v1.5"}},
|
||||||
|
{api.LevelBaseline, "v1.10", []string{"a:v1.0", "b:v1.10", "c:v1.10"}},
|
||||||
|
{api.LevelBaseline, "v1.11", []string{"a:v1.0", "b:v1.10", "c:v1.10", "d:v1.11"}},
|
||||||
|
{api.LevelBaseline, "latest", []string{"a:v1.0", "b:v1.10", "c:v1.10", "d:v1.20"}},
|
||||||
|
}
|
||||||
|
for _, test := range levelCases {
|
||||||
|
test.Run(t, reg)
|
||||||
|
// Restricted results should be identical to baseline.
|
||||||
|
restrictedTest := test
|
||||||
|
restrictedTest.level = api.LevelRestricted
|
||||||
|
restrictedTest.Run(t, reg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckRegistry_Empty(t *testing.T) {
|
||||||
|
reg, err := NewEvaluator(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
levelCases := []registryTestCase{
|
||||||
|
{api.LevelPrivileged, "latest", nil},
|
||||||
|
{api.LevelBaseline, "latest", nil},
|
||||||
|
{api.LevelRestricted, "latest", nil},
|
||||||
|
}
|
||||||
|
for _, test := range levelCases {
|
||||||
|
test.Run(t, reg)
|
||||||
|
// Restricted results should be identical to baseline.
|
||||||
|
restrictedTest := test
|
||||||
|
restrictedTest.level = api.LevelRestricted
|
||||||
|
restrictedTest.Run(t, reg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type registryTestCase struct {
|
||||||
|
level api.Level
|
||||||
|
version string
|
||||||
|
expectedReasons []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *registryTestCase) Run(t *testing.T, registry Evaluator) {
|
||||||
|
t.Run(fmt.Sprintf("%s:%s", tc.level, tc.version), func(t *testing.T) {
|
||||||
|
results := registry.EvaluatePod(api.LevelVersion{tc.level, versionOrPanic(tc.version)}, nil, nil)
|
||||||
|
|
||||||
|
// Set extract the ForbiddenReasons from the results.
|
||||||
|
var actualReasons []string
|
||||||
|
for _, result := range results {
|
||||||
|
actualReasons = append(actualReasons, result.ForbiddenReason)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tc.expectedReasons, actualReasons)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCheck(id CheckID, level api.Level, versions []string) Check {
|
||||||
c := Check{
|
c := Check{
|
||||||
ID: id,
|
ID: id,
|
||||||
Level: level,
|
Level: level,
|
||||||
@ -94,6 +179,13 @@ func generateCheck(id string, level api.Level, versions []string) Check {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withOverrides(c Check, overrides []CheckID) Check {
|
||||||
|
for i := range c.Versions {
|
||||||
|
c.Versions[i].OverrideCheckIDs = overrides
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
func versionOrPanic(v string) api.Version {
|
func versionOrPanic(v string) api.Version {
|
||||||
ver, err := api.ParseVersion(v)
|
ver, err := api.ParseVersion(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -99,7 +99,7 @@ var fixtureGenerators = map[fixtureKey]fixtureGenerator{}
|
|||||||
type fixtureKey struct {
|
type fixtureKey struct {
|
||||||
version api.Version
|
version api.Version
|
||||||
level api.Level
|
level api.Level
|
||||||
check string
|
check policy.CheckID
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixtureGenerator holds generators for valid and invalid fixtures.
|
// fixtureGenerator holds generators for valid and invalid fixtures.
|
||||||
@ -184,7 +184,7 @@ func getFixtures(key fixtureKey) (fixtureData, error) {
|
|||||||
fail: generator.generateFail(validPodForLevel.DeepCopy()),
|
fail: generator.generateFail(validPodForLevel.DeepCopy()),
|
||||||
}
|
}
|
||||||
if len(data.expectErrorSubstring) == 0 {
|
if len(data.expectErrorSubstring) == 0 {
|
||||||
data.expectErrorSubstring = key.check
|
data.expectErrorSubstring = string(key.check)
|
||||||
}
|
}
|
||||||
if len(data.fail) == 0 {
|
if len(data.fail) == 0 {
|
||||||
return fixtureData{}, fmt.Errorf("generateFail for %#v must return at least one pod", key)
|
return fixtureData{}, fmt.Errorf("generateFail for %#v must return at least one pod", key)
|
||||||
|
@ -47,7 +47,7 @@ func ensureCapabilities(p *corev1.Pod) *corev1.Pod {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
fixtureData_1_0 := fixtureGenerator{
|
fixtureData_1_0 := fixtureGenerator{
|
||||||
expectErrorSubstring: "non-default capabilities",
|
expectErrorSubstring: "capabilities",
|
||||||
generatePass: func(p *corev1.Pod) []*corev1.Pod {
|
generatePass: func(p *corev1.Pod) []*corev1.Pod {
|
||||||
// don't generate fixtures if minimal valid pod drops ALL
|
// don't generate fixtures if minimal valid pod drops ALL
|
||||||
if p.Spec.Containers[0].SecurityContext != nil && p.Spec.Containers[0].SecurityContext.Capabilities != nil {
|
if p.Spec.Containers[0].SecurityContext != nil && p.Spec.Containers[0].SecurityContext.Capabilities != nil {
|
||||||
|
@ -28,7 +28,7 @@ TODO: include field paths in reflect-based unit test
|
|||||||
func init() {
|
func init() {
|
||||||
|
|
||||||
fixtureData_1_0 := fixtureGenerator{
|
fixtureData_1_0 := fixtureGenerator{
|
||||||
expectErrorSubstring: "hostPath volumes",
|
expectErrorSubstring: "hostPath",
|
||||||
generatePass: func(p *corev1.Pod) []*corev1.Pod {
|
generatePass: func(p *corev1.Pod) []*corev1.Pod {
|
||||||
// minimal valid pod already captures all valid combinations
|
// minimal valid pod already captures all valid combinations
|
||||||
return nil
|
return nil
|
||||||
|
@ -82,10 +82,10 @@ func TestFixtures(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, pod := range checkData.pass {
|
for i, pod := range checkData.pass {
|
||||||
expectedFiles.Insert(testFixtureFile(t, passDir, fmt.Sprintf("%s%d", strings.ToLower(checkID), i), pod))
|
expectedFiles.Insert(testFixtureFile(t, passDir, fmt.Sprintf("%s%d", strings.ToLower(string(checkID)), i), pod))
|
||||||
}
|
}
|
||||||
for i, pod := range checkData.fail {
|
for i, pod := range checkData.fail {
|
||||||
expectedFiles.Insert(testFixtureFile(t, failDir, fmt.Sprintf("%s%d", strings.ToLower(checkID), i), pod))
|
expectedFiles.Insert(testFixtureFile(t, failDir, fmt.Sprintf("%s%d", strings.ToLower(string(checkID)), i), pod))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,8 +73,8 @@ func toJSON(pod *corev1.Pod) string {
|
|||||||
// checksForLevelAndVersion returns the set of check IDs that apply when evaluating the given level and version.
|
// checksForLevelAndVersion returns the set of check IDs that apply when evaluating the given level and version.
|
||||||
// checks are assumed to be well-formed and valid to pass to policy.NewEvaluator().
|
// checks are assumed to be well-formed and valid to pass to policy.NewEvaluator().
|
||||||
// level must be api.LevelRestricted or api.LevelBaseline
|
// level must be api.LevelRestricted or api.LevelBaseline
|
||||||
func checksForLevelAndVersion(checks []policy.Check, level api.Level, version api.Version) ([]string, error) {
|
func checksForLevelAndVersion(checks []policy.Check, level api.Level, version api.Version) ([]policy.CheckID, error) {
|
||||||
retval := []string{}
|
retval := []policy.CheckID{}
|
||||||
for _, check := range checks {
|
for _, check := range checks {
|
||||||
if !version.Older(check.Versions[0].MinimumVersion) && (level == check.Level || level == api.LevelRestricted) {
|
if !version.Older(check.Versions[0].MinimumVersion) && (level == check.Level || level == api.LevelRestricted) {
|
||||||
retval = append(retval, check.ID)
|
retval = append(retval, check.ID)
|
||||||
@ -318,7 +318,7 @@ func Run(t *testing.T, opts Options) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run(ns+"_pass_"+checkID, func(t *testing.T) {
|
t.Run(ns+"_pass_"+string(checkID), func(t *testing.T) {
|
||||||
for i, pod := range checkData.pass {
|
for i, pod := range checkData.pass {
|
||||||
createPod(t, i, pod, true, "")
|
createPod(t, i, pod, true, "")
|
||||||
createController(t, i, pod, true, "")
|
createController(t, i, pod, true, "")
|
||||||
@ -332,7 +332,7 @@ func Run(t *testing.T, opts Options) {
|
|||||||
disabledRequiredFeatures = append(disabledRequiredFeatures, f)
|
disabledRequiredFeatures = append(disabledRequiredFeatures, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.Run(ns+"_fail_"+checkID, func(t *testing.T) {
|
t.Run(ns+"_fail_"+string(checkID), func(t *testing.T) {
|
||||||
if len(disabledRequiredFeatures) > 0 {
|
if len(disabledRequiredFeatures) > 0 {
|
||||||
t.Skipf("features required for failure cases are disabled: %v", disabledRequiredFeatures)
|
t.Skipf("features required for failure cases are disabled: %v", disabledRequiredFeatures)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user