Skip to content

Commit

Permalink
score: allow ignore/allow annotations on child template resources
Browse files Browse the repository at this point in the history
  • Loading branch information
zegl committed Sep 15, 2022
1 parent 6e84736 commit 7120802
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 64 deletions.
6 changes: 5 additions & 1 deletion parser/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,11 @@ func detectFileLocation(fileName string, fileOffset int, fileContents []byte) ks
func (p *Parser) decodeItem(cnf config.Configuration, s *parsedObjects, detectedVersion schema.GroupVersionKind, fileName string, fileOffset int, fileContents []byte) error {
addPodSpeccer := func(ps ks.PodSpecer) {
s.podspecers = append(s.podspecers, ps)
s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: ps.GetTypeMeta(), ObjectMeta: ps.GetObjectMeta(), FileLocationer: ps})
s.bothMetas = append(s.bothMetas, ks.BothMeta{
TypeMeta: ps.GetTypeMeta(),
ObjectMeta: ps.GetObjectMeta(),
FileLocationer: ps,
})
}

fileLocation := detectFileLocation(fileName, fileOffset, fileContents)
Expand Down
23 changes: 23 additions & 0 deletions score/apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package score
import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
)

Expand Down Expand Up @@ -102,3 +105,23 @@ func TestStatefulsetSelectorLabels(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-different-labels.yaml", "StatefulSet Pod Selector labels match template metadata labels", scorecard.GradeCritical)
}

func TestStatefulsetTemplateIgnores(t *testing.T) {
t.Parallel()
skipped := wasSkipped(t, config.Configuration{
UseIgnoreChecksAnnotation: true,
UseOptionalChecksAnnotation: true,
AllFiles: []ks.NamedReader{testFile("statefulset-nested-ignores.yaml")},
}, "Container Image Tag")
assert.True(t, skipped)
}

func TestStatefulsetTemplateIgnoresNotIgnoredWhenFlagDisabled(t *testing.T) {
t.Parallel()
skipped := wasSkipped(t, config.Configuration{
UseIgnoreChecksAnnotation: false,
UseOptionalChecksAnnotation: true,
AllFiles: []ks.NamedReader{testFile("statefulset-nested-ignores.yaml")},
}, "Container Image Tag")
assert.False(t, skipped)
}
25 changes: 14 additions & 11 deletions score/score.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
for _, ingress := range allObjects.Ingresses() {
o := newObject(ingress.GetTypeMeta(), ingress.GetObjectMeta())
for _, test := range allChecks.Ingresses() {
o.Add(test.Fn(ingress), test.Check, ingress)
o.Add(test.Fn(ingress), test.Check, ingress, ingress.GetObjectMeta().Annotations)
}
}

for _, meta := range allObjects.Metas() {
o := newObject(meta.TypeMeta, meta.ObjectMeta)
for _, test := range allChecks.Metas() {
o.Add(test.Fn(meta), test.Check, meta)
o.Add(test.Fn(meta), test.Check, meta, meta.ObjectMeta.Annotations)
}
}

Expand All @@ -72,22 +72,25 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
ObjectMeta: pod.Pod().ObjectMeta,
Spec: pod.Pod().Spec,
}, pod.Pod().TypeMeta)
o.Add(score, test.Check, pod)
o.Add(score, test.Check, pod, pod.Pod().ObjectMeta.Annotations)
}
}

for _, podspecer := range allObjects.PodSpeccers() {
o := newObject(podspecer.GetTypeMeta(), podspecer.GetObjectMeta())
for _, test := range allChecks.Pods() {
score := test.Fn(podspecer.GetPodTemplateSpec(), podspecer.GetTypeMeta())
o.Add(score, test.Check, podspecer)
o.Add(score, test.Check, podspecer,
podspecer.GetObjectMeta().Annotations,
podspecer.GetPodTemplateSpec().Annotations,
)
}
}

for _, service := range allObjects.Services() {
o := newObject(service.Service().TypeMeta, service.Service().ObjectMeta)
for _, test := range allChecks.Services() {
o.Add(test.Fn(service.Service()), test.Check, service)
o.Add(test.Fn(service.Service()), test.Check, service, service.Service().Annotations)
}
}

Expand All @@ -98,7 +101,7 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
if err != nil {
return nil, err
}
o.Add(res, test.Check, statefulset)
o.Add(res, test.Check, statefulset, statefulset.StatefulSet().ObjectMeta.Annotations)
}
}

Expand All @@ -109,35 +112,35 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
if err != nil {
return nil, err
}
o.Add(res, test.Check, deployment)
o.Add(res, test.Check, deployment, deployment.Deployment().ObjectMeta.Annotations)
}
}

for _, netpol := range allObjects.NetworkPolicies() {
o := newObject(netpol.NetworkPolicy().TypeMeta, netpol.NetworkPolicy().ObjectMeta)
for _, test := range allChecks.NetworkPolicies() {
o.Add(test.Fn(netpol.NetworkPolicy()), test.Check, netpol)
o.Add(test.Fn(netpol.NetworkPolicy()), test.Check, netpol, netpol.NetworkPolicy().ObjectMeta.Annotations)
}
}

for _, cjob := range allObjects.CronJobs() {
o := newObject(cjob.GetTypeMeta(), cjob.GetObjectMeta())
for _, test := range allChecks.CronJobs() {
o.Add(test.Fn(cjob), test.Check, cjob)
o.Add(test.Fn(cjob), test.Check, cjob, cjob.GetObjectMeta().Annotations)
}
}

for _, hpa := range allObjects.HorizontalPodAutoscalers() {
o := newObject(hpa.GetTypeMeta(), hpa.GetObjectMeta())
for _, test := range allChecks.HorizontalPodAutoscalers() {
o.Add(test.Fn(hpa), test.Check, hpa)
o.Add(test.Fn(hpa), test.Check, hpa, hpa.GetObjectMeta().Annotations)
}
}

for _, pdb := range allObjects.PodDisruptionBudgets() {
o := newObject(pdb.GetTypeMeta(), pdb.GetObjectMeta())
for _, test := range allChecks.PodDisruptionBudgets() {
o.Add(test.Fn(pdb), test.Check, pdb)
o.Add(test.Fn(pdb), test.Check, pdb, pdb.GetObjectMeta().Annotations)
}
}

Expand Down
15 changes: 15 additions & 0 deletions score/score_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@ func testExpectedScoreWithConfig(t *testing.T, config config.Configuration, test
return nil
}

func wasSkipped(t *testing.T, config config.Configuration, testcase string) bool {
sc, err := testScore(config)
assert.NoError(t, err)
for _, objectScore := range sc {
for _, s := range objectScore.Checks {
if s.Check.Name == testcase {
return s.Skipped
}
}
}

assert.Fail(t, "test was not run")
return false
}

func testScore(config config.Configuration) (scorecard.Scorecard, error) {
p, err := parser.New()
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions score/security_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ func TestContainerSeccompMissing(t *testing.T) {
AllFiles: []ks.NamedReader{testFile("pod-seccomp-no-annotation.yaml")},
EnabledOptionalTests: structMap,
}, "Container Seccomp Profile", scorecard.GradeWarning)

}

func TestContainerSeccompMissingNotRunByDefault(t *testing.T) {
t.Parallel()
skipped := wasSkipped(t, config.Configuration{
AllFiles: []ks.NamedReader{testFile("pod-seccomp-no-annotation.yaml")},
}, "Container Seccomp Profile")
assert.True(t, skipped)
}

func TestContainerSeccompAllGood(t *testing.T) {
Expand Down
109 changes: 109 additions & 0 deletions score/testdata/statefulset-nested-ignores.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: trivy
namespace: trivy-staging
spec:
podManagementPolicy: Parallel
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app.kubernetes.io/instance: trivy
app.kubernetes.io/name: trivy
serviceName: trivy
template:
metadata:
annotations:
kube-score/ignore: container-image-tag,pod-probes
labels:
app.kubernetes.io/instance: trivy
app.kubernetes.io/name: trivy
spec:
automountServiceAccountToken: false
containers:
- args:
- server
envFrom:
- configMapRef:
name: trivy
- secretRef:
name: trivy
image: aquasec/trivy:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 10
httpGet:
path: /healthz
port: trivy-http
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
name: main
ports:
- containerPort: 4954
name: trivy-http
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: trivy-http
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources:
limits:
cpu: "1"
ephemeral-storage: 128Mi
memory: 1Gi
requests:
cpu: 200m
memory: 512Mi
securityContext:
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 65534
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /tmp
name: tmp-data
- mountPath: /home/scanner/.cache
name: data
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext:
fsGroup: 65534
runAsNonRoot: true
runAsUser: 65534
serviceAccount: trivy
serviceAccountName: trivy
terminationGracePeriodSeconds: 30
volumes:
- emptyDir: {}
name: tmp-data
updateStrategy:
rollingUpdate:
partition: 0
type: RollingUpdate
volumeClaimTemplates:
- apiVersion: v1
kind: PersistentVolumeClaim
metadata:
creationTimestamp: null
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
volumeMode: Filesystem
status:
phase: Pending
42 changes: 42 additions & 0 deletions scorecard/enabled.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package scorecard

import (
"strings"

ks "github.com/zegl/kube-score/domain"
)

func (so *ScoredObject) isEnabled(check ks.Check, annotations, childAnnotations map[string]string) bool {
isIn := func(csv string, key string) bool {
for _, v := range strings.Split(csv, ",") {
if v == key {
return true
}
if _, ok := impliedIgnoreAnnotations[v]; ok {
return true
}
}
return false
}

if childAnnotations != nil && so.useIgnoreChecksAnnotation && isIn(childAnnotations[ignoredChecksAnnotation], check.ID) {
return false
}
if childAnnotations != nil && so.useOptionalChecksAnnotation && isIn(childAnnotations[optionalChecksAnnotation], check.ID) {
return true
}
if so.useIgnoreChecksAnnotation && isIn(annotations[ignoredChecksAnnotation], check.ID) {
return false
}
if so.useOptionalChecksAnnotation && isIn(annotations[optionalChecksAnnotation], check.ID) {
return true
}

// Optional checks are disabled unless explicitly allowed above
if check.Optional {
return false
}

// Enabled by default
return true
}
Loading

0 comments on commit 7120802

Please sign in to comment.