Merge pull request #2487 from vardhaman22/test/rancher-cis-benchmark

hull tests for rancher-cis-benchmark chart
pull/2512/head
Vardhaman Surana 2023-05-11 22:45:24 +05:30 committed by GitHub
commit 872076dc31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 2212 additions and 0 deletions

View File

@ -0,0 +1,66 @@
package common
import (
"testing"
"github.com/rancher/hull/pkg/checker"
"github.com/rancher/hull/pkg/test"
"github.com/stretchr/testify/assert"
policyv1 "k8s.io/api/policy/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
rbacv1 "k8s.io/kubernetes/pkg/apis/rbac"
)
func EnsurePSPsExist(numExpectedPSPs int) test.Checks {
return test.Checks{
checker.OnResources(func(tc *checker.TestContext, pspList []*policyv1.PodSecurityPolicy) {
pspsEnabled, _ := checker.RenderValue[bool](tc, ".Values.global.cattle.psp.enabled")
if pspsEnabled {
assert.Equal(tc.T, numExpectedPSPs, len(pspList),
"total number of PSPs mismatch, expected:%d, got: %d",
numExpectedPSPs, len(pspList))
}
}),
onRoles(func(tc *checker.TestContext, objRules map[metav1.Object][]rbacv1.PolicyRule) {
pspsEnabled, _ := checker.RenderValue[bool](tc, ".Values.global.cattle.psp.enabled")
if !pspsEnabled {
for obj, rules := range objRules {
for _, rule := range rules {
for _, resource := range rule.Resources {
if resource == "podsecuritypolicies" {
tc.T.Errorf("err: Role %s has reference to psp resources with psp disabled", obj.GetName())
return
}
}
}
}
}
}),
}
}
func onRoles(typeCheckFunc func(tc *checker.TestContext, rules map[metav1.Object][]rbacv1.PolicyRule)) checker.ChainedCheckFunc {
return func(tc *checker.TestContext) checker.CheckFunc {
return func(t *testing.T, objs struct {
ClusterRoles []*rbacv1.ClusterRole
Roles []*rbacv1.Role
}) {
tc.T = t
rules := make(map[metav1.Object][]rbacv1.PolicyRule)
for _, obj := range objs.ClusterRoles {
rules[obj] = obj.Rules
}
for _, obj := range objs.Roles {
rules[obj] = obj.Rules
}
typeCheckFunc(tc, rules)
}
}
}

11
tests/common/helpers.go Normal file
View File

@ -0,0 +1,11 @@
package common
import "github.com/rancher/hull/pkg/checker"
func GetSystemDefaultRegistry(tc *checker.TestContext) string {
systemDefaultRegistry, _ := checker.RenderValue[string](tc, ".Values.global.cattle.systemDefaultRegistry")
if systemDefaultRegistry != "" {
systemDefaultRegistry += "/"
}
return systemDefaultRegistry
}

View File

@ -0,0 +1,120 @@
package common
import (
"fmt"
"strings"
"github.com/rancher/hull/pkg/checker"
"github.com/rancher/hull/pkg/test"
"github.com/rancher/wrangler/pkg/relatedresource"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Check that every Workload has a ServiceAccount deployed with it
var AllWorkloadsHaveServiceAccount = test.Checks{
checker.PerWorkload(func(tc *checker.TestContext, obj metav1.Object, podTemplateSpec corev1.PodTemplateSpec) {
namespace := obj.GetNamespace()
if namespace == "" {
annotations := obj.GetAnnotations()
if annotations["helm.sh/hook"] != "" {
releaseNamespace, exists := checker.RenderValue[string](tc, ".Release.Namespace")
if exists {
namespace = releaseNamespace
}
}
}
key := relatedresource.NewKey(
namespace,
podTemplateSpec.Spec.ServiceAccountName,
)
checker.MapSet(tc, "ServiceAccountsToCheck", key, false)
}),
checker.PerResource(func(tc *checker.TestContext, serviceAccount *corev1.ServiceAccount) {
key := checker.Key(serviceAccount)
_, exists := checker.MapGet[string, relatedresource.Key, bool](tc, "ServiceAccountsToCheck", key)
if !exists {
// does not belong to any workload
tc.T.Logf("warn: serviceaccount %s is not tied to any workload", key)
return
}
checker.MapSet(tc, "ServiceAccountsToCheck", key, true)
}),
checker.Once(func(tc *checker.TestContext) {
checker.MapFor(tc, "ServiceAccountsToCheck", func(key relatedresource.Key, exists bool) {
assert.True(tc.T, exists, "serviceaccount %s is not in this chart", key)
})
}),
}
var AllWorkloadsHaveNodeSelectorsAndTolerationsForOS = test.Checks{
checker.PerWorkload(func(tc *checker.TestContext, obj metav1.Object, podTemplateSpec corev1.PodTemplateSpec) {
nodeSelector := podTemplateSpec.Spec.NodeSelector
betaOSVal, hasBetaOSAnnotation := nodeSelector["beta.kubernetes.io/os"]
osVal, hasOSAnnotation := nodeSelector["kubernetes.io/os"]
if hasBetaOSAnnotation && hasOSAnnotation {
assert.Equal(tc.T, osVal, betaOSVal, fmt.Sprintf("%T %s is has conflicting values for nodeSelector beta.kubernetes.io/os or kubernetes.io/os", obj, checker.Key(obj)))
}
if hasBetaOSAnnotation {
if betaOSVal == "windows" {
checker.MapSet(tc, "Windows Workload", &podTemplateSpec, true)
}
tc.T.Logf("warn: beta.kubernetes.io/os nodeSelector has been deprecated but is used in %T %s", obj, checker.Key(obj))
assert.Contains(tc.T, []string{"linux", "windows"}, betaOSVal, fmt.Sprintf("%T %s cannot have value for beta.kubernetes.io/os that is not 'linux' or 'windows': found %s", obj, checker.Key(obj), betaOSVal))
}
if hasOSAnnotation {
if osVal == "windows" {
checker.MapSet(tc, "Windows Workload", &obj, true)
}
assert.Contains(tc.T, []string{"linux", "windows"}, osVal, fmt.Sprintf("%T %s cannot have value for kubernetes.io/os that is not 'linux' or 'windows': found %s", obj, checker.Key(obj), osVal))
}
assert.False(tc.T, !hasBetaOSAnnotation && !hasOSAnnotation, fmt.Sprintf("%T %s is missing OS key for nodeSelector, expected to find either beta.kubernetes.io/os or kubernetes.io/os", obj, checker.Key(obj)))
}),
checker.PerWorkload(func(tc *checker.TestContext, obj metav1.Object, podTemplateSpec corev1.PodTemplateSpec) {
isWindowsWorkload, _ := checker.MapGet[string, *metav1.Object, bool](tc, "Windows Workload", &obj)
if isWindowsWorkload {
// no need to check for tolerations
return
}
tolerations := podTemplateSpec.Spec.Tolerations
var foundToleration bool
for _, toleration := range tolerations {
if toleration.Key != "cattle.io/os" {
continue
}
if toleration.Value != "linux" {
continue
}
if toleration.Effect != "NoSchedule" {
continue
}
if toleration.Operator != "Equal" {
continue
}
foundToleration = true
}
assert.True(tc.T, foundToleration, "could not find toleration in workload %T %s that tolerates the NoSchedule 'cattle.io/os: linux' taint", obj, checker.Key(obj))
}),
}
var AllContainerImagesShouldHaveSystemDefaultRegistryPrefix = test.Checks{
checker.PerWorkload(func(tc *checker.TestContext, obj metav1.Object, podTemplateSpec corev1.PodTemplateSpec) {
systemDefaultRegistry := GetSystemDefaultRegistry(tc)
for _, container := range podTemplateSpec.Spec.Containers {
if !strings.HasPrefix(container.Image, systemDefaultRegistry) {
tc.T.Errorf("err: contianer %s of object %s does not have systemDefaultRegistry(%s) prefix for image(%s)",
container.Name, obj.GetName(), systemDefaultRegistry, container.Image)
}
}
}),
}

143
tests/go.mod Normal file
View File

@ -0,0 +1,143 @@
module github.com/rancher/charts/tests
go 1.19
require (
github.com/rancher/hull v0.0.0-20230329190516-51af9be6b929
github.com/rancher/wrangler v1.1.1
github.com/stretchr/testify v1.8.2
k8s.io/api v0.26.2
k8s.io/apimachinery v0.26.2
k8s.io/kubernetes v1.26.2
)
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Masterminds/squirrel v1.5.3 // indirect
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/containerd/containerd v1.6.15 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.21+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v20.10.21+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-git/go-billy/v5 v5.0.0 // indirect
github.com/go-gorp/gorp/v3 v3.0.2 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/iancoleman/strcase v0.2.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rancher/lasso v0.0.0-20221227210133-6ea88ca2fbcc // indirect
github.com/rubenv/sql-migrate v1.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xlab/treeprint v1.1.0 // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
google.golang.org/grpc v1.49.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
helm.sh/helm/v3 v3.11.1 // indirect
k8s.io/apiextensions-apiserver v0.26.0 // indirect
k8s.io/apiserver v0.26.0 // indirect
k8s.io/cli-runtime v0.26.0 // indirect
k8s.io/client-go v0.26.0 // indirect
k8s.io/component-base v0.26.0 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/kube-aggregator v0.25.4 // indirect
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
k8s.io/kubectl v0.26.0 // indirect
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
oras.land/oras-go v1.2.2 // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/kustomize/api v0.12.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

1051
tests/go.sum Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,719 @@
package rancher_cis_benchmark
import (
"encoding/json"
"fmt"
"github.com/rancher/charts/tests/common"
"github.com/rancher/hull/pkg/chart"
"github.com/rancher/hull/pkg/checker"
"github.com/rancher/hull/pkg/test"
"github.com/rancher/hull/pkg/utils"
"github.com/stretchr/testify/assert"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var ChartPath = utils.MustGetLatestChartVersionPathFromIndex("../index.yaml", "rancher-cis-benchmark", true)
const (
DefaultReleaseName = "rancher-cis-benchmark"
DefaultNamespace = "cis-operator-system"
CisOperatorDeployExistsCheck = "CisOperatorDeploymentExistsCheck"
FoundKey = "found"
PatchSaJobExistsCheck = "PatchSaJobExistsCheck"
)
var suite = test.Suite{
ChartPath: ChartPath,
Cases: []test.Case{
{
Name: "Using Defaults",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace),
},
{
Name: "Set Values.global.cattle.systemDefaultRegistry",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
SetValue("global.cattle.systemDefaultRegistry", "test-registry"),
},
{
Name: "Set Values.tolerations",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("tolerations", testTolerations),
},
{
Name: "Set Values.nodeSelector",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("nodeSelector", testNodeSelector),
},
{
Name: "Set .Values.global.cattle.psp.enabled to true",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("global.cattle.psp.enabled", true),
},
{
Name: "Set .Values.global.cattle.psp.enabled to false",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("global.cattle.psp.enabled", false),
},
{
Name: "Set Values.global.cattle.clusterName",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
SetValue("global.cattle.clusterName", "test-cluster"),
},
{
Name: "Set .Values.global.kubectl.repository and .Values.global.kubectl.tag",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("global", map[string]string{
"kubectl.repository": "test-kubectl-repo",
"kubectl.tag": "v1.20.11",
}),
},
{
Name: "Set .Values.global.kubectl.imagePullPolicy to IfNotPresent",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
SetValue("global.imagePullPolicy", "IfNotPresent"),
},
{
Name: "Set .Values.global.kubectl.imagePullPolicy to Always",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
SetValue("global.imagePullPolicy", "Always"),
},
{
Name: "Set .Values.global.kubectl.imagePullPolicy to Never",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
SetValue("global.imagePullPolicy", "Never"),
},
{
Name: "Set .Values.image.cisoperator.repository and .Values.image.cisoperator.tag",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("image", map[string]string{
"cisoperator.repository": "test/cis-operator",
"cisoperator.tag": "v1.0.0",
}),
},
{
Name: "Set securityScan and sonobuoy image values",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("image", map[string]string{
"securityScan.repository": "test/security-scan",
"securityScan.tag": "v1.1.0",
"sonobuoy.repository": "test/sonobuoy",
"sonobuoy.tag": "v1.2.0",
}),
},
{
Name: "Set alerts configuration values",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("alerts", map[string]interface{}{
"enabled": true,
"severity": "info",
"metricsPort": 8099,
}),
},
{
Name: "Set .Values.alerts.enabled to false",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("alerts.enabled", false),
},
{
Name: "Set Values.image.cisoperator.debug to true",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("image.cisoperator.debug", true),
},
{
Name: "Set Values.image.cisoperator.debug to false",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("image.cisoperator.debug", false),
},
{
Name: "Set Values.securityScanJob.overrideTolerations to 'true' and securityScanJob.tolerations to []",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("securityScanJob", map[string]interface{}{
"overrideTolerations": true,
"tolerations": []corev1.Toleration{},
}),
},
{
Name: "Set Values.securityScanJob.overrideTolerations to 'true' and securityScanJob.tolerations to testSecScanJobTolerations",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("securityScanJob", map[string]interface{}{
"overrideTolerations": true,
"tolerations": testSecScanJobTolerations,
}),
},
{
Name: "Set Values.securityScanJob.overrideTolerations to 'false' and securityScanJob.tolerations to testSecScanJobTolerations",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("securityScanJob", map[string]interface{}{
"overrideTolerations": false,
"tolerations": testSecScanJobTolerations,
}),
},
{
Name: "Set Values.resources",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("resources", testResources),
},
{
Name: "Set Values.affinity",
TemplateOptions: chart.NewTemplateOptions(DefaultReleaseName, DefaultNamespace).
Set("affinity", testAffinity),
},
},
NamedChecks: []test.NamedCheck{
{
Name: "All Workloads Have Service Account",
Checks: common.AllWorkloadsHaveServiceAccount,
},
{
Name: "All Workloads Have Node Selectors And Tolerations For OS",
Checks: common.AllWorkloadsHaveNodeSelectorsAndTolerationsForOS,
},
{
Name: "All Workload Container Should Have SystemDefaultRegistryPrefix",
Checks: common.AllContainerImagesShouldHaveSystemDefaultRegistryPrefix,
Covers: []string{
"Values.global.cattle.systemDefaultRegistry",
},
},
{
Name: "Check All Workloads Have NodeSelector As Per Given Value",
Covers: []string{
".Values.nodeSelector",
},
Checks: test.Checks{
checker.PerWorkload(func(tc *checker.TestContext, obj metav1.Object, podTemplateSpec corev1.PodTemplateSpec) {
nodeSelectorAddedByValues, _ := checker.RenderValue[map[string]string](tc, ".Values.nodeSelector")
expectedNodeSelector := map[string]string{}
for k, v := range nodeSelectorAddedByValues {
expectedNodeSelector[k] = v
}
for k, v := range defaultNodeSelector {
expectedNodeSelector[k] = v
}
assert.Equal(tc.T,
expectedNodeSelector, podTemplateSpec.Spec.NodeSelector,
"workload %s (type: %T) does not have correct nodeSelectors, expected: %v got: %v",
obj.GetName(), obj, expectedNodeSelector, podTemplateSpec.Spec.NodeSelector,
)
}),
},
},
{
Name: "Check All Workloads Have Tolerations As Per Given Value",
Covers: []string{
".Values.tolerations",
},
Checks: test.Checks{
checker.PerWorkload(func(tc *checker.TestContext, obj metav1.Object, podTemplateSpec corev1.PodTemplateSpec) {
tolerationsAddedByValues, _ := checker.RenderValue[[]corev1.Toleration](tc, ".Values.tolerations")
expectedTolerations := append(defaultTolerations, tolerationsAddedByValues...)
if len(expectedTolerations) == 0 {
expectedTolerations = nil
}
assert.Equal(tc.T,
expectedTolerations, podTemplateSpec.Spec.Tolerations,
"workload %s (type: %T) does not have correct tolerations, expected: %v got: %v",
obj.GetName(), obj, expectedTolerations, podTemplateSpec.Spec.Tolerations,
)
}),
},
},
{
Name: "Check that all job containers have global imagePullPolicy",
Covers: []string{
".Values.global.imagePullPolicy",
},
Checks: test.Checks{
checker.PerResource(func(tc *checker.TestContext, job *batchv1.Job) {
expectedImagePullPolicy, exists := checker.RenderValue[corev1.PullPolicy](tc, ".Values.global.imagePullPolicy")
if exists {
for _, container := range job.Spec.Template.Spec.Containers {
assert.Equal(tc.T,
expectedImagePullPolicy, container.ImagePullPolicy,
"container %s of job %s does not have correct image: expected: %v got: %v",
container.Name, job.Name, expectedImagePullPolicy, container.ImagePullPolicy)
}
}
}),
},
},
{
Name: "Check kubectl image value",
Covers: []string{
".Values.global.kubectl.repository",
".Values.global.kubectl.tag",
".Values.global.cattle.systemDefaultRegistry",
},
Checks: test.Checks{
checker.PerResource(func(tc *checker.TestContext, job *batchv1.Job) {
if job.Name != "patch-sa" {
return
}
checker.MapSet(tc, PatchSaJobExistsCheck, FoundKey, true)
ok := assert.Equal(tc.T, 1, len(job.Spec.Template.Spec.Containers),
"job %s does not have correct number of containers: expected: %d, got: %d",
job.Name, 1, len(job.Spec.Template.Spec.Containers))
if !ok {
return
}
container := job.Spec.Template.Spec.Containers[0]
systemDefaultRegistry := common.GetSystemDefaultRegistry(tc)
kubectlRepo, _ := checker.RenderValue[string](tc, ".Values.global.kubectl.repository")
kubectlTag, _ := checker.RenderValue[string](tc, ".Values.global.kubectl.tag")
containerImage := kubectlRepo + ":" + kubectlTag
expectedContainerImage := systemDefaultRegistry + containerImage
assert.Equal(tc.T,
expectedContainerImage, container.Image,
"container %s of job %s does not have correct image: expected: %v got: %v",
container.Name, job.Name, expectedContainerImage, container.Image)
}),
patchSaJobExistsCheck,
},
},
{
Name: "PSPs Are Created And Referenced As Per .Values.global.cattle.psp.enabled field",
Covers: []string{
".Values.global.cattle.psp.enabled",
},
Checks: common.EnsurePSPsExist(1),
},
{
Name: "Check cisoperator image repository and tag",
Covers: []string{
".Values.image.cisoperator.repository",
".Values.image.cisoperator.tag",
".Values.global.cattle.systemDefaultRegistry",
},
Checks: test.Checks{
checker.PerWorkload(func(tc *checker.TestContext, obj metav1.Object, podTemplateSpec corev1.PodTemplateSpec) {
if obj.GetName() != "cis-operator" {
return
}
checker.MapSet(tc, CisOperatorDeployExistsCheck, FoundKey, true)
ok := assert.Equal(tc.T, 1, len(podTemplateSpec.Spec.Containers),
"deployment %s does not have correct number of container: expected: %d, got: %d",
obj.GetName(), 1, len(podTemplateSpec.Spec.Containers))
if !ok {
return
}
container := podTemplateSpec.Spec.Containers[0]
systemDefaultRegistry := common.GetSystemDefaultRegistry(tc)
cisOperatorRepo, _ := checker.RenderValue[string](tc, ".Values.image.cisoperator.repository")
cisOperatorTag, _ := checker.RenderValue[string](tc, ".Values.image.cisoperator.tag")
containerImage := cisOperatorRepo + ":" + cisOperatorTag
expectedContainerImage := systemDefaultRegistry + containerImage
assert.Equal(tc.T,
expectedContainerImage, container.Image,
"container %s of deployment %s does not have correct image: expected: %v got: %v",
container.Name, obj.GetName(), expectedContainerImage, container.Image)
}),
cisOperatorDeployExistsCheck,
},
},
{
Name: "Check securityScan and sonobuoy images",
Covers: []string{
".Values.image.securityScan.repository",
".Values.image.securityScan.tag",
".Values.image.sonobuoy.repository",
".Values.image.sonobuoy.tag",
},
Checks: test.Checks{
checker.PerWorkload(func(tc *checker.TestContext, obj metav1.Object, podTemplateSpec corev1.PodTemplateSpec) {
if obj.GetName() != "cis-operator" {
return
}
checker.MapSet(tc, CisOperatorDeployExistsCheck, FoundKey, true)
ok := assert.Equal(tc.T, 1, len(podTemplateSpec.Spec.Containers),
"deployment %s does not have correct number of container: expected: %d, got: %d",
obj.GetName(), 1, len(podTemplateSpec.Spec.Containers))
if !ok {
return
}
container := podTemplateSpec.Spec.Containers[0]
assertSecurityScanAndSunobuoyImageValues(tc, container.Env)
}),
cisOperatorDeployExistsCheck,
},
},
{
Name: "Check alerts configuration",
Covers: []string{
".Values.alerts.severity",
".Values.alerts.enabled",
".Values.alerts.metricsPort",
},
Checks: test.Checks{
checker.PerWorkload(func(tc *checker.TestContext, obj metav1.Object, podTemplateSpec corev1.PodTemplateSpec) {
if obj.GetName() != "cis-operator" {
return
}
checker.MapSet(tc, CisOperatorDeployExistsCheck, FoundKey, true)
ok := assert.Equal(tc.T, 1, len(podTemplateSpec.Spec.Containers),
"deployment %s does not have correct number of container: expected: %d, got: %d",
obj.GetName(), 1, len(podTemplateSpec.Spec.Containers))
if !ok {
return
}
container := podTemplateSpec.Spec.Containers[0]
assertAlertsConfigurationValues(tc, container.Env)
}),
cisOperatorDeployExistsCheck,
},
},
{
Name: "Check cisoperator configuration",
Covers: []string{
".Values.global.cattle.clusterName",
".Values.image.cisoperator.debug",
},
Checks: test.Checks{
checker.PerWorkload(func(tc *checker.TestContext, obj metav1.Object, podTemplateSpec corev1.PodTemplateSpec) {
if obj.GetName() != "cis-operator" {
return
}
checker.MapSet(tc, CisOperatorDeployExistsCheck, FoundKey, true)
ok := assert.Equal(tc.T, 1, len(podTemplateSpec.Spec.Containers),
"deployment %s does not have correct number of container: expected: %d, got: %d",
obj.GetName(), 1, len(podTemplateSpec.Spec.Containers))
if !ok {
return
}
container := podTemplateSpec.Spec.Containers[0]
assertCisOperatorConfigurationValues(tc, container.Env)
}),
cisOperatorDeployExistsCheck,
},
},
{
Name: "Check securityScanJob configuration",
Covers: []string{
".Values.securityScanJob.overrideTolerations",
".Values.securityScanJob.tolerations",
},
Checks: test.Checks{
checker.PerWorkload(func(tc *checker.TestContext, obj metav1.Object, podTemplateSpec corev1.PodTemplateSpec) {
if obj.GetName() != "cis-operator" {
return
}
checker.MapSet(tc, CisOperatorDeployExistsCheck, FoundKey, true)
ok := assert.Equal(tc.T, 1, len(podTemplateSpec.Spec.Containers),
"deployment %s does not have correct number of container: expected: %d, got: %d",
obj.GetName(), 1, len(podTemplateSpec.Spec.Containers))
if !ok {
return
}
container := podTemplateSpec.Spec.Containers[0]
assertSecurityScanJobConfigurationValues(tc, container.Env)
}),
cisOperatorDeployExistsCheck,
},
},
{
Name: "Check affinity",
Covers: []string{
".Values.affinity",
},
Checks: test.Checks{
checker.PerWorkload(func(tc *checker.TestContext, obj metav1.Object, podTemplateSpec corev1.PodTemplateSpec) {
if obj.GetName() != "cis-operator" {
return
}
checker.MapSet(tc, CisOperatorDeployExistsCheck, FoundKey, true)
expectedAffinity, _ := checker.RenderValue[*corev1.Affinity](tc, ".Values.affinity")
if expectedAffinity != nil && (*expectedAffinity) == (corev1.Affinity{}) {
expectedAffinity = nil
}
assert.Equal(tc.T,
expectedAffinity, podTemplateSpec.Spec.Affinity,
"deployment %s does not have correct affinity: expected: %v, got: %v",
obj.GetName(), expectedAffinity, podTemplateSpec.Spec.Affinity)
}),
cisOperatorDeployExistsCheck,
},
},
{
Name: "Check resources",
Covers: []string{
".Values.resources",
},
Checks: test.Checks{
checker.PerWorkload(func(tc *checker.TestContext, obj metav1.Object, podTemplateSpec corev1.PodTemplateSpec) {
if obj.GetName() != "cis-operator" {
return
}
checker.MapSet(tc, CisOperatorDeployExistsCheck, FoundKey, true)
ok := assert.Equal(tc.T, 1, len(podTemplateSpec.Spec.Containers),
"deployment %s does not have correct number of container: expected: %d, got: %d",
obj.GetName(), 1, len(podTemplateSpec.Spec.Containers))
if !ok {
return
}
container := podTemplateSpec.Spec.Containers[0]
expectedResourceReq, _ := checker.RenderValue[corev1.ResourceRequirements](tc, ".Values.resources")
assert.Equal(tc.T,
expectedResourceReq, container.Resources,
"container %s of deployment %s does not have correct resources constraint: expected: %v, got: %v",
container.Name, obj.GetName(), expectedResourceReq, container.Resources)
}),
cisOperatorDeployExistsCheck,
},
},
},
}
func assertSecurityScanAndSunobuoyImageValues(tc *checker.TestContext, env []corev1.EnvVar) {
systemDefaultRegistry := common.GetSystemDefaultRegistry(tc)
for _, envVar := range env {
expectedValue := ""
switch envVar.Name {
case "SECURITY_SCAN_IMAGE":
value, _ := checker.RenderValue[string](tc, ".Values.image.securityScan.repository")
expectedValue = systemDefaultRegistry + value
case "SECURITY_SCAN_IMAGE_TAG":
expectedValue, _ = checker.RenderValue[string](tc, ".Values.image.securityScan.tag")
case "SONOBUOY_IMAGE":
value, _ := checker.RenderValue[string](tc, ".Values.image.sonobuoy.repository")
expectedValue = systemDefaultRegistry + value
case "SONOBUOY_IMAGE_TAG":
expectedValue, _ = checker.RenderValue[string](tc, ".Values.image.sonobuoy.tag")
default:
expectedValue = envVar.Value
}
assert.Equal(tc.T,
expectedValue, envVar.Value,
"container of cis-operator deployment does not have correct env value for envKey:%s, expected: %v, got: %v",
envVar.Name, expectedValue, envVar.Value)
}
}
func assertAlertsConfigurationValues(tc *checker.TestContext, env []corev1.EnvVar) {
for _, envVar := range env {
expectedValue := ""
switch envVar.Name {
case "CIS_ALERTS_METRICS_PORT":
metricsPortValue, _ := checker.RenderValue[int](tc, ".Values.alerts.metricsPort")
expectedValue = fmt.Sprintf("%d", metricsPortValue)
case "CIS_ALERTS_SEVERITY":
expectedValue, _ = checker.RenderValue[string](tc, ".Values.alerts.severity")
case "CIS_ALERTS_ENABLED":
alertsEnabled, _ := checker.RenderValue[bool](tc, ".Values.alerts.enabled")
expectedValue = fmt.Sprintf("%t", alertsEnabled)
default:
expectedValue = envVar.Value
}
assert.Equal(tc.T,
expectedValue, envVar.Value,
"container of cis-operator deployment does not have correct env value for envKey:%s, expected: %v, got: %v",
envVar.Name, expectedValue, envVar.Value)
}
}
func assertCisOperatorConfigurationValues(tc *checker.TestContext, env []corev1.EnvVar) {
for _, envVar := range env {
expectedValue := ""
switch envVar.Name {
case "CLUSTER_NAME":
expectedValue, _ = checker.RenderValue[string](tc, ".Values.global.cattle.clusterName")
case "CIS_OPERATOR_DEBUG":
debug, exists := checker.RenderValue[bool](tc, ".Values.image.cisoperator.debug")
if !exists {
tc.T.Logf("warn: .Values.image.cisoperator.debug not set")
continue
}
expectedValue = fmt.Sprintf("%t", debug)
default:
expectedValue = envVar.Value
}
assert.Equal(tc.T,
expectedValue, envVar.Value,
"container of cis-operator deployment does not have correct env value for envKey:%s, expected: %v, got: %v",
envVar.Name, expectedValue, envVar.Value)
}
}
func assertSecurityScanJobConfigurationValues(tc *checker.TestContext, env []corev1.EnvVar) {
for _, envVar := range env {
expectedValue := ""
switch envVar.Name {
case "SECURITY_SCAN_JOB_TOLERATIONS":
overrideScanJobToleration, _ := checker.RenderValue[bool](tc, ".Values.securityScanJob.overrideTolerations")
if overrideScanJobToleration {
tolerations, _ := checker.RenderValue[[]map[string]interface{}](tc, ".Values.securityScanJob.tolerations")
bytes, _ := json.Marshal(tolerations)
expectedValue = string(bytes)
}
default:
expectedValue = envVar.Value
}
assert.Equal(tc.T,
expectedValue, envVar.Value,
"container of cis-operator deployment does not have correct env value for envKey:%s, expected: %v, got: %v",
envVar.Name, expectedValue, envVar.Value)
}
}
var cisOperatorDeployExistsCheck = checker.Once(func(tc *checker.TestContext) {
foundCisOperatorDeploy, _ := checker.MapGet[string, string, bool](tc, CisOperatorDeployExistsCheck, FoundKey)
if !foundCisOperatorDeploy {
tc.T.Error("err: cis-operator depoloyment not found")
}
})
var patchSaJobExistsCheck = checker.Once(func(tc *checker.TestContext) {
foundPatchSaJob, _ := checker.MapGet[string, string, bool](tc, PatchSaJobExistsCheck, FoundKey)
if !foundPatchSaJob {
tc.T.Error("err: patch-sa job not found")
}
})

View File

@ -0,0 +1,15 @@
package rancher_cis_benchmark
import (
"testing"
"github.com/rancher/hull/pkg/test"
)
func TestChart(t *testing.T) {
opts := test.GetRancherOptions()
opts.Coverage.IncludeSubcharts = true
opts.Coverage.Disabled = false
opts.YAMLLint.Enabled = false
suite.Run(t, opts)
}

View File

@ -0,0 +1,87 @@
package rancher_cis_benchmark
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
// for Values.resources
var testResources = corev1.ResourceRequirements{
Limits: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(250, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(64, resource.BinarySI),
},
Requests: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(500, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(120, resource.BinarySI),
},
}
// for Values.nodeSelector
var defaultNodeSelector = map[string]string{
"kubernetes.io/os": "linux",
}
var testNodeSelector = map[string]string{
"test": "testVal",
}
// for Values.tolerations
var defaultTolerations = []corev1.Toleration{
{
Key: "cattle.io/os",
Operator: corev1.TolerationOpEqual,
Value: "linux",
Effect: corev1.TaintEffectNoSchedule,
},
}
var testTolerations = []corev1.Toleration{
{
Key: "test",
Operator: corev1.TolerationOpEqual,
Value: "test",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "test1",
Operator: corev1.TolerationOpExists,
Value: "test1",
Effect: corev1.TaintEffectNoExecute,
},
}
// for Values.securityScanJob.tolerations
var testSecScanJobTolerations = []corev1.Toleration{
{
Key: "test-scan1",
Operator: corev1.TolerationOpEqual,
Value: "test-scan1",
Effect: corev1.TaintEffectNoSchedule,
},
{
Key: "test-scan2",
Operator: corev1.TolerationOpExists,
Value: "test-scan2",
Effect: corev1.TaintEffectNoExecute,
},
}
// for Values.affinity
var testAffinity = &corev1.Affinity{
NodeAffinity: &corev1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
NodeSelectorTerms: []corev1.NodeSelectorTerm{
{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: "test",
Values: []string{"test"},
Operator: corev1.NodeSelectorOpIn,
},
},
},
},
},
},
}