Skip to content

Commit

Permalink
Merge pull request #248 from luotianqi777/sarif
Browse files Browse the repository at this point in the history
feat: support sarif
  • Loading branch information
luotianqi777 authored Jan 10, 2024
2 parents c069eaa + eeb2dab commit 2ada35f
Show file tree
Hide file tree
Showing 14 changed files with 256 additions and 42 deletions.
1 change: 1 addition & 0 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ Files supported by the `out` parameter are listed below:
| | `html` | `.html` | `v1.0.6` and above |
| | `sqlite` | `.sqlite` | `v1.0.13` and above|
| | `csv` | `.csv` | `v1.0.13` and above|
| | `sarif`| `.sarif` | |
| SBOM | `spdx` | `.spdx` `.spdx.json` `.spdx.xml` | `v1.0.8` and above |
| | `cdx` | `.cdx.json` `.cdx.xml` | `v1.0.11`and above |
| | `swid` | `.swid.json` `.swid.xml` | `v1.0.11`and above |
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ v3.0.2开始,OpenSCA-cli可以通过proj参数向OpenSCA SaaS同步检出结
| | `html` | `.html` |
| | `sqlite` | `.sqlite` |
| | `csv` | `.csv` |
| | `sarif` | `.sarif` |
| SBOM清单 | `spdx` | `.spdx` `.spdx.json` `.spdx.xml` |
| | `cdx` | `.cdx.json` `.cdx.xml` |
| | `swid` | `.swid.json` `.swid.xml` |
Expand Down
14 changes: 14 additions & 0 deletions cmd/detail/detail.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,20 @@ type Vuln struct {
ExploitLevelId int `json:"exploit_level_id" gorm:"column:exploit_level_id"`
}

func (v *Vuln) SecurityLevel() string {
switch v.SecurityLevelId {
case 1:
return "Critical"
case 2:
return "High"
case 3:
return "Medium"
case 4:
return "Low"
}
return "Unknown"
}

func vulnLanguageKey(language model.Language) []string {
switch language {
case model.Lan_Java:
Expand Down
5 changes: 3 additions & 2 deletions cmd/format/csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ func Csv(report Report, out string) {
return true
})

outWrite(out, func(w io.Writer) {
w.Write([]byte(table))
outWrite(out, func(w io.Writer) error {
_, err := w.Write([]byte(table))
return err
})

}
8 changes: 4 additions & 4 deletions cmd/format/cyclonedx.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,14 @@ func cyclonedxbom(dep *detail.DepDetailGraph) *cyclonedx.BOM {

func CycloneDXJson(report Report, out string) {
bom := cyclonedxbom(report.DepDetailGraph)
outWrite(out, func(w io.Writer) {
cyclonedx.NewBOMEncoder(w, cyclonedx.BOMFileFormatJSON).SetPretty(true).Encode(bom)
outWrite(out, func(w io.Writer) error {
return cyclonedx.NewBOMEncoder(w, cyclonedx.BOMFileFormatJSON).SetPretty(true).Encode(bom)
})
}

func CycloneDXXml(report Report, out string) {
bom := cyclonedxbom(report.DepDetailGraph)
outWrite(out, func(w io.Writer) {
cyclonedx.NewBOMEncoder(w, cyclonedx.BOMFileFormatXML).SetPretty(true).Encode(bom)
outWrite(out, func(w io.Writer) error {
return cyclonedx.NewBOMEncoder(w, cyclonedx.BOMFileFormatXML).SetPretty(true).Encode(bom)
})
}
16 changes: 6 additions & 10 deletions cmd/format/dsdx.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,24 @@ import (
"io"

"github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail"
"github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs"
"github.com/xmirrorsecurity/opensca-cli/v3/opensca/model"
)

func Dsdx(report Report, out string) {
outWrite(out, func(w io.Writer) {
err := dsdxDoc(report).WriteDsdx(w)
if err != nil {
logs.Warn(err)
}
outWrite(out, func(w io.Writer) error {
return dsdxDoc(report).WriteDsdx(w)
})
}

func DsdxJson(report Report, out string) {
outWrite(out, func(w io.Writer) {
json.NewEncoder(w).Encode(dsdxDoc(report))
outWrite(out, func(w io.Writer) error {
return json.NewEncoder(w).Encode(dsdxDoc(report))
})
}

func DsdxXml(report Report, out string) {
outWrite(out, func(w io.Writer) {
xml.NewEncoder(w).Encode(dsdxDoc(report))
outWrite(out, func(w io.Writer) error {
return xml.NewEncoder(w).Encode(dsdxDoc(report))
})
}

Expand Down
5 changes: 3 additions & 2 deletions cmd/format/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ func Html(report Report, out string) {
}); err != nil {
logs.Warn(err)
} else {
outWrite(out, func(w io.Writer) {
w.Write(bytes.Replace(index, []byte(`"此处填充json数据"`), data, 1))
outWrite(out, func(w io.Writer) error {
_, err := w.Write(bytes.Replace(index, []byte(`"此处填充json数据"`), data, 1))
return err
})
return
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/format/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (
)

func Json(report Report, out string) {
outWrite(out, func(w io.Writer) {
outWrite(out, func(w io.Writer) error {
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
encoder.Encode(report)
return encoder.Encode(report)
})
}
195 changes: 195 additions & 0 deletions cmd/format/sarif.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package format

import (
"encoding/json"
"fmt"
"io"
"regexp"
"strings"

"github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail"
)

type sarifReport struct {
Version string `json:"version"`
Schema string `json:"$schema"`
Runs []sarifRun `json:"runs"`
}

type sarifRun struct {
Tool struct {
Driver struct {
Name string `json:"name"`
Version string `json:"version"`
InformationUri string `json:"informationUri"`
Rules []sarifRule `json:"rules"`
} `json:"driver"`
} `json:"tool"`
Results []sarifResult `json:"results"`
}

type sarifRule struct {
Id string `json:"id"`
Name string `json:"name"`
ShortDescription sarifRuleShortDescription `json:"shortDescription"`
FullDescription sarifRuleFullDescription `json:"fullDescription"`
Help sarifRuleHelp `json:"help"`
Properties sarifRuleProperties `json:"properties"`
}

type sarifRuleShortDescription struct {
Text string `json:"text"`
}

type sarifRuleFullDescription struct {
Text string `json:"text"`
}

type sarifRuleHelp struct {
Text string `json:"text"`
Markdown string `json:"markdown"`
}

type sarifRuleProperties struct {
Tags []string `json:"tags"`
}

type sarifResult struct {
RuleId string `json:"ruleId"`
Level string `json:"level"`
Message struct {
Text string `json:"text"`
} `json:"message"`
Locations []sarifLocation `json:"locations"`
}

type sarifLocation struct {
PhysicalLocation struct {
ArtifactLocation struct {
Uri string `json:"uri"`
Index int `json:"index,omitempty"`
} `json:"artifactLocation"`
Region struct {
StartColumn int `json:"startColumn"`
EndColumn int `json:"endColumn"`
StartLine int `json:"startLine"`
EndLine int `json:"endLine"`
} `json:"region"`
} `json:"physicalLocation"`
}

func Sarif(report Report, out string) {

s := sarifReport{
Version: "2.1.0",
Schema: "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json",
}

run := sarifRun{}
run.Tool.Driver.Name = "opensca-cli"
run.Tool.Driver.Version = strings.TrimLeft(report.TaskInfo.ToolVersion, "vV")
run.Tool.Driver.InformationUri = "https://opensca.xmirror.cn"

vulnInfos := map[string]*detail.VulnInfo{}

report.ForEach(func(n *detail.DepDetailGraph) bool {
for _, vuln := range n.Vulnerabilities {

if vuln.Id == "" {
continue
}

vulnInfos[vuln.Id] = &detail.VulnInfo{Vuln: vuln, Language: n.Language}

result := sarifResult{
RuleId: vuln.Id,
Level: "warning",
}
result.Message.Text = fmt.Sprintf("引入的组件 %s 中存在 %s", n.Dep.Key()[:strings.LastIndex(n.Dep.Key(), ":")], vuln.Name)
for i, path := range n.Paths {
if truncIndex := strings.Index(path, "["); truncIndex > 0 {
path = strings.Trim(path[:truncIndex], `\/`)
}
location := sarifLocation{}
location.PhysicalLocation.ArtifactLocation.Uri = path
location.PhysicalLocation.ArtifactLocation.Index = i
location.PhysicalLocation.Region.StartColumn = 1
location.PhysicalLocation.Region.EndColumn = 1
location.PhysicalLocation.Region.StartLine = 1
location.PhysicalLocation.Region.EndLine = 1
result.Locations = append(result.Locations, location)
}

run.Results = append(run.Results, result)
}
return true
})

for _, vuln := range vulnInfos {
run.Tool.Driver.Rules = append(run.Tool.Driver.Rules, sarifRule{
Id: vuln.Id,
Name: vuln.Name,
ShortDescription: sarifRuleShortDescription{Text: vuln.Name},
FullDescription: sarifRuleFullDescription{Text: vuln.Description},
Help: sarifRuleHelp{Markdown: formatDesc(vuln)},
Properties: sarifRuleProperties{Tags: formatTags(vuln)},
})
}

s.Runs = []sarifRun{run}
outWrite(out, func(w io.Writer) error {
return json.NewEncoder(w).Encode(s)
})
}

func formatDesc(v *detail.VulnInfo) string {
table := []struct {
fmt string
val string
}{
{"| id | %s |", v.Id},
{"| --- | --- |", ""},
{"| cve | %s |", v.Cve},
{"| cnnvd | %s |", v.Cnnvd},
{"| cnvd | %s |", v.Cnvd},
{"| cwe | %s |", v.Cwe},
{"| level | %s |", v.SecurityLevel()},
{"| desc | %s |", sanitizeString(v.Description)},
{"| suggestion | %s |", sanitizeString(v.Suggestion)},
}
var lines []string
for _, line := range table {
if strings.Contains(line.fmt, "%s") && line.val == "" {
continue
}
if line.val == "" {
lines = append(lines, line.fmt)
} else {
lines = append(lines, fmt.Sprintf(line.fmt, line.val))
}
}

return strings.Join(lines, "\n")
}

func sanitizeString(s string) string {
re := regexp.MustCompile("<[^>]*>")
s = re.ReplaceAllString(s, "")

s = strings.ReplaceAll(s, "\r", "")
s = strings.ReplaceAll(s, "\n", "")

return s
}

func formatTags(v *detail.VulnInfo) []string {
tags := []string{"security", "Use-Vulnerable-and-Outdated-Components", v.Cve, v.Cwe, v.AttackType, v.Language}
for i := 0; i < len(tags); {
if tags[i] == "" {
tags = append(tags[:i], tags[i+1:]...)
} else {
i++
}
}
return tags
}
8 changes: 6 additions & 2 deletions cmd/format/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,15 @@ func Save(report Report, output string) {
Csv(report, out)
case ".sqlite", ".db":
Sqlite(report, out)
case ".sarif":
Sarif(report, out)
default:
Json(report, out)
}
}
}

func outWrite(out string, do func(io.Writer)) {
func outWrite(out string, do func(io.Writer) error) {

if out == "" {
do(os.Stdout)
Expand All @@ -95,7 +97,9 @@ func outWrite(out string, do func(io.Writer)) {
logs.Warn(err)
} else {
defer w.Close()
do(w)
if err = do(w); err != nil {
logs.Warn(err)
}
}
}

Expand Down
16 changes: 6 additions & 10 deletions cmd/format/spdx.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,24 @@ import (
"io"

"github.com/xmirrorsecurity/opensca-cli/v3/cmd/detail"
"github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs"
"github.com/xmirrorsecurity/opensca-cli/v3/opensca/model"
)

func Spdx(report Report, out string) {
outWrite(out, func(w io.Writer) {
err := spdxDoc(report).WriteSpdx(w)
if err != nil {
logs.Warn(err)
}
outWrite(out, func(w io.Writer) error {
return spdxDoc(report).WriteSpdx(w)
})
}

func SpdxJson(report Report, out string) {
outWrite(out, func(w io.Writer) {
json.NewEncoder(w).Encode(spdxDoc(report))
outWrite(out, func(w io.Writer) error {
return json.NewEncoder(w).Encode(spdxDoc(report))
})
}

func SpdxXml(report Report, out string) {
outWrite(out, func(w io.Writer) {
xml.NewEncoder(w).Encode(spdxDoc(report))
outWrite(out, func(w io.Writer) error {
return xml.NewEncoder(w).Encode(spdxDoc(report))
})
}

Expand Down
Loading

0 comments on commit 2ada35f

Please sign in to comment.