Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds functionality to generate scan report in JSON format (#104) #7

Merged
merged 1 commit into from
Mar 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ fileignoreconfig:
- filename : private.pem
checksum :
ignore_detectors : [filename,filecontent]
- filename: detector/pattern_detector.go
checksum: 1c46863e979796d929522d73c465643b8794cccbf0054cb1ca6903a143e54757
ignore_detectors: []
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,16 @@ To run the scanner please "cd" into the directory to be scanned and run the foll

* `talisman --scan`

Running this command will create a folder named <i>talisman_reports</i> in the root of the current directory and store the report files there.

In case you want to store the reports in some other location, it can be provided as an option with the command:

* `talisman --scan --reportdirectory=/Users/username/Desktop`

OR

* `talisman --scan --rd=/Users/username/Desktop`

<i>Talisman currently does not support ignoring of files for scanning.</i>

# Uninstallation
Expand Down
29 changes: 19 additions & 10 deletions detector/detection_results.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ type FailureData struct {
type DetectionResults struct {
Failures map[git_repo.FilePath]*FailureData
ignores map[git_repo.FilePath][]string
warnings map[git_repo.FilePath][]string
warnings map[git_repo.FilePath]*FailureData
}

//NewDetectionResults is a new DetectionResults struct. It represents the pre-run state of a Detection run.
func NewDetectionResults() *DetectionResults {
result := DetectionResults{make(map[git_repo.FilePath]*FailureData), make(map[git_repo.FilePath][]string), make(map[git_repo.FilePath][]string)}
result := DetectionResults{make(map[git_repo.FilePath]*FailureData), make(map[git_repo.FilePath][]string), make(map[git_repo.FilePath]*FailureData)}
return &result
}

Expand All @@ -48,12 +48,18 @@ func (r *DetectionResults) Fail(filePath git_repo.FilePath, message string, comm
}
}

func (r *DetectionResults) Warn(filePath git_repo.FilePath, message string) {
warnings, ok := r.warnings[filePath]
if !ok {
r.warnings[filePath] = []string{message}
func (r *DetectionResults) Warn(filePath git_repo.FilePath, message string, commits []string) {
if r.warnings[filePath] == nil {
r.warnings[filePath] = &FailureData{make(map[string][]string)}
}
if r.warnings[filePath].FailuresInCommits == nil {
r.warnings[filePath].FailuresInCommits = make(map[string][]string)
}
existingCommits := r.warnings[filePath].FailuresInCommits[message]
if len(existingCommits) == 0 {
r.warnings[filePath].FailuresInCommits[message] = commits
} else {
r.warnings[filePath] = append(warnings, message)
r.warnings[filePath].FailuresInCommits[message] = append(r.warnings[filePath].FailuresInCommits[message], commits...)
}
}

Expand Down Expand Up @@ -185,9 +191,12 @@ func (r *DetectionResults) ReportFileFailures(filePath git_repo.FilePath) [][]st
func (r *DetectionResults) ReportFileWarnings(filePath git_repo.FilePath) [][]string {
warnings := r.warnings[filePath]
var data [][]string
if len(warnings) > 0 {
for _, warning := range warnings {
data = append(data, []string{string(filePath), warning})
if len(warnings.FailuresInCommits) > 0 {
for warningMessage := range warnings.FailuresInCommits {
if len(warningMessage) > 150 {
warningMessage = warningMessage[:150] + "\n" + warningMessage[150:]
}
data = append(data, []string{string(filePath), warningMessage})
}
}
return data
Expand Down
2 changes: 1 addition & 1 deletion detector/filecontent_detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func fillResults(results []string, addition git_repo.Addition, result *Detection
"filePath": addition.Path,
}).Info(info)
if string(addition.Name) == DefaultRCFileName {
result.Warn(addition.Path, fmt.Sprintf(output, res))
result.Warn(addition.Path, fmt.Sprintf(output, res), []string{})
} else {
result.Fail(addition.Path, fmt.Sprintf(output, res), []string{})
}
Expand Down
72 changes: 72 additions & 0 deletions detector/json_detection_results.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package detector

import "talisman/git_repo"

type Details struct {
Category string `json:"type"`
Message string `json:"message"`
Commits []string `json:"commits"`
}

type ResultsDetails struct {
Filename git_repo.FilePath `json:"filename"`
FailureList []Details `json:"failure_list"`
WarningList []Details `json:"warning_list"`
}

type FailureTypes struct {
Filecontent int `json:"filecontent"`
Filesize int `json:"filesize"`
Filename int `json:"filename"`
}

type ResultsSummary struct {
Types FailureTypes `json:"types"`
}

type JsonDetectionResults struct {
Summary ResultsSummary `json:"summary"`
Results []ResultsDetails `json:"results"`

}

func (result JsonDetectionResults) getResultObjectForFileName(filename git_repo.FilePath) ResultsDetails {
for _, resultDetails := range result.Results {
if resultDetails.Filename == filename {
return resultDetails
}
}
return ResultsDetails{"", make([]Details, 0), make([]Details, 0)}
}

func GetJsonSchema(r *DetectionResults) JsonDetectionResults {

jsonResults := JsonDetectionResults{}
failures := r.Failures
for path, data := range failures {
resultDetails := ResultsDetails{}
resultDetails.Filename = path

for message, commits := range data.FailuresInCommits {
failureDetails := Details{}
failureDetails.Message = message
failureDetails.Commits = commits
resultDetails.FailureList = append(resultDetails.FailureList, failureDetails)
}
jsonResults.Results = append(jsonResults.Results, resultDetails)
}
warnings := r.warnings
for path, data := range warnings {
resultDetails := jsonResults.getResultObjectForFileName(path)
resultDetails.Filename = path

for message, commits := range data.FailuresInCommits {
failureDetails := Details{}
failureDetails.Message = message
failureDetails.Commits = commits
resultDetails.WarningList = append(resultDetails.WarningList, failureDetails)
}
}
return jsonResults
}

2 changes: 1 addition & 1 deletion detector/pattern_detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func (detector PatternDetector) Test(additions []git_repo.Addition, ignoreConfig
"filePath": addition.Path,
"pattern": detection,
}).Warn("Warning file as it matched pattern.")
result.Warn(addition.Path, fmt.Sprintf("Potential secret pattern : %s", detection))
result.Warn(addition.Path, fmt.Sprintf("Potential secret pattern : %s", detection), addition.Commits)
} else {
log.WithFields(log.Fields{
"filePath": addition.Path,
Expand Down
39 changes: 34 additions & 5 deletions report/report.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,53 @@
package report

import (
"encoding/json"
"html/template"
"log"
"os"
"path/filepath"
"talisman/detector"
)

const reportsFolder = "talisman_reports"
const htmlFileName string = "report.html"
const jsonFileName string = "report.json"
// GenerateReport generates a talisman scan report in html format
func GenerateReport(r *detector.DetectionResults) {
func GenerateReport(r *detector.DetectionResults, directory string) string {

var path string
var htmlFilePath string
var jsonFilePath string

path = filepath.Join(directory, "talisman_reports")
htmlFilePath = filepath.Join(directory, reportsFolder, htmlFileName)
jsonFilePath = filepath.Join(directory, reportsFolder, jsonFileName)
os.MkdirAll(path, 0755)


reportHTML := getReportHTML()
reportTemplate := template.New("report")
reportTemplate, _ = reportTemplate.Parse(reportHTML)
htmlFile, err := os.Create(htmlFilePath)
if err != nil {
log.Fatal("Cannot create report.html file", err)
}
reportTemplate.ExecuteTemplate(htmlFile, "report", r)
htmlFile.Close()

jsonFile, err := os.Create(jsonFilePath)
if err != nil {
log.Fatal("Cannot create report.json file", err)

file, err := os.Create("report.html")
}
jsonResultSchema := detector.GetJsonSchema(r)
jsonString, err := json.Marshal(jsonResultSchema)
if err != nil {
log.Fatal("Cannot create file", err)
log.Fatal("Unable to marshal JSON")
}
reportTemplate.ExecuteTemplate(file, "report", r)
file.Close()
jsonFile.Write(jsonString)
jsonFile.Close()
return path
}

func getReportHTML() string {
Expand Down
7 changes: 4 additions & 3 deletions runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ func (r *Runner) RunWithoutErrors() int {
}

//Scan scans git commit history for potential secrets and returns 0 or 1 as exit code
func (r *Runner) Scan() int {
func (r *Runner) Scan(reportDirectory string) int {

fmt.Println("Please wait while talisman scans entire repository including the git history...")
additions := scanner.GetAdditions()
ignores := detector.TalismanRCIgnore{}
detector.DefaultChain().Test(additions, ignores, r.results)
report.GenerateReport(r.results)
fmt.Println("Please check report.html in your current directory for the talisman scan report")
reportsPath := report.GenerateReport(r.results, reportDirectory)
fmt.Printf("Please check %s folder for the talisman scan report", reportsPath)
return r.exitStatus()
}

Expand Down
7 changes: 6 additions & 1 deletion talisman.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var (
Version = "Development Build"
scan bool
checksum string
reportdirectory string
)

const (
Expand All @@ -41,6 +42,7 @@ type options struct {
pattern string
scan bool
checksum string
reportdirectory string
}

//Logger is the default log device, set to emit at the Error level by default
Expand All @@ -56,6 +58,8 @@ func main() {
flag.BoolVar(&scan, "scan", false, "scanner scans the git commit history for potential secrets")
flag.StringVar(&checksum, "c", "", "short form of checksum calculator")
flag.StringVar(&checksum, "checksum", "", "checksum calculator calculates checksum and suggests .talsimarc format")
flag.StringVar(&reportdirectory, "reportdirectory", "", "directory where the scan reports will be stored")
flag.StringVar(&reportdirectory, "rd", "", "short form of report directory")

flag.Parse()

Expand All @@ -75,6 +79,7 @@ func main() {
pattern: pattern,
scan: scan,
checksum: checksum,
reportdirectory: reportdirectory,
}

os.Exit(run(os.Stdin, _options))
Expand All @@ -97,7 +102,7 @@ func run(stdin io.Reader, _options options) (returnCode int) {
return NewRunner(make([]git_repo.Addition, 0)).RunChecksumCalculator(strings.Fields(_options.checksum))
} else if _options.scan {
log.Infof("Running scanner")
return NewRunner(make([]git_repo.Addition, 0)).Scan()
return NewRunner(make([]git_repo.Addition, 0)).Scan(_options.reportdirectory)
} else if _options.pattern != "" {
log.Infof("Running %s pattern", _options.pattern)
directoryHook := NewDirectoryHook()
Expand Down