goscan/cmd/root.go

233 lines
6 KiB
Go
Raw Normal View History

/*
Package cmd
Copyright © 2024 Shane C. <shane@scaffoe.com>
*/
package cmd
import (
2024-09-06 20:08:36 +01:00
"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"
2024-09-06 15:03:37 +01:00
"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) {
2024-09-06 15:03:37 +01:00
action := githubactions.New()
cwd, err := os.Getwd()
if err != nil {
log.Fatalln(err)
}
pretty.Logln(cwd)
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]))),
2024-09-06 20:08:36 +01:00
fmt.Sprintf("[%s L%d C%d](%s)", *location.PhysicalLocation.ArtifactLocation.URI, *location.PhysicalLocation.Region.StartLine, *location.PhysicalLocation.Region.StartColumn, linkToFile.String()),
})
}
2024-09-06 15:03:37 +01:00
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"])
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()
2024-09-06 20:08:36 +01:00
if isAction {
action.AddStepSummary(markdownOutputStr)
2024-09-06 20:08:36 +01:00
}
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{
2024-09-06 21:40:38 +01:00
Type: forgejo.IssueTypeIssue,
})
if err != nil {
2024-09-06 20:08:36 +01:00
log.Fatalln(err)
}
2024-09-06 15:03:37 +01:00
2024-09-06 21:14:15 +01:00
file, err := os.OpenFile("testing.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
log.Fatalln(err)
}
defer file.Close()
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:") && actionCtx.Repository == issue.Repository.FullName {
if _, _, err := forgeClient.EditIssue(repoOwner, repoName, issue.Index, 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")
}