Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

score: allow ignore/allow annotations on child template resources #489

Merged
merged 1 commit into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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