228 lines
5.9 KiB
Go
228 lines
5.9 KiB
Go
/*
|
|
Package cmd
|
|
Copyright © 2024 Shane C. <shane@scaffoe.com>
|
|
*/
|
|
package cmd
|
|
|
|
import (
|
|
"codeberg.org/mvdkleijn/forgejo-sdk/forgejo"
|
|
"fmt"
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/kr/pretty"
|
|
"github.com/nao1215/markdown"
|
|
"github.com/owenrumney/go-sarif/sarif"
|
|
"github.com/sethvargo/go-githubactions"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var isAction bool
|
|
var isGithub bool
|
|
|
|
// rootCmd represents the base command when called without any subcommands
|
|
var rootCmd = &cobra.Command{
|
|
Use: "goscan",
|
|
Short: "A brief description of your application",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
|
|
action := githubactions.New()
|
|
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
repo, err := git.PlainOpen(cwd)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
ref, err := repo.Head()
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
pretty.Logln(ref.Name().Short())
|
|
pretty.Logln(ref.Hash().String())
|
|
|
|
report, err := sarif.Open("output.sarif")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if len(report.Runs) != 1 {
|
|
log.Fatalf("expected 1 result, got %d", len(report.Runs))
|
|
}
|
|
|
|
run := report.Runs[0]
|
|
|
|
var rows [][]string
|
|
sevCountMap := map[string]int{
|
|
"high": 0,
|
|
"medium": 0,
|
|
"low": 0,
|
|
}
|
|
|
|
for _, result := range run.Results {
|
|
|
|
rule, err := run.GetRuleById(*result.RuleID)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if len(result.Locations) != 1 {
|
|
log.Fatalf("expected 1 result, got %d", len(result.Locations))
|
|
}
|
|
|
|
location := result.Locations[0]
|
|
severity := rule.Properties["tags"].([]interface{})[1].(string)
|
|
confidence := rule.Properties["precision"].(string)
|
|
|
|
var severityEmoji string
|
|
|
|
switch strings.ToLower(severity) {
|
|
case "low":
|
|
sevCountMap["low"] = sevCountMap["low"] + 1
|
|
severityEmoji = "🟨"
|
|
case "medium":
|
|
sevCountMap["medium"] = sevCountMap["medium"] + 1
|
|
severityEmoji = "🟧"
|
|
case "high":
|
|
sevCountMap["high"] = sevCountMap["high"] + 1
|
|
severityEmoji = "🟥"
|
|
}
|
|
|
|
var confidenceEmoji string
|
|
|
|
switch strings.ToLower(confidence) {
|
|
case "low":
|
|
confidenceEmoji = "🟧"
|
|
case "medium":
|
|
confidenceEmoji = "🟨"
|
|
case "high":
|
|
confidenceEmoji = "🟩"
|
|
}
|
|
|
|
var linkToFile strings.Builder
|
|
|
|
if isGithub {
|
|
linkToFile.WriteString("./blob/commit/")
|
|
} else {
|
|
linkToFile.WriteString("./src/commit/")
|
|
}
|
|
|
|
linkToFile.WriteString(ref.Hash().String() + "/" + *location.PhysicalLocation.ArtifactLocation.URI + "#L" + strconv.Itoa(*location.PhysicalLocation.Region.StartLine))
|
|
|
|
rows = append(rows, []string{
|
|
fmt.Sprintf("`%s` - %s", *result.RuleID, *rule.Name),
|
|
fmt.Sprintf("%s **%c**", severityEmoji, severity[0]),
|
|
fmt.Sprintf("%s **%s**", confidenceEmoji, strings.ToUpper(string(confidence[0]))),
|
|
fmt.Sprintf("[%s L%d C%d](%s)", *location.PhysicalLocation.ArtifactLocation.URI, *location.PhysicalLocation.Region.StartLine, *location.PhysicalLocation.Region.StartColumn, linkToFile.String()),
|
|
})
|
|
|
|
}
|
|
|
|
var markdownOutput strings.Builder
|
|
markdownHandler := markdown.NewMarkdown(&markdownOutput)
|
|
markdownHandler.H1("GoSec Report:")
|
|
markdownHandler.PlainText("This report automatically updates each time the action runs.\n")
|
|
|
|
if len(rows) == 0 {
|
|
markdownHandler.PlainText("**Nothing Found! 🥳**")
|
|
} else {
|
|
markdownHandler.PlainText("<details>")
|
|
markdownHandler.PlainText("<summary>Results:</summary>\n")
|
|
if sevCountMap["high"] != 0 {
|
|
markdownHandler.PlainText(fmt.Sprintf("🟥 **%d** high severity issues\n", sevCountMap["high"]))
|
|
}
|
|
if sevCountMap["medium"] != 0 {
|
|
markdownHandler.PlainText(fmt.Sprintf("🟧 **%d** medium severity issues\n", sevCountMap["medium"]))
|
|
}
|
|
if sevCountMap["low"] != 0 {
|
|
markdownHandler.PlainText(fmt.Sprintf("🟨 **%d** low severity issues\n", sevCountMap["low"]))
|
|
}
|
|
markdownHandler.PlainTextf("Total of **%d** issues.\n", sevCountMap["high"]+sevCountMap["medium"]+sevCountMap["low"])
|
|
}
|
|
|
|
if len(rows) != 0 {
|
|
markdownHandler.CustomTable(markdown.TableSet{
|
|
Header: []string{"Rule", "Severity", "Confidence", "Location"},
|
|
Rows: rows,
|
|
}, markdown.TableOptions{
|
|
AutoWrapText: false,
|
|
AutoFormatHeaders: false,
|
|
})
|
|
markdownHandler.PlainText("</details>")
|
|
}
|
|
|
|
err = markdownHandler.Build()
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
markdownOutputStr := markdownOutput.String()
|
|
|
|
if isAction {
|
|
action.AddStepSummary(markdownOutputStr)
|
|
}
|
|
|
|
actionCtx, err := action.Context()
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
forgeClient, err := forgejo.NewClient(actionCtx.ServerURL, forgejo.SetToken(os.Getenv("GITHUB_TOKEN")))
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
repoOwner, repoName := actionCtx.Repo()
|
|
|
|
issues, _, err := forgeClient.ListIssues(forgejo.ListIssueOption{
|
|
CreatedBy: "forgejo-actions",
|
|
KeyWord: "GoSec Report",
|
|
Type: forgejo.IssueTypeIssue,
|
|
})
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
if len(issues) == 0 {
|
|
if _, _, err := forgeClient.CreateIssue(repoOwner, repoName, forgejo.CreateIssueOption{
|
|
Title: "GoSec Report",
|
|
Body: markdownOutputStr,
|
|
}); err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
} else {
|
|
for _, issue := range issues {
|
|
if issue.Title == "GoSec Report" && strings.Contains(issue.Body, "GoSec Report:") {
|
|
if _, _, err := forgeClient.EditIssue(repoOwner, repoName, issue.ID, forgejo.EditIssueOption{
|
|
Body: &markdownOutputStr,
|
|
}); err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
},
|
|
}
|
|
|
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
|
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
|
func Execute() {
|
|
err := rootCmd.Execute()
|
|
if err != nil {
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.Flags().BoolVar(&isAction, "is-action", false, "If set, will run some things specific to git actions")
|
|
rootCmd.Flags().BoolVar(&isGithub, "is-github", false, "If set, will change some outputs to support github")
|
|
}
|