Roll out a policy across repositories¶
A platform team keeps one default config that every repository layers its own sarifix.yaml over. The default owns the scanners and guardrails, and each repository opts in to the rules it is ready for.
You'll use Google's microservices-demo: eleven services with Kubernetes manifests, Kustomize overlays, a Helm chart, Terraform and Dockerfiles.
1. The platform default¶
Kept in a central repository and checked out next to each service repository, here as ../platform/sarifix-org.yaml:
# Platform team default, layered under every repo's own sarifix.yaml.
version: 1
scanners:
- name: trivy
match: ["Trivy"]
command: ["trivy", "config", "--format", "sarif", "-o", "{out}", "{dir}"]
- name: checkov
match: ["Checkov"]
command: ["checkov", "-d", "{dir}", "--framework", "kubernetes,dockerfile,terraform",
"-o", "sarif", "--output-file-path", "{outdir}"]
output: "{outdir}/results_sarif.sarif"
rules:
deny: ["*/*SECRET*"]
min_level: error
paths:
deny: ["vendor/**", "**/genproto/**"]
limits:
batches_per_run: 3
2. The repository's opt-in¶
sarifix.yaml in the service repository only adds what is specific to it:
How layers combine:
- Scalars and lists in the repository file replace the default's. Here
rules.allowcomes from the repository, andrules.deny,min_levelandlimitsfrom the default. - Scanners and campaigns merge by
nameandid. A repository can override one scanner, or add its own, without copying the rest. .github/workflows/**is always inpaths.deny, whatever either file says.
3. Validate, scan, plan¶
$ sarifix config validate --org-config ../platform/sarifix-org.yaml -q
✓ ../platform/sarifix-org.yaml
✓ sarifix.yaml
$ sarifix scan --org-config ../platform/sarifix-org.yaml --log-level warn
✓ trivy 369 findings → sarif/trivy.sarif
✓ checkov 374 findings → sarif/checkov.sarif
$ sarifix plan --org-config ../platform/sarifix-org.yaml --sarif 'sarif/*.sarif'
743 findings 70 batched in 3 batches 263 deferred 410 skipped
Batches
1. checkov/CKV_K8S_21 error 23 findings in 7 files → sarifix/checkov/ckv_k8s_21
kubernetes-manifests/adservice.yaml, kubernetes-manifests/cartservice.yaml, kubernetes-manifests/checkoutservice.yaml, kubernetes-manifests/currencyservice.yaml, kubernetes-manifests/emailservice.yaml…
2. checkov/CKV_K8S_21#2 error 23 findings in 7 files → sarifix/checkov/ckv_k8s_21-2
kubernetes-manifests/paymentservice.yaml, kubernetes-manifests/productcatalogservice.yaml, kubernetes-manifests/recommendationservice.yaml, kubernetes-manifests/shippingservice.yaml, kustomize/base/ad…
3. checkov/CKV_K8S_21#3 error 24 findings in 8 files → sarifix/checkov/ckv_k8s_21-3
kustomize/base/currencyservice.yaml, kustomize/base/emailservice.yaml, kustomize/base/frontend.yaml, kustomize/base/loadgenerator.yaml, kustomize/base/paymentservice.yaml, kustomize/base/productcatalo…
...
Deferred (over limits.batches_per_run)
1. checkov/CKV_K8S_21#4 error 7 findings in 3 files → sarifix/checkov/ckv_k8s_21-4
kustomize/components/google-cloud-operations/otel-collector.yaml, kustomize/components/shopping-assistant/shoppingassistantservice.yaml, kustomize/components/without-loadgenerator/delete-loadgenerator…
2. checkov/CKV_K8S_21#5 error 25 findings in 1 file → sarifix/checkov/ckv_k8s_21-5
release/kubernetes-manifests.yaml
...
Skipped
below_min_level 353
rule_not_allowed 57
config validate lists both files it loaded. Errors name the file and line they came from, so a typo in the default is reported against sarifix-org.yaml.
below_min_level is large because the default only queues error-level findings.
4. Find what shouldn't be in the queue¶
The deferred list shows release/kubernetes-manifests.yaml, a single file with 35 findings for one rule alone. The report says where all the queued findings sit:
$ jq '[.runs[].results[] | select(.properties.sarifix.status != "skipped") | .locations[0].physicalLocation.artifactLocation.uri | split("/")[0]] | group_by(.) | map({(.[0]): length}) | add' sarifix-report/sarifix.sarif
{
"kubernetes-manifests": 101,
"kustomize": 131,
"release": 101
}
release/ is generated from kubernetes-manifests/ by the project's release scripts, and fixing it by hand would be overwritten at the next release. Exclude it in the repository config:
paths:
# Lists replace the org's list, so repeat its entries. release/ is
# generated from kubernetes-manifests/ by the release scripts.
deny: ["vendor/**", "**/genproto/**", "release/**"]
Because lists replace rather than merge, the repository repeats the default's two entries. Check the resolved value rather than guessing:
$ sarifix config validate --org-config ../platform/sarifix-org.yaml --json | jq -c .paths.deny
[".github/workflows/**","vendor/**","**/genproto/**","release/**"]
$ sarifix plan --org-config ../platform/sarifix-org.yaml --sarif 'sarif/*.sarif' | head -1
743 findings 70 batched in 3 batches 162 deferred 511 skipped
$ sarifix plan --org-config ../platform/sarifix-org.yaml --sarif 'sarif/*.sarif' | sed -n '/^Skipped/,$p'
Skipped
below_min_level 353
rule_not_allowed 57
path_denied 101
101 findings in generated files are now path_denied instead of queued.
5. A change to one Deployment¶
Diffs stay exact in multi-document Kubernetes files. Adding a namespace to the adservice Deployment:
$ git diff
diff --git a/kubernetes-manifests/adservice.yaml b/kubernetes-manifests/adservice.yaml
index 25ff1b7f..bdaa043c 100644
--- a/kubernetes-manifests/adservice.yaml
+++ b/kubernetes-manifests/adservice.yaml
@@ -16,6 +16,7 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: adservice
+ namespace: shop
labels:
app: adservice
spec:
$ sarifix scan --org-config ../platform/sarifix-org.yaml --log-level warn
✓ trivy 368 findings → sarif/trivy.sarif
✓ checkov 373 findings → sarif/checkov.sarif
$ sarifix diff --base '../ms-base/*.sarif' --head 'sarif/*.sarif'
0 new 2 fixed 741 unchanged
Fixed
- Checkov/CKV_K8S_21 kubernetes-manifests/adservice.yaml:15 The default namespace should not be used
- Trivy/KSV-0110 kubernetes-manifests/adservice.yaml:17 Artifact: kubernetes-manifests/adservice.yaml Type: kubernetes Vulnerability KSV-0110 Severity: LOW Message: deployment …
The change fixes exactly the two namespace findings. Every other finding on the same Deployment is unchanged. In a file with several objects that all start with apiVersion: apps/v1, sarifix identifies each object by its opening lines down to its name:. Edits further into the object don't change that identity.
What you learned¶
--org-configlayers a shared default under each repository's file. Scanners and campaigns merge, while scalars and lists replace.config validate --jsonshows the resolved config, which is the fastest way to check a layering question.- Report mode plus
jqfinds what is filling the queue, such as generated files that belong inpaths.deny.