-
Notifications
You must be signed in to change notification settings - Fork 5
/
tfsec.go
122 lines (115 loc) · 5.26 KB
/
tfsec.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package ci
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/fairwindsops/insights-plugins/plugins/ci/pkg/commands"
"github.com/fairwindsops/insights-plugins/plugins/ci/pkg/models"
"github.com/hashicorp/go-multierror"
"github.com/sirupsen/logrus"
)
const DefaultCustomCheckRuleID = "tfsec_custom_check"
func (ci *CIScan) TerraformEnabled() bool {
return *ci.config.Reports.TFSec.Enabled
}
func (ci *CIScan) ProcessTerraformPaths() (report *models.ReportInfo, errs error) {
logrus.Infof("processing %d Terraform paths", len(ci.config.Terraform.Paths))
if len(ci.config.Terraform.Paths) == 0 {
return nil, nil
}
allErrs := new(multierror.Error)
var reportProperties models.TFSecReportProperties
TFSecVersion, err := commands.Exec("tfsec", "-v")
if err != nil {
return nil, fmt.Errorf("cannot get the version of tfsec: %v: %s", err, TFSecVersion)
}
TFSecVersion = strings.TrimPrefix(TFSecVersion, "v")
for _, terraformPath := range ci.config.Terraform.Paths {
results, err := ci.ProcessTerraformPath(terraformPath)
if err != nil {
allErrs = multierror.Append(allErrs, fmt.Errorf("unable to process path %q: %v", terraformPath, err))
}
if len(results) > 0 {
reportProperties.Items = append(reportProperties.Items, results...)
}
}
var allErrsCombined error = nil
if len(allErrs.Errors) > 0 { // keep the multierror from becoming individual action items
allErrsCombined = fmt.Errorf("%v", allErrs.Error()) // return string representation from the multierror
}
if len(reportProperties.Items) == 0 {
logrus.Infof("there were no tfsec findings after processing %d paths\n", len(ci.config.Terraform.Paths))
return nil, allErrsCombined
}
file, err := json.MarshalIndent(reportProperties, "", " ")
if err != nil {
return nil, fmt.Errorf("while encoding report output: %w", err)
}
report = &models.ReportInfo{
Report: "tfsec",
Version: TFSecVersion,
Filename: "tfsec.json",
}
err = os.WriteFile(filepath.Join(ci.config.Options.TempFolder, report.Filename), file, 0644)
if err != nil {
return nil, fmt.Errorf("while writing report output: %w", err)
}
return report, allErrsCombined
}
func (ci *CIScan) ProcessTerraformPath(terraformPath string) ([]models.TFSecResult, error) {
terraformPathAsFileName := strings.ReplaceAll(strings.TrimPrefix(terraformPath, ci.repoBaseFolder), "/", "_")
outputFile := filepath.Join(ci.config.Options.TempFolder, fmt.Sprintf("tfsec-output-%s", terraformPathAsFileName))
customChecks := ci.config.Reports.TFSec.CustomChecksDirectory != nil && *ci.config.Reports.TFSec.CustomChecksDirectory != ""
params := []string{}
if customChecks {
params = append(params, "--custom-check-dir", *ci.config.Reports.TFSec.CustomChecksDirectory)
}
// The -s avoids tfsec exiting with an error value for scan warnings.
//configFile, configFilePath, "-s", "-f", "json", "-O", outputFile, terraformPath
params = append(params, "-s", "-f", "json", "-O", outputFile, terraformPath)
logrus.Info("tfsec ", params)
output, err := commands.ExecWithMessage(exec.Command("tfsec", params...), "scanning Terraform in "+terraformPath)
if err != nil {
return nil, fmt.Errorf("%v: %s", err, output)
}
var reportProperties models.TFSecReportProperties
data, err := os.ReadFile(outputFile)
if err != nil {
logrus.Errorf("Error reading tfsec output from %s: %v", outputFile, err)
return nil, fmt.Errorf("while reading output from %s: %w", outputFile, err)
}
err = json.Unmarshal(data, &reportProperties)
if err != nil {
logrus.Errorf("Error decoding tfsec output from %s: %v", outputFile, err)
return nil, fmt.Errorf("while decoding output from %s: %w", outputFile, err)
}
logrus.Infof("%d tfsec results for path %s", len(reportProperties.Items), terraformPath)
logrus.Debugf("Removing the base repository path %q from the file name of each tfsec result", ci.repoBaseFolder)
for i := range reportProperties.Items {
newFileName := reportProperties.Items[i].Location.FileName
if strings.HasPrefix(reportProperties.Items[i].Location.FileName, "terraform-aws-modules/") {
logrus.Debugf("preppending %q to filename %q because it refers to a Terraform module", terraformPath, newFileName)
newFileName = filepath.Join(terraformPath, newFileName)
}
newFileName = strings.TrimPrefix(newFileName, ci.repoBaseFolder+"/") // trim base folder as-is
absRepoBaseFolder, err := filepath.Abs(ci.repoBaseFolder) // Also attempt to trim the absolute version of the same path
if err != nil {
logrus.Warnf("tfsec result filenames will retain the repository base folder of %q because it was unable to be trimmed as an absolute path: %v", ci.repoBaseFolder, err)
} else {
newFileName = strings.TrimPrefix(newFileName, absRepoBaseFolder+"/")
}
logrus.Debugf("updating filename %q to be relative to the repository: %q", reportProperties.Items[i].Location.FileName, newFileName)
reportProperties.Items[i].Location.FileName = newFileName
if len(reportProperties.Items[i].RuleID) == 0 {
reportProperties.Items[i].RuleID = strings.TrimPrefix(reportProperties.Items[i].LongID, "custom-custom-")
if len(reportProperties.Items[i].RuleID) == 0 {
reportProperties.Items[i].RuleID = DefaultCustomCheckRuleID
}
}
}
logrus.Debugf("tfsec output for %s: %#v", terraformPath, reportProperties)
return reportProperties.Items, nil
}