Skip to content

Commit

Permalink
add sarif output support (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
mthbernardes authored Mar 14, 2022
1 parent 48fe559 commit 7990c86
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 35 deletions.
1 change: 1 addition & 0 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
cli-matic/cli-matic {:mvn/version "0.4.3"}
selmer/selmer {:mvn/version "1.12.50"}
org.slf4j/slf4j-nop {:mvn/version "2.0.0-alpha6"}
borkdude/edamame {:mvn/version "0.0.19"}
org.clojure/tools.deps.alpha {:mvn/version "0.12.1148"}
org.owasp/dependency-check-core {:mvn/version "6.5.3"}
org.apache.maven.resolver/maven-resolver-transport-http {:mvn/version "1.7.3"}}
Expand Down
2 changes: 2 additions & 0 deletions resources/github/query-package-vulnerabilities
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ query Vulnerabilities {
nodes {
vulnerableVersionRange
advisory {
description
summary
severity
cvss {
score
Expand Down
22 changes: 22 additions & 0 deletions resources/sarif-help.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Dependency
## Information
Name: {{vulnerable-dependency.dependency}}
Version: {{vulnerable-dependency.mvn/version}}

## Vulnerabilities
{% for identifier in identifiers %}
- {{identifier.value}}
{% endfor %}

## Location
{% if vulnerable-dependency.parents|empty? %}
Impossible to find dependency in tree.
It could be a jar inside some project.

{% else %}
{{vulnerable-dependency.parents|build-tree}}
{% endif %}
# Fix suggestion
```clojure
{{vulnerable-dependency.remediate-suggestion|safe}}
```
2 changes: 1 addition & 1 deletion src/clj_watson/cli.clj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
:default :present
:as "path of deps.edn to scan."}
{:option "output" :short "o"
:type #{"json" "edn" "stdout" "stdout-simple"} ; keep stdout type to avoid break current automations
:type #{"json" "edn" "stdout" "stdout-simple" "sarif"} ; keep stdout type to avoid break current automations
:default "stdout"
:as "Output type."}
{:option "aliases" :short "a"
Expand Down
33 changes: 29 additions & 4 deletions src/clj_watson/controller/deps.clj
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
(ns clj-watson.controller.deps
(:require
[clojure.set :refer [rename-keys]]
[clojure.tools.deps.alpha :as deps]
[clojure.tools.deps.alpha.util.maven :as maven])
[clojure.tools.deps.alpha.util.maven :as maven]
[edamame.core :refer [parse-string]])
(:import
(java.io File)))

Expand All @@ -11,18 +13,41 @@
(coll? aliases) (map keyword aliases)
:else []))

(defn ^:private dependencies-map->dependencies-vector [dependencies]
(defn ^:private dependencies-map->dependencies-vector [dependencies dependencies-physical-location]
(reduce (fn [dependency-vector [dependency-name dependency-info]]
(conj dependency-vector (assoc dependency-info :dependency dependency-name)))
(let [dependency-physical-location (or (get dependencies-physical-location dependency-name)
(->> dependency-info
:parents
ffirst
(get dependencies-physical-location)))]
(->> (assoc dependency-info :dependency dependency-name :physical-location dependency-physical-location)
(conj dependency-vector))))
[] dependencies))

(defn ^:private rename-location-keys [location]
(rename-keys location {:row :startLine :end-row :endLine :col :startColumn :end-col :endColumn}))

(defn ^:private deps->dependencies-location [deps-path]
(let [deps (-> deps-path slurp parse-string)]
(->> (tree-seq coll? identity deps)
(filter symbol?)
(reduce (fn [locations element]
(if-let [location (some-> element meta rename-location-keys)]
(assoc locations element location)
locations))
{}))))

(defn parse [^String deps-path aliases]
(let [project-deps (-> deps-path File. deps/slurp-deps (update :mvn/repos merge maven/standard-repos))
aliases (build-aliases project-deps aliases)
dependencies-physical-location (deps->dependencies-location deps-path)
aliases-resolver {:resolve-args (deps/combine-aliases project-deps aliases)
:classpath-args (deps/combine-aliases project-deps aliases)}]
{:deps project-deps
:dependencies (-> project-deps (deps/calc-basis aliases-resolver) :libs dependencies-map->dependencies-vector)}))
:dependencies (-> project-deps
(deps/calc-basis aliases-resolver)
:libs
(dependencies-map->dependencies-vector dependencies-physical-location))}))

(comment
(parse "resources/vulnerable-deps.edn" nil))
24 changes: 14 additions & 10 deletions src/clj_watson/controller/output.clj
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
(ns clj-watson.controller.output
(:require
[cheshire.core :as json]
[clj-watson.logic.stdout :as logic.stdout]
[clj-watson.logic.sarif :as logic.sarif]
[clj-watson.logic.template :as logic.template]
[clojure.java.io :as io]
[clojure.pprint :as pprint]))

(defmulti ^:private generate* (fn [_ kind] (keyword kind)))
(defmulti ^:private generate* (fn [_ _ kind] (keyword kind)))

(defmethod ^:private generate* :stdout-simple [dependencies _]
(defmethod ^:private generate* :stdout-simple [dependencies & _]
(let [template (-> "simple-report.mustache" io/resource slurp)]
(println (logic.stdout/generate dependencies template))))
(println (logic.template/generate {:vulnerable-dependencies dependencies} template))))

(defmethod ^:private generate* :stdout [dependencies _]
(defmethod ^:private generate* :stdout [dependencies & _]
(let [template (-> "full-report.mustache" io/resource slurp)]
(println (logic.stdout/generate dependencies template))))
(println (logic.template/generate {:vulnerable-dependencies dependencies} template))))

(defmethod ^:private generate* :json [dependencies _]
(defmethod ^:private generate* :json [dependencies & _]
(-> dependencies json/generate-string pprint/pprint))

(defmethod ^:private generate* :edn [dependencies _]
(defmethod ^:private generate* :edn [dependencies & _]
(pprint/pprint dependencies))

(defn generate [dependencies kind]
(generate* dependencies kind))
(defmethod ^:private generate* :sarif [dependencies deps-edn-path & _]
(-> dependencies (logic.sarif/generate deps-edn-path) json/generate-string println))

(defn generate [dependencies deps-edn-path kind]
(generate* dependencies deps-edn-path kind))
8 changes: 4 additions & 4 deletions src/clj_watson/entrypoint.clj
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
(defmethod scan* :default [opts]
(scan* (assoc opts :database-strategy "dependency-check")))

(defn scan [{:keys [fail-on-result output] :as opts}]
(defn scan [{:keys [fail-on-result output deps-edn-path] :as opts}]
(let [vulnerabilities (scan* opts)]
(controller.output/generate vulnerabilities output)
(controller.output/generate vulnerabilities deps-edn-path output)
(if (and (-> vulnerabilities count (> 0)) fail-on-result)
(System/exit 1)
(System/exit 0))))
Expand All @@ -45,5 +45,5 @@
:database-strategy "github-advisory"
:suggest-fix true}))

(controller.output/generate vulnerabilities "stdout")
(controller.output/generate vulnerabilities "stdout-simple"))
(controller.output/generate vulnerabilities "deps.edn" "sarif")
(controller.output/generate vulnerabilities "deps.edn" "stdout-simple"))
16 changes: 11 additions & 5 deletions src/clj_watson/logic/dependency_check/vulnerability.clj
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,17 @@
:version-start-including (.getVersionStartIncluding vulnerable-software)}))

(defn ^:private build-vulnerability-map [vulnerability safe-versions]
(-> (assoc-in {} [:advisory :identifiers] [{:value (.getName vulnerability)}])
(assoc-in [:advisory :cvss :score] (cvssv3 vulnerability))
(assoc-in [:advisory :severity] (severity vulnerability))
(assoc-in [:firstPatchedVersion :identifier] (-> safe-versions first vals first))
(assoc :safe-versions safe-versions)))
(let [vulnerability-identifier (.getName vulnerability)
vulnerability-cvss (cvssv3 vulnerability)
vulnerability-severity (severity vulnerability)
summary (format "Vulnerability identified as %s of score %s and severity %s found." vulnerability-identifier vulnerability-cvss vulnerability-severity)]
(-> (assoc-in {} [:advisory :identifiers 0 :value] vulnerability-identifier)
(assoc-in [:advisory :cvss :score] vulnerability-cvss)
(assoc-in [:advisory :severity] vulnerability-severity)
(assoc-in [:advisory :description] (.getDescription vulnerability))
(assoc-in [:advisory :summary] summary)
(assoc-in [:firstPatchedVersion :identifier] (-> safe-versions first vals first))
(assoc :safe-versions safe-versions))))

(defn get-information
[current-version all-versions ^Vulnerability vulnerability]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
(ns clj-watson.logic.stdout
(:require
[selmer.filters :refer [add-filter!]]
[selmer.parser :refer [render]]))
(ns clj-watson.logic.formatter)

(defn ^:private dependencies-hierarchy-to-tree* [tree]
(loop [text ""
Expand All @@ -14,15 +11,10 @@
new-text (format "%s%s[%s]\n" text tabs dependency)]
(recur new-text (inc count) (next dependencies))))))

(defn ^:private dependencies-hierarchy-to-tree [trees]
(defn dependencies-hierarchy-to-tree [trees]
(if (and (= 1 (count trees)) (every? empty? trees))
"Direct dependency."
(->> trees
reverse
(map dependencies-hierarchy-to-tree*)
(reduce #(str %1 "\n" %2)))))

(add-filter! :build-tree dependencies-hierarchy-to-tree)

(defn generate [dependencies template]
(render template {:vulnerable-dependencies dependencies}))
(reduce #(str %1 "\n" %2)))))
58 changes: 58 additions & 0 deletions src/clj_watson/logic/sarif.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
(ns clj-watson.logic.sarif
(:require
[clj-watson.logic.template :as logic.template]
[clojure.java.io :as io]
[clojure.string :as string]))

(def ^:private sarif-boilerplate
{:$schema "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json"
:version "2.1.0"
:runs [{:tool
{:driver {:name "clj-watson"
:informationUri "https://github.com/clj-holmes/clj-watson"
:version "3.0.2"}}}]})

(defn ^:private advisory->sarif-rule [dependency dependency-info {{:keys [description summary identifiers cvss]} :advisory}]
(let [identifier (-> identifiers first :value)
; needs to remove it from here
template (-> "sarif-help.mustache" io/resource slurp)
help-text (logic.template/generate {:vulnerable-dependency dependency-info :identifiers identifiers} template)]
[{:id identifier
:name (format "VulnerableDependency%s" (-> dependency name string/capitalize))
:shortDescription {:text summary}
:fullDescription {:text description}
:help {:text help-text
:markdown help-text}
:helpUri (format "https://github.com/advisories/%s" identifier)
:properties {:security-severity (-> cvss :score str)}
:defaultConfiguration {:level "error"}}]))

(defn ^:private dependencies->sarif-rules [dependencies]
(->> dependencies
(map (fn [{:keys [dependency vulnerabilities] :as dependency-info}]
(->> vulnerabilities
(map #(advisory->sarif-rule dependency dependency-info %))
(reduce concat))))
(reduce concat)))

(defn ^:private advisory->sarif-result
[filename physical-location dependency {{:keys [identifiers]} :advisory}]
{:ruleId (-> identifiers first :value)
:message {:text (format "Vulnerability found in direct dependency %s" dependency)}
:locations [{:physicalLocation
{:artifactLocation {:uri filename}
:region physical-location}}]})

(defn ^:private dependencies->sarif-results [dependencies deps-edn-path]
(->> dependencies
(map (fn [{:keys [dependency vulnerabilities physical-location]}]
(->> vulnerabilities
(map #(advisory->sarif-result deps-edn-path physical-location dependency %)))))
(reduce concat)))

(defn generate [dependencies deps-edn-path]
(let [rules (dependencies->sarif-rules dependencies)
results (dependencies->sarif-results dependencies deps-edn-path)]
(-> sarif-boilerplate
(assoc-in [:runs 0 :tool :driver :rules] rules)
(assoc-in [:runs 0 :results] results))))
10 changes: 10 additions & 0 deletions src/clj_watson/logic/template.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
(ns clj-watson.logic.template
(:require
[clj-watson.logic.formatter :refer [dependencies-hierarchy-to-tree]]
[selmer.filters :refer [add-filter!]]
[selmer.parser :refer [render]]))

(add-filter! :build-tree dependencies-hierarchy-to-tree)

(defn generate [dependencies template]
(render template dependencies))

0 comments on commit 7990c86

Please sign in to comment.