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

Add support for repo-url and branch name in CLI flags and scan output #1059

Merged
merged 2 commits into from
Oct 21, 2021
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
8 changes: 6 additions & 2 deletions docs/usage/command_line_mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,9 @@ aws_ecr_repository:
|--webhook-token string| Token used for sending authenticated requests to the notification webhook | This flag is optional when using the notification webhook|
|--webhook-url | A webhook URL where Terrascan will send JSON scan report and normalized IaC JSON | This overrides any notification webhook URLs configured in config TOML file specified with the `-c` flag|
|--use-terraform-cache |Use this to refer terraform remote modules from terraform init cache rather than downloading | By default remote module will be downloaded in temporary directory. If this flag is set then modules will be refered from terraform init cache if module is not present in terraform init cache it will be downloaded. Directory will be scanned non recurively if this flag is used.(applicable only with terraform IaC provider)|
| --find-vuln | find vulnerbilities | Use this to fetch vulnerabilities identified on the registry for docker images present in IaC the files scanned |
| --find-vuln | find vulnerabilities | Use this to fetch vulnerabilities identified on the registry for docker images present in IaC the files scanned |
| --repo-url | repository url | This flag can be used to include the repository URL as part of scan results and notifications |
| --repo-ref | repository branch name | This flag can be used to include the repository branch name as part of scan results and notifications |
| -v | verbose | Displays violations with all details |

| Global flags | Description | Options |
Expand Down Expand Up @@ -319,7 +321,9 @@ Flags:
-p, --policy-path stringArray policy path directory
-t, --policy-type strings policy type (all, aws, azure, docker, gcp, github, k8s) (default [all])
-r, --remote-type string type of remote backend (git, s3, gcs, http, terraform-registry)
-u, --remote-url string url pointing to remote IaC repository
-u, --remote-url string url pointing to remote IaC repository
--repo-ref string branch of the repo being scanned
--repo-url string URL of the repo being scanned, will be reflected in scan summary
--scan-rules strings one or more rules to scan (example: --scan-rules="ruleID1,ruleID2")
--severity string minimum severity level of the policy violations to be reported by terrascan
--show-passed display passed rules, along with violations
Expand Down
12 changes: 10 additions & 2 deletions pkg/cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ type ScanOptions struct {

// notificationWebhookToken is the auth token to call the notification webhook URL
notificationWebhookToken string

// repoURL lets us specify URL of the repository being scanned
repoURL string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please evaluate how the branch information can be captured along with repo url


// repoRef lets us specify the branch of the repository being scanned
repoRef string
}

// NewScanOptions returns a new pointer to ScanOptions
Expand Down Expand Up @@ -192,8 +198,10 @@ func (s *ScanOptions) Run() error {
}

// create a new runtime executor for processing IaC
executor, err := runtime.NewExecutor(s.iacType, s.iacVersion, s.policyType,
s.iacFilePath, s.iacDirPath, s.policyPath, s.scanRules, s.skipRules, s.categories, s.severity, s.nonRecursive, s.useTerraformCache, s.findVulnerabilities, s.notificationWebhookURL, s.notificationWebhookToken)
executor, err := runtime.NewExecutor(s.iacType, s.iacVersion, s.policyType, s.iacFilePath, s.iacDirPath,
s.policyPath, s.scanRules, s.skipRules, s.categories, s.severity, s.nonRecursive, s.useTerraformCache,
s.findVulnerabilities, s.notificationWebhookURL, s.notificationWebhookToken, s.repoURL, s.repoRef,
)
if err != nil {
return err
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/cli/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,7 @@ func init() {
scanCmd.Flags().BoolVarP(&scanOptions.findVulnerabilities, "find-vuln", "", false, "fetches vulnerabilities identified in Docker images")
scanCmd.Flags().StringVarP(&scanOptions.notificationWebhookURL, "webhook-url", "", "", "webhook URL where Terrascan will send JSON scan report and normalized IaC JSON")
scanCmd.Flags().StringVarP(&scanOptions.notificationWebhookToken, "webhook-token", "", "", "optional token used when sending authenticated requests to the notification webhook")
scanCmd.Flags().StringVarP(&scanOptions.repoURL, "repo-url", "", "", "URL of the repo being scanned, will be reflected in scan summary")
scanCmd.Flags().StringVarP(&scanOptions.repoRef, "repo-ref", "", "", "branch of the repo being scanned")
RegisterCommand(rootCmd, scanCmd)
}
4 changes: 2 additions & 2 deletions pkg/http-server/file-scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,10 @@ func (g *APIHandler) scanFile(w http.ResponseWriter, r *http.Request) {
var executor *runtime.Executor
if g.test {
executor, err = runtime.NewExecutor(iacType, iacVersion, cloudType,
tempFile.Name(), "", []string{"./testdata/testpolicies"}, scanRules, skipRules, categories, severity, false, false, false, notificationWebhookURL, notificationWebhookToken)
tempFile.Name(), "", []string{"./testdata/testpolicies"}, scanRules, skipRules, categories, severity, false, false, false, notificationWebhookURL, notificationWebhookToken, "", "")
} else {
executor, err = runtime.NewExecutor(iacType, iacVersion, cloudType,
tempFile.Name(), "", getPolicyPathFromConfig(), scanRules, skipRules, categories, severity, false, false, findVulnerabilities, notificationWebhookURL, notificationWebhookToken)
tempFile.Name(), "", getPolicyPathFromConfig(), scanRules, skipRules, categories, severity, false, false, findVulnerabilities, notificationWebhookURL, notificationWebhookToken, "", "")
}
if err != nil {
zap.S().Error(err)
Expand Down
3 changes: 2 additions & 1 deletion pkg/http-server/remote-repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type scanRemoteRepoReq struct {
d downloader.Downloader
NotificationWebhookURL string `json:"webhook_url"`
NotificationWebhookToken string `json:"webhook_token"`
RepoRef string `json:"repo-ref"`
}

// scanRemoteRepo downloads the remote Iac repository and scans it for
Expand Down Expand Up @@ -131,7 +132,7 @@ func (s *scanRemoteRepoReq) ScanRemoteRepo(iacType, iacVersion string, cloudType

// create a new runtime executor for scanning the remote repo
executor, err := runtime.NewExecutor(iacType, iacVersion, cloudType,
"", iacDirPath, policyPath, s.ScanRules, s.SkipRules, s.Categories, s.Severity, s.NonRecursive, false, s.FindVulnerabilities, s.NotificationWebhookURL, s.NotificationWebhookToken)
"", iacDirPath, policyPath, s.ScanRules, s.SkipRules, s.Categories, s.Severity, s.NonRecursive, false, s.FindVulnerabilities, s.NotificationWebhookURL, s.NotificationWebhookToken, s.RemoteURL, s.RepoRef)
if err != nil {
zap.S().Error(err)
return output, isAdmissionDenied, err
Expand Down
2 changes: 1 addition & 1 deletion pkg/http-server/webhook-scan-logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (g *APIHandler) getLogs(w http.ResponseWriter, r *http.Request) {
)

// Validate if authorized (API key is specified and matched the server one (saved in an environment variable)
validatingWebhook := admissionWebhook.NewValidatingWebhook([]byte(""), "", "")
validatingWebhook := admissionWebhook.NewValidatingWebhook([]byte(""), "", "", "", "")
if err := validatingWebhook.Authorize(apiKey); err != nil {
switch err {
case admissionWebhook.ErrAPIKeyMissing:
Expand Down
4 changes: 3 additions & 1 deletion pkg/http-server/webhook-scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ func (g *APIHandler) validateK8SWebhook(w http.ResponseWriter, r *http.Request)
qP = r.URL.Query()
notificationWebhookURL = qP.Get("webhook-url")
notificationWebhookToken = qP.Get("webhook-token")
repoURL = qP.Get("repo-url")
repoRef = qP.Get("repo-ref")
)

// Read the request into byte array
Expand All @@ -50,7 +52,7 @@ func (g *APIHandler) validateK8SWebhook(w http.ResponseWriter, r *http.Request)
}
zap.S().Debugf("scanning configuration webhook request: %+v", string(body))

validatingWebhook := admissionWebhook.NewValidatingWebhook(body, notificationWebhookURL, notificationWebhookToken)
validatingWebhook := admissionWebhook.NewValidatingWebhook(body, notificationWebhookURL, notificationWebhookToken, repoURL, repoRef)
// Validate if authorized (API key is specified and matched the server one (saved in an environment variable)
if err := validatingWebhook.Authorize(apiKey); err != nil {
switch err {
Expand Down
10 changes: 7 additions & 3 deletions pkg/k8s/admission-webhook/validating-webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,19 @@ type ValidatingWebhook struct {
dblogger *dblogs.WebhookScanLogger
notificationWebhookURL string
notificationWebhookToken string
repoURL string
repoRef string
}

// NewValidatingWebhook returns a new, empty ValidatingWebhook struct
func NewValidatingWebhook(body []byte, notificationWebhookURL, notificationWebhookToken string) AdmissionWebhook {
func NewValidatingWebhook(body []byte, notificationWebhookURL, notificationWebhookToken, repoURL, repoRef string) AdmissionWebhook {
return ValidatingWebhook{
dblogger: dblogs.NewWebhookScanLogger(),
requestBody: body,
notificationWebhookURL: notificationWebhookURL,
notificationWebhookToken: notificationWebhookToken,
repoURL: repoURL,
repoRef: repoRef,
}
}

Expand Down Expand Up @@ -195,10 +199,10 @@ func (w ValidatingWebhook) scanK8sFile(filePath string) (runtime.Output, error)

if flag.Lookup("test.v") != nil {
executor, err = runtime.NewExecutor("k8s", "v1", []string{"k8s"},
filePath, "", []string{testPoliciesPath}, []string{}, []string{}, []string{}, "", false, false, false, w.notificationWebhookURL, w.notificationWebhookToken)
filePath, "", []string{testPoliciesPath}, []string{}, []string{}, []string{}, "", false, false, false, w.notificationWebhookURL, w.notificationWebhookToken, w.repoURL, w.repoRef)
} else {
executor, err = runtime.NewExecutor("k8s", "v1", []string{"k8s"},
filePath, "", []string{}, []string{}, []string{}, []string{}, "", false, false, false, w.notificationWebhookURL, w.notificationWebhookToken)
filePath, "", []string{}, []string{}, []string{}, []string{}, "", false, false, false, w.notificationWebhookURL, w.notificationWebhookToken, w.repoURL, w.repoRef)
}
if err != nil {
zap.S().Errorf("failed to create runtime executer: '%v'", err)
Expand Down
1 change: 1 addition & 0 deletions pkg/results/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type ViolationStore struct {
// ScanSummary will hold the default scan summary data
type ScanSummary struct {
ResourcePath string `json:"file/folder" yaml:"file/folder" xml:"file_folder,attr"`
Branch string `json:"branch,omitempty" yaml:"branch,omitempty" xml:"branch,attr,omitempty"`
IacType string `json:"iac_type" yaml:"iac_type" xml:"iac_type,attr"`
Timestamp string `json:"scanned_at" yaml:"scanned_at" xml:"scanned_at,attr"`
ShowViolationDetails bool `json:"-" yaml:"-" xml:"-"`
Expand Down
10 changes: 9 additions & 1 deletion pkg/runtime/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@ type Executor struct {
vulnerabilityEngine vulnerability.Engine
notificationWebhookURL string
notificationWebhookToken string
repoURL string
repoRef string
}

// NewExecutor creates a runtime object
func NewExecutor(iacType, iacVersion string, policyTypes []string, filePath, dirPath string, policyPath, scanRules, skipRules, categories []string, severity string, nonRecursive, useTerraformCache, findVulnerabilities bool, notificationWebhookURL, notificationWebhookToken string) (e *Executor, err error) {
func NewExecutor(iacType, iacVersion string, policyTypes []string, filePath, dirPath string, policyPath, scanRules, skipRules, categories []string, severity string, nonRecursive, useTerraformCache, findVulnerabilities bool, notificationWebhookURL, notificationWebhookToken, repoURL, repoRef string) (e *Executor, err error) {
e = &Executor{
filePath: filePath,
dirPath: dirPath,
Expand All @@ -76,6 +78,8 @@ func NewExecutor(iacType, iacVersion string, policyTypes []string, filePath, dir
findVulnerabilities: findVulnerabilities,
notificationWebhookURL: notificationWebhookURL,
notificationWebhookToken: notificationWebhookToken,
repoURL: repoURL,
repoRef: repoRef,
}

// assigning vulnerabilityEngine
Expand Down Expand Up @@ -266,6 +270,10 @@ func (e *Executor) Execute(configOnly bool) (results Output, err error) {
}

// send notifications, if configured
if e.repoURL != "" {
results.Violations.Summary.ResourcePath = e.repoURL
results.Violations.Summary.Branch = e.repoRef
}
e.SendNotifications(results)

// successful
Expand Down
4 changes: 3 additions & 1 deletion pkg/runtime/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,8 @@ type flagSet struct {
skipRules []string
notificationWebhookURL string
notificationWebhookToken string
repoURL string
repoRef string
}

func TestNewExecutor(t *testing.T) {
Expand Down Expand Up @@ -596,7 +598,7 @@ func TestNewExecutor(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
config.LoadGlobalConfig(tt.configfile)

gotExecutor, gotErr := NewExecutor(tt.flags.iacType, tt.flags.iacVersion, tt.flags.policyTypes, tt.flags.filePath, tt.flags.dirPath, tt.flags.policyPath, tt.flags.scanRules, tt.flags.skipRules, tt.flags.categories, tt.flags.severity, false, false, false, tt.flags.notificationWebhookURL, tt.flags.notificationWebhookToken)
gotExecutor, gotErr := NewExecutor(tt.flags.iacType, tt.flags.iacVersion, tt.flags.policyTypes, tt.flags.filePath, tt.flags.dirPath, tt.flags.policyPath, tt.flags.scanRules, tt.flags.skipRules, tt.flags.categories, tt.flags.severity, false, false, false, tt.flags.notificationWebhookURL, tt.flags.notificationWebhookToken, tt.flags.repoURL, tt.flags.repoRef)

if !reflect.DeepEqual(tt.wantErr, gotErr) {
t.Errorf("Mismatch in error => got: '%v', want: '%v'", gotErr, tt.wantErr)
Expand Down
14 changes: 11 additions & 3 deletions pkg/writer/human_readable.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,23 @@ func detailedViolations(v results.Violation) string {
}

func scanSummary(s results.ScanSummary) string {
out := fmt.Sprintf("%-20v:\t%s\n\t%-20v:\t%s\n\t%-20v:\t%s\n\t%-20v:\t%d\n\t%-20v:\t%d\n\t%-20v:\t%d\n\t%-20v:\t%d\n\t%-20v:\t%d\n\t",
"File/Folder", s.ResourcePath,

out := fmt.Sprintf("%-20v:\t%s\n\t",
"File/Folder", s.ResourcePath)

if s.Branch != "" {
out += fmt.Sprintf("%-20v:\t%s\n\t", "Branch", s.Branch)
}

out += fmt.Sprintf("%-20v:\t%s\n\t%-20v:\t%s\n\t%-20v:\t%d\n\t%-20v:\t%d\n\t%-20v:\t%d\n\t%-20v:\t%d\n\t%-20v:\t%d\n\t",
"IaC Type", s.IacType,
"Scanned At", s.Timestamp,
"Policies Validated", s.TotalPolicies,
"Violated Policies", s.ViolatedPolicies,
"Low", s.LowCount,
"Medium", s.MediumCount,
"High", s.HighCount)
"High", s.HighCount,
)

if s.Vulnerabilities != nil {
out += fmt.Sprintf("%-20v:\t%d\n\t", "Vulnerabilities", *s.Vulnerabilities)
Expand Down
32 changes: 32 additions & 0 deletions pkg/writer/human_readable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@ var (
},
},
}
summaryWithRepoURLRepoRef = results.ScanSummary{
ResourcePath: "https://github.com/user/repository.git",
Branch: "main",
IacType: "terraform",
Timestamp: "2020-12-12 11:21:29.902796 +0000 UTC",
TotalPolicies: 566,
LowCount: 0,
MediumCount: 0,
HighCount: 1,
ViolatedPolicies: 1,
}
)

const (
Expand Down Expand Up @@ -165,6 +176,18 @@ Scan Summary -
Medium : 0
High : 1
Vulnerabilities : 1`

expectedOutput4 = `Scan Summary -

File/Folder : https://github.com/user/repository.git
Branch : main
IaC Type : terraform
Scanned At : 2020-12-12 11:21:29.902796 +0000 UTC
Policies Validated : 566
Violated Policies : 1
Low : 0
Medium : 0
High : 1`
)

func TestHumanReadbleWriter(t *testing.T) {
Expand Down Expand Up @@ -200,6 +223,15 @@ func TestHumanReadbleWriter(t *testing.T) {
input: vulnerabilitiesInputHumanReadable,
expectedOutput: vulnerabilityScanOutputHumanReadable,
},
{
name: "Human Readable Writer: with repository url and branch",
input: policy.EngineOutput{
ViolationStore: &results.ViolationStore{
Summary: summaryWithRepoURLRepoRef,
},
},
expectedOutput: expectedOutput4,
},
}

for _, tt := range tests {
Expand Down
2 changes: 2 additions & 0 deletions pkg/writer/junit_xml.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type JUnitTestSuite struct {
Time string `xml:"time,attr"`
Name string `xml:"name,attr"`
Package string `xml:"package,attr"`
Branch string `xml:"branch,attr,omitempty"`
Properties []JUnitProperty `xml:"properties>property,omitempty"`
TestCases []JUnitTestCase
}
Expand Down Expand Up @@ -103,6 +104,7 @@ func newJunitTestSuite(summary results.ScanSummary) JUnitTestSuite {
Time: fmt.Sprint(summary.TotalTime),
Failures: summary.ViolatedPolicies,
Package: summary.ResourcePath,
Branch: summary.Branch,
Properties: []JUnitProperty{
{
Name: "Terrascan Version",
Expand Down
21 changes: 21 additions & 0 deletions pkg/writer/junit_xml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ func TestJUnitXMLWriter(t *testing.T) {
</testsuites>
`, version.Get())

testOutputRepoURLRepoRef := fmt.Sprintf(`
<testsuites tests="566" name="TERRASCAN_POLICY_SUITES" failures="1" time="0">
<testsuite tests="566" failures="1" time="0" name="TERRASCAN_POLICY_SUITE" package="https://github.com/user/repository.git" branch="main">
<properties>
<property name="Terrascan Version" value="%s"></property>
</properties>
</testsuite>
</testsuites>
`, version.Get())

type args struct {
data interface{}
}
Expand Down Expand Up @@ -108,6 +118,17 @@ func TestJUnitXMLWriter(t *testing.T) {
},
wantWriter: testOutputPassedRules,
},
{
name: "data with repository url and branch",
args: args{
policy.EngineOutput{
ViolationStore: &results.ViolationStore{
Summary: summaryWithRepoURLRepoRef,
},
},
},
wantWriter: testOutputRepoURLRepoRef,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions test/e2e/help/golden/help_scan.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Flags:
-t, --policy-type strings policy type (all, aws, azure, docker, gcp, github, k8s) (default [all])
-r, --remote-type string type of remote backend (git, s3, gcs, http, terraform-registry)
-u, --remote-url string url pointing to remote IaC repository
--repo-ref string branch of the repo being scanned
--repo-url string URL of the repo being scanned, will be reflected in scan summary
--scan-rules strings one or more rules to scan (example: --scan-rules="ruleID1,ruleID2")
--severity string minimum severity level of the policy violations to be reported by terrascan
--show-passed display passed rules, along with violations
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Violation Details -

Description : Enable AWS AMI Encryption
File : main.tf
Module Name : root
Plan Root : ./
Line : 5
Severity : MEDIUM

-----------------------------------------------------------------------


Scan Summary -

File/Folder : https://github.com/accurics/terrascan.git
Branch : main
IaC Type : terraform
Scanned At : 2021-03-02 15:45:17.636568 +0000 UTC
Policies Validated : 8
Violated Policies : 1
Low : 0
Medium : 1
High : 0
Loading