diff --git a/pkg/engine/inspector.go b/pkg/engine/inspector.go index 94b012ff999..2b6ffbac52d 100644 --- a/pkg/engine/inspector.go +++ b/pkg/engine/inspector.go @@ -348,9 +348,14 @@ func (c *Inspector) decodeQueryResults(ctx *QueryContext, results rego.ResultSet if _, ok := c.excludeResults[vulnerability.SimilarityID]; ok { log.Debug(). Msgf("Excluding result SimilarityID: %s", vulnerability.SimilarityID) - } else { - vulnerabilities = append(vulnerabilities, vulnerability) + continue + } else if checkComment(vulnerability.Line, file.LinesIgnore) { + log.Debug(). + Msgf("Excluding result Comment: %s", vulnerability.SimilarityID) + continue } + + vulnerabilities = append(vulnerabilities, vulnerability) } if failedDetectLine { @@ -360,6 +365,16 @@ func (c *Inspector) decodeQueryResults(ctx *QueryContext, results rego.ResultSet return vulnerabilities, nil } +// checkComment checks if the vulnerability should be skipped from comment +func checkComment(line int, ignoreLines []int) bool { + for _, ignoreLine := range ignoreLines { + if line == ignoreLine { + return true + } + } + return false +} + // contains is a simple method to check if a slice // contains an entry func contains(s []string, e string) bool { diff --git a/pkg/engine/inspector_test.go b/pkg/engine/inspector_test.go index 1e99a54776d..b696148fd96 100644 --- a/pkg/engine/inspector_test.go +++ b/pkg/engine/inspector_test.go @@ -363,7 +363,7 @@ func TestNewInspector(t *testing.T) { // nolint Query: "all_auth_users_get_read_access", Content: string(contentByte), InputData: "{}", - Platform: "cloudFormation", + Platform: "terraform", Metadata: map[string]interface{}{ "id": "57b9893d-33b1-4419-bcea-b828fb87e318", "queryName": "All Auth Users Get Read Access", @@ -371,7 +371,7 @@ func TestNewInspector(t *testing.T) { // nolint "category": "Access Control", "descriptionText": "Misconfigured S3 buckets can leak private information to the entire internet or allow unauthorized data tampering / deletion", // nolint "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket#acl", - "platform": "CloudFormation", + "platform": "Terraform", }, Aggregation: 1, }, @@ -521,18 +521,18 @@ func TestEngine_LenQueriesByPlat(t *testing.T) { { name: "test_len_queries_plat", args: args{ - queriesPath: filepath.FromSlash("./assets/queries"), + queriesPath: filepath.FromSlash("./test/fixtures"), platform: []string{"terraform"}, }, - min: 100, + min: 1, }, { name: "test_len_queries_plat_dockerfile", args: args{ - queriesPath: filepath.FromSlash("./assets/queries"), + queriesPath: filepath.FromSlash("././test/fixtures"), platform: []string{"dockerfile"}, }, - min: 50, + min: 1, }, } @@ -540,7 +540,7 @@ func TestEngine_LenQueriesByPlat(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ins := newInspectorInstance(t, tt.args.queriesPath) got := ins.LenQueriesByPlat(tt.args.platform) - require.True(t, got > tt.min) + require.True(t, got >= tt.min) }) } } @@ -560,7 +560,7 @@ func TestEngine_GetFailedQueries(t *testing.T) { { name: "test_get_failed_queries", args: args{ - queriesPath: filepath.FromSlash("./assets/queries"), + queriesPath: filepath.FromSlash("./test/fixtures"), nrFailedQueries: 5, }, }, @@ -696,3 +696,33 @@ func (m *mockSource) GetQueryLibrary(platform string) (source.RegoLibraries, err LibraryInputData: "{}", }, errGettingEmbeddedLibrary } + +func TestInspector_checkComment(t *testing.T) { + tests := []struct { + name string + lines []int + line int + want bool + }{ + { + name: "test_checkComment_true", + lines: []int{1, 2, 3, 4, 5, 6}, + line: 3, + want: true, + }, + { + name: "test_checkComment_false", + lines: []int{1, 2, 3, 4, 5, 6}, + line: 7, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := checkComment(tt.line, tt.lines); got != tt.want { + t.Errorf("checkComment() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/engine/source/filesystem_test.go b/pkg/engine/source/filesystem_test.go index ac125cde86c..9d22c03e2ba 100644 --- a/pkg/engine/source/filesystem_test.go +++ b/pkg/engine/source/filesystem_test.go @@ -102,9 +102,9 @@ func TestFilesystemSource_GetQueriesWithExclude(t *testing.T) { //nolint "id": "57b9893d-33b1-4419-bcea-b828fb87e318", "queryName": "All Auth Users Get Read Access", "severity": model.SeverityHigh, - "platform": "CloudFormation", + "platform": "Terraform", }, - Platform: "cloudFormation", + Platform: "terraform", Aggregation: 1, }, }, @@ -219,9 +219,9 @@ func TestFilesystemSource_GetQueriesWithInclude(t *testing.T) { "id": "57b9893d-33b1-4419-bcea-b828fb87e318", "queryName": "All Auth Users Get Read Access", "severity": model.SeverityHigh, - "platform": "CloudFormation", + "platform": "Terraform", }, - Platform: "cloudFormation", + Platform: "terraform", Aggregation: 1, }, }, @@ -433,9 +433,9 @@ func TestFilesystemSource_GetQueries(t *testing.T) { "id": "57b9893d-33b1-4419-bcea-b828fb87e318", "queryName": "All Auth Users Get Read Access", "severity": model.SeverityHigh, - "platform": "CloudFormation", + "platform": "Terraform", }, - Platform: "cloudFormation", + Platform: "terraform", Aggregation: 1, }, }, @@ -511,7 +511,7 @@ func Test_ReadMetadata(t *testing.T) { "id": "", "queryName": "", "severity": model.SeverityHigh, - "platform": "", + "platform": "Dockerfile", "aggregation": float64(1), }, wantErr: false, diff --git a/pkg/kics/sink.go b/pkg/kics/sink.go index caad9dd453c..ea49c78bdd8 100644 --- a/pkg/kics/sink.go +++ b/pkg/kics/sink.go @@ -61,6 +61,7 @@ func (s *Service) sink(ctx context.Context, filename, scanID string, rc io.Reade Kind: documents.Kind, FilePath: filename, Commands: fileCommands, + LinesIgnore: documents.IgnoreLines, } s.saveToFile(ctx, &file) } diff --git a/pkg/model/model.go b/pkg/model/model.go index 8e5d139d847..d74186b1039 100644 --- a/pkg/model/model.go +++ b/pkg/model/model.go @@ -2,6 +2,7 @@ package model import ( + "regexp" "sort" "strings" @@ -19,6 +20,13 @@ const ( KindHELM FileKind = "HELM" ) +// Constants to describe commands given from comments +const ( + IgnoreLine CommentCommand = "ignore-line" + IgnoreBlock CommentCommand = "ignore-block" + IgnoreComment CommentCommand = "ignore-comment" +) + // Constants to describe vulnerability's severity const ( SeverityHigh = "HIGH" @@ -52,6 +60,11 @@ var ( } ) +var ( + // KICSCommentRgxp is the regexp to identify if a comment is a KICS comment + KICSCommentRgxp = regexp.MustCompile(`^((/{2})|#)\s*kics\s*`) +) + // Version - is the model for the version response type Version struct { Latest bool `json:"is_latest"` @@ -65,6 +78,9 @@ type VulnerabilityLines struct { LineWithVulnerabilty string } +// CommentCommand represents a command given from a comment +type CommentCommand string + // FileKind is the extension of a file type FileKind string @@ -103,6 +119,7 @@ type FileMetadata struct { HelmID string IDInfo map[int]interface{} Commands CommentsCommands + LinesIgnore []int } // QueryMetadata is a representation of general information about a query diff --git a/pkg/parser/docker/parser.go b/pkg/parser/docker/parser.go index ee3f11cf721..046a0e7a946 100644 --- a/pkg/parser/docker/parser.go +++ b/pkg/parser/docker/parser.go @@ -37,13 +37,13 @@ func (p *Parser) Resolve(fileContent []byte, filename string) (*[]byte, error) { } // Parse - parses dockerfile to Json -func (p *Parser) Parse(_ string, fileContent []byte) ([]model.Document, error) { +func (p *Parser) Parse(_ string, fileContent []byte) ([]model.Document, []int, error) { var documents []model.Document reader := bytes.NewReader(fileContent) parsed, err := parser.Parse(reader) if err != nil { - return nil, errors.Wrap(err, "failed to parse Dockerfile") + return nil, []int{}, errors.Wrap(err, "failed to parse Dockerfile") } fromValue := "args" @@ -82,16 +82,16 @@ func (p *Parser) Parse(_ string, fileContent []byte) ([]model.Document, error) { j, err := json.Marshal(resource) if err != nil { - return nil, errors.Wrap(err, "failed to Marshal Dockerfile") + return nil, []int{}, errors.Wrap(err, "failed to Marshal Dockerfile") } if err := json.Unmarshal(j, &doc); err != nil { - return nil, errors.Wrap(err, "failed to Unmarshal Dockerfile") + return nil, []int{}, errors.Wrap(err, "failed to Unmarshal Dockerfile") } documents = append(documents, *doc) - return documents, nil + return documents, []int{}, nil } // GetKind returns the kind of the parser diff --git a/pkg/parser/docker/parser_test.go b/pkg/parser/docker/parser_test.go index 85828e24d37..85d53406f28 100644 --- a/pkg/parser/docker/parser_test.go +++ b/pkg/parser/docker/parser_test.go @@ -66,7 +66,7 @@ func TestParser_Parse(t *testing.T) { } for idx, sampleFile := range sample { - doc, err := p.Parse("Dockerfile", []byte(sampleFile)) + doc, _, err := p.Parse("Dockerfile", []byte(sampleFile)) switch idx { case 0: require.NoError(t, err) diff --git a/pkg/parser/json/parser.go b/pkg/parser/json/parser.go index 4eda49d72eb..c7a9087dbf1 100644 --- a/pkg/parser/json/parser.go +++ b/pkg/parser/json/parser.go @@ -18,13 +18,13 @@ func (p *Parser) Resolve(fileContent []byte, filename string) (*[]byte, error) { } // Parse parses json file and returns it as a Document -func (p *Parser) Parse(_ string, fileContent []byte) ([]model.Document, error) { +func (p *Parser) Parse(_ string, fileContent []byte) ([]model.Document, []int, error) { r := model.Document{} err := json.Unmarshal(fileContent, &r) if err != nil { r := []model.Document{} err = json.Unmarshal(fileContent, &r) - return r, err + return r, []int{}, err } jLine := initializeJSONLine(fileContent) @@ -34,12 +34,12 @@ func (p *Parser) Parse(_ string, fileContent []byte) ([]model.Document, error) { kicsPlan, err := parseTFPlan(kicsJSON) if err != nil { // JSON is not a tf plan - return []model.Document{kicsJSON}, nil + return []model.Document{kicsJSON}, []int{}, nil } p.shouldIdent = true - return []model.Document{kicsPlan}, nil + return []model.Document{kicsPlan}, []int{}, nil } // SupportedExtensions returns extensions supported by this parser, which is json extension diff --git a/pkg/parser/json/parser_test.go b/pkg/parser/json/parser_test.go index ece59d616ae..193d8433c89 100644 --- a/pkg/parser/json/parser_test.go +++ b/pkg/parser/json/parser_test.go @@ -37,7 +37,7 @@ func TestParser_SupportedTypes(t *testing.T) { func TestParser_Parse(t *testing.T) { p := &Parser{} - doc, err := p.Parse("test.json", []byte(have)) + doc, _, err := p.Parse("test.json", []byte(have)) require.NoError(t, err) require.Len(t, doc, 1) require.Contains(t, doc[0], "martin") diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index 4c139c6d421..348a950f645 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -14,7 +14,7 @@ type kindParser interface { GetCommentToken() string SupportedExtensions() []string SupportedTypes() []string - Parse(filePath string, fileContent []byte) ([]model.Document, error) + Parse(filePath string, fileContent []byte) ([]model.Document, []int, error) Resolve(fileContent []byte, filename string) (*[]byte, error) StringifyContent(content []byte) (string, error) } @@ -71,9 +71,10 @@ type Parser struct { // ParsedDocument is a struct containing data retrieved from parsing type ParsedDocument struct { - Docs []model.Document - Kind model.FileKind - Content string + Docs []model.Document + Kind model.FileKind + Content string + IgnoreLines []int } // CommentsCommands gets commands on comments in the file beginning, before the code starts @@ -115,7 +116,7 @@ func (c *Parser) Parse(filePath string, fileContent []byte) (ParsedDocument, err if err != nil { return ParsedDocument{}, err } - obj, err := c.parsers.Parse(filePath, *resolved) + obj, igLines, err := c.parsers.Parse(filePath, *resolved) if err != nil { return ParsedDocument{}, err } @@ -127,15 +128,17 @@ func (c *Parser) Parse(filePath string, fileContent []byte) (ParsedDocument, err } return ParsedDocument{ - Docs: obj, - Kind: c.parsers.GetKind(), - Content: cont, + Docs: obj, + Kind: c.parsers.GetKind(), + Content: cont, + IgnoreLines: igLines, }, nil } return ParsedDocument{ - Docs: nil, - Kind: "break", - Content: "", + Docs: nil, + Kind: "break", + Content: "", + IgnoreLines: []int{}, }, ErrNotSupportedFile } diff --git a/pkg/parser/terraform/comment/comment.go b/pkg/parser/terraform/comment/comment.go new file mode 100644 index 00000000000..473855a42ef --- /dev/null +++ b/pkg/parser/terraform/comment/comment.go @@ -0,0 +1,192 @@ +package comment + +import ( + "strings" + + "github.com/Checkmarx/kics/pkg/model" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" +) + +// Comment is a comment token +type Comment hclsyntax.Token + +// position returns the position of the comment +func (c *Comment) position() hcl.Pos { + return hcl.Pos{Line: c.Range.End.Line + 1, Column: c.Range.End.Column, Byte: c.Range.End.Byte} +} + +// value returns the value of a comment +func (c *Comment) value() (value model.CommentCommand) { + comment := strings.ToLower(string(c.Bytes)) + // check if we are working with kics command + if model.KICSCommentRgxp.MatchString(comment) { + comment = model.KICSCommentRgxp.ReplaceAllString(comment, "") + commands := strings.Split(strings.Trim(comment, "\n"), " ") + value = processCommands(commands) + return + } + + return model.CommentCommand(comment) +} + +// Ignore is a map of commands to ignore +type Ignore map[model.CommentCommand][]hcl.Pos + +// Build builds the Ignore map +func (i *Ignore) Build(ignoreLine, ignoreBlock, ignoreComment []hcl.Pos) { + ignoreStruct := map[model.CommentCommand][]hcl.Pos{ + model.IgnoreLine: ignoreLine, + model.IgnoreBlock: ignoreBlock, + model.IgnoreComment: ignoreComment, + } + + *i = ignoreStruct +} + +// /////////////////////////// +// LINES TO IGNORE // +// /////////////////////////// + +// GetIgnoreLines returns the lines to ignore from a comment +func GetIgnoreLines(ignore Ignore, body *hclsyntax.Body) (lines []int) { + lines = make([]int, 0) + for _, position := range ignore[model.IgnoreBlock] { + lines = append(lines, checkBlock(body, position)...) + } + lines = append(lines, getLinesFromPos(ignore[model.IgnoreLine])...) + lines = append(lines, getLinesFromPos(ignore[model.IgnoreComment])...) + return +} + +// getLinesFromPos will return a list of lines from a list of positions +func getLinesFromPos(positions []hcl.Pos) (lines []int) { + lines = make([]int, 0) + for _, position := range positions { + lines = append(lines, position.Line) + } + return +} + +// checkBlock checks if the position is inside a block and returns the lines to ignore +func checkBlock(body *hclsyntax.Body, position hcl.Pos) (lines []int) { + lines = make([]int, 0) + blocks := body.BlocksAtPos(position) + + for _, block := range blocks { + lines = append(lines, getLinesFromBlock(block, position)...) + } + return +} + +// getLinesFromBlock returns the lines to ignore from a block +func getLinesFromBlock(block *hcl.Block, position hcl.Pos) (lines []int) { + lines = make([]int, 0) + if checkBlockRange(block, position) { + rangeBlock := block.Body.(*hclsyntax.Body).Range() + lines = append(lines, lineRange(rangeBlock.Start.Line, rangeBlock.End.Line)...) + } else { + // check in attributes + attribute := block.Body.(*hclsyntax.Body).AttributeAtPos(position) + lines = append(lines, getLinesFromAttr(attribute)...) + } + return +} + +// getLinesFromAttr returns the lines to ignore from an attribute +func getLinesFromAttr(atr *hcl.Attribute) (lines []int) { + lines = make([]int, 0) + if atr == nil { + return + } + + lines = append(lines, lineRange(atr.Range.Start.Line, atr.Range.End.Line)...) + return +} + +// checkBlockRange checks if the position is inside a block +func checkBlockRange(block *hcl.Block, position hcl.Pos) bool { + return block.TypeRange.End == position +} + +// lineRange returns a list of lines from a range +func lineRange(start, end int) (lines []int) { + lines = make([]int, end-start+1) + for i := range lines { + lines[i] = start + i + } + return +} + +// /////////////////////////// +// COMMENT PARSER // +// /////////////////////////// + +// ParseComments parses the comments and returns the kics commands +func ParseComments(src []byte, filename string) (Ignore, error) { + comments, diags := hclsyntax.LexConfig(src, filename, hcl.Pos{Line: 0, Column: 0}) + if diags != nil && diags.HasErrors() { + return Ignore{}, diags.Errs()[0] + } + + ig := processTokens(comments) + + return ig, nil +} + +// processTokens goes over the tokens and returns the kics commands +func processTokens(tokens hclsyntax.Tokens) (ig Ignore) { + ignoreLines := make([]hcl.Pos, 0) + ignoreBlocks := make([]hcl.Pos, 0) + ignoreComments := make([]hcl.Pos, 0) + for i := range tokens { + // token is not a comment + if tokens[i].Type != hclsyntax.TokenComment || i+1 > len(tokens) { + continue + } + ignoreLines, ignoreBlocks, ignoreComments = processComment((*Comment)(&tokens[i]), + (*Comment)(&tokens[i+1]), ignoreLines, ignoreBlocks, ignoreComments) + } + ig = make(map[model.CommentCommand][]hcl.Pos) + ig.Build(ignoreLines, ignoreBlocks, ignoreComments) + return ig +} + +// processComment analyzes the comment to determine which type of kics command the comment is +func processComment(comment *Comment, tokenToIgnore *Comment, + ignoreLine, ignoreBlock, ignoreComments []hcl.Pos) (ignoreLineR, ignoreBlockR, ignoreCommentsR []hcl.Pos) { + ignoreLineR = ignoreLine + ignoreBlockR = ignoreBlock + ignoreCommentsR = ignoreComments + + switch comment.value() { + case model.IgnoreLine: + // comment is of type kics ignore-line + ignoreLineR = append(ignoreLineR, tokenToIgnore.position()) + case model.IgnoreBlock: + // comment is of type kics ignore-block + ignoreBlockR = append(ignoreBlockR, tokenToIgnore.position()) + default: + // comment is not of type kics ignore + ignoreCommentsR = append(ignoreCommentsR, comment.position()) + return + } + + return +} + +// processCommands goes over kics commands in a line and returns the type of command given +func processCommands(commands []string) model.CommentCommand { + for _, command := range commands { + switch com := model.CommentCommand(command); com { + case model.IgnoreLine: + return model.IgnoreLine + case model.IgnoreBlock: + return model.IgnoreBlock + default: + continue + } + } + + return model.CommentCommand(commands[0]) +} diff --git a/pkg/parser/terraform/comment/comment_test.go b/pkg/parser/terraform/comment/comment_test.go new file mode 100644 index 00000000000..879f629a6ec --- /dev/null +++ b/pkg/parser/terraform/comment/comment_test.go @@ -0,0 +1,173 @@ +package comment + +import ( + "reflect" + "testing" + + "github.com/Checkmarx/kics/pkg/model" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/stretchr/testify/require" +) + +var ( + samples = map[string][]byte{ + "ignore-block": []byte(` + // kics ignore-block + resource "aws_api_gateway_stage" "positive2" { + deployment_id = "some deployment id" + rest_api_id = "some rest api id" + stage_name = "development" + }`), + "ignore-line": []byte(` + resource "aws_api_gateway_stage" "positive2" { + deployment_id = "some deployment id" + // kics ignore-line + rest_api_id = "some rest api id" + stage_name = "development" + }`), + "regular-comment": []byte(` + resource "aws_api_gateway_stage" "positive2" { + deployment_id = "some deployment id" + // kics do-not-ignore + rest_api_id = "some rest api id" + // regular comment + stage_name = "development" + }`), + "ignore-inner-block": []byte(` + resource "aws_api_gateway_stage" "positive2" { + deployment_id = "some deployment id" + // kics ignore-block + tags = { + Terraform = "true" + Environment = "dev" + } + stage_name = "development" + }`), + } +) + +// TestComment_ParseComments tests the parsing of comments +func TestComment_ParseComments(t *testing.T) { + tests := []struct { + name string + content []byte + filename string + want Ignore + wantErr bool + }{ + { + name: "TestComment_ParseComments: ignore-block", + content: samples["ignore-block"], + filename: "", + want: Ignore{ + model.IgnoreBlock: []hcl.Pos{ + {Line: 3, Column: 11, Byte: 34}, + }, + model.IgnoreLine: []hcl.Pos{}, + model.IgnoreComment: []hcl.Pos{}, + }, + wantErr: false, + }, + { + name: "TestComment_ParseComments: ignore-line", + content: samples["ignore-line"], + filename: "", + want: Ignore{ + model.IgnoreBlock: []hcl.Pos{}, + model.IgnoreLine: []hcl.Pos{ + {Line: 5, Column: 16, Byte: 130}, + }, + model.IgnoreComment: []hcl.Pos{}, + }, + wantErr: false, + }, + { + name: "TestComment_ParseComments: regular-comment", + content: samples["regular-comment"], + filename: "", + want: Ignore{ + model.IgnoreBlock: []hcl.Pos{}, + model.IgnoreLine: []hcl.Pos{}, + model.IgnoreComment: []hcl.Pos{ + {Line: 5, Column: 1, Byte: 117}, + {Line: 7, Column: 1, Byte: 179}, + }, + }, + wantErr: false, + }, + { + name: "TestComment_ParseComments: ignore inner-block", + content: samples["ignore-inner-block"], + filename: "", + want: Ignore{ + model.IgnoreBlock: []hcl.Pos{ + {Line: 5, Column: 9, Byte: 124}, + }, + model.IgnoreLine: []hcl.Pos{}, + model.IgnoreComment: []hcl.Pos{}, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseComments(tt.content, tt.filename) + if (err != nil) != tt.wantErr { + t.Errorf("ParseComments() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseComments() = %v, want %v", got, tt.want) + } + }) + } +} + +// TestComment_GetIgnoreLines tests the ignore lines from retrieved comments +func TestComment_GetIgnoreLines(t *testing.T) { + tests := []struct { + name string + content []byte + filename string + want []int + }{ + { + name: "TestComment_GetIgnoreLines: ignore-block", + content: samples["ignore-block"], + filename: "", + want: []int{3, 4, 5, 6, 7}, + }, + { + name: "TestComment_GetIgnoreLines: ignore-line", + content: samples["ignore-line"], + filename: "", + want: []int{5}, + }, + { + name: "TestComment_GetIgnoreLines: regular-comment", + content: samples["regular-comment"], + filename: "", + want: []int{5, 7}, + }, + { + name: "TestComment_GetIgnoreLines: ignore inner-block", + content: samples["ignore-inner-block"], + filename: "", + want: []int{5, 6, 7, 8}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ignore, err := ParseComments(tt.content, tt.filename) + require.NoError(t, err) + file, diagnostics := hclsyntax.ParseConfig(tt.content, tt.filename, hcl.Pos{Byte: 0, Line: 1, Column: 1}) + require.False(t, diagnostics.HasErrors()) + if got := GetIgnoreLines(ignore, file.Body.(*hclsyntax.Body)); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetIgnoreLines() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/parser/terraform/terraform.go b/pkg/parser/terraform/terraform.go index 45fcf1b7759..8d7fcf46bc2 100644 --- a/pkg/parser/terraform/terraform.go +++ b/pkg/parser/terraform/terraform.go @@ -4,11 +4,13 @@ import ( "path/filepath" "github.com/Checkmarx/kics/pkg/model" + "github.com/Checkmarx/kics/pkg/parser/terraform/comment" "github.com/Checkmarx/kics/pkg/parser/terraform/converter" "github.com/Checkmarx/kics/pkg/parser/utils" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/pkg/errors" + "github.com/rs/zerolog/log" ) // RetriesDefaultValue is default number of times a parser will retry to execute @@ -89,21 +91,27 @@ func addExtraInfo(json []model.Document, path string) ([]model.Document, error) } // Parse execute parser for the content in a file -func (p *Parser) Parse(path string, content []byte) ([]model.Document, error) { +func (p *Parser) Parse(path string, content []byte) ([]model.Document, []int, error) { file, diagnostics := hclsyntax.ParseConfig(content, filepath.Base(path), hcl.Pos{Byte: 0, Line: 1, Column: 1}) + ignore, err := comment.ParseComments(content, path) + if err != nil { + log.Err(err).Msg("failed to get comments to ignore") + } + + linesToIgnore := comment.GetIgnoreLines(ignore, file.Body.(*hclsyntax.Body)) if diagnostics != nil && diagnostics.HasErrors() && len(diagnostics.Errs()) > 0 { - err := diagnostics.Errs()[0] - return nil, err + err = diagnostics.Errs()[0] + return nil, linesToIgnore, err } fc, parseErr := p.convertFunc(file, inputVariableMap) json, err := addExtraInfo([]model.Document{fc}, path) if err != nil { - return json, errors.Wrap(err, "failed terraform parse") + return json, linesToIgnore, errors.Wrap(err, "failed terraform parse") } - return json, errors.Wrap(parseErr, "failed terraform parse") + return json, linesToIgnore, errors.Wrap(parseErr, "failed terraform parse") } // SupportedExtensions returns Terraform extensions diff --git a/pkg/parser/terraform/terraform_test.go b/pkg/parser/terraform/terraform_test.go index 791af2bff79..6d2928672cd 100644 --- a/pkg/parser/terraform/terraform_test.go +++ b/pkg/parser/terraform/terraform_test.go @@ -23,21 +23,21 @@ resource "aws_s3_bucket" "b" { count = ` resource "aws_instance" "server" { count = true == true ? 0 : 1 - + subnet_id = var.subnet_ids[count.index] - + ami = "ami-a1b2c3d4" instance_type = "t2.micro" - + } - + resource "aws_instance" "server1" { count = length(var.subnet_ids) - + ami = "ami-a1b2c3d4" instance_type = "t2.micro" subnet_id = var.subnet_ids[count.index] - + }` ) @@ -62,7 +62,7 @@ func TestParser_SupportedExtensions(t *testing.T) { // Test_Parser tests the functions [Parser()] and all the methods called by them func Test_Parser(t *testing.T) { parser := NewDefault() - document, err := parser.Parse("test.tf", []byte(have)) + document, _, err := parser.Parse("test.tf", []byte(have)) require.NoError(t, err) require.Len(t, document, 1) @@ -73,7 +73,7 @@ func Test_Parser(t *testing.T) { // Test_Count tests resources with count set to 0 func Test_Count(t *testing.T) { parser := NewDefault() - document, err := parser.Parse("count.tf", []byte(count)) + document, _, err := parser.Parse("count.tf", []byte(count)) require.NoError(t, err) require.Len(t, document, 1) require.Contains(t, document[0], "resource") diff --git a/pkg/parser/yaml/parser.go b/pkg/parser/yaml/parser.go index 0eef04f00dd..5d72881af0c 100644 --- a/pkg/parser/yaml/parser.go +++ b/pkg/parser/yaml/parser.go @@ -19,7 +19,7 @@ func (p *Parser) Resolve(fileContent []byte, filename string) (*[]byte, error) { } // Parse parses yaml/yml file and returns it as a Document -func (p *Parser) Parse(filePath string, fileContent []byte) ([]model.Document, error) { +func (p *Parser) Parse(filePath string, fileContent []byte) ([]model.Document, []int, error) { var documents []model.Document dec := yaml.NewDecoder(bytes.NewReader(fileContent)) @@ -32,10 +32,10 @@ func (p *Parser) Parse(filePath string, fileContent []byte) ([]model.Document, e } if len(documents) == 0 { - return nil, errors.Wrap(errors.New("invalid yaml"), "failed to parse yaml") + return nil, []int{}, errors.Wrap(errors.New("invalid yaml"), "failed to parse yaml") } - return convertKeysToString(addExtraInfo(documents, filePath)), nil + return convertKeysToString(addExtraInfo(documents, filePath)), []int{}, nil } // convertKeysToString goes through every document to convert map[interface{}]interface{} diff --git a/pkg/parser/yaml/parser_test.go b/pkg/parser/yaml/parser_test.go index 4b95916e85d..9c9f13f1934 100644 --- a/pkg/parser/yaml/parser_test.go +++ b/pkg/parser/yaml/parser_test.go @@ -250,7 +250,7 @@ downscaler_enabled: "false" for idx, tt := range have { t.Run(fmt.Sprintf("test_parse_case_%d", idx), func(t *testing.T) { - doc, err := p.Parse("test.yaml", []byte(tt)) + doc, _, err := p.Parse("test.yaml", []byte(tt)) if want[idx].wantErr { require.Error(t, err) } else { @@ -349,7 +349,7 @@ func TestModel_TestYamlParser(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { parser := Parser{} - got, err := parser.Parse("", []byte(tt.sample)) + got, _, err := parser.Parse("", []byte(tt.sample)) if tt.wantErr { require.Error(t, err) } else { diff --git a/test/fixtures/all_auth_users_get_read_access/metadata.json b/test/fixtures/all_auth_users_get_read_access/metadata.json index 4cf55269363..f202129a65a 100644 --- a/test/fixtures/all_auth_users_get_read_access/metadata.json +++ b/test/fixtures/all_auth_users_get_read_access/metadata.json @@ -5,5 +5,5 @@ "category": "Access Control", "descriptionText": "Misconfigured S3 buckets can leak private information to the entire internet or allow unauthorized data tampering / deletion", "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket#acl", - "platform": "CloudFormation" + "platform": "Terraform" } diff --git a/test/fixtures/type-test01/template01/metadata.json b/test/fixtures/type-test01/template01/metadata.json index 76352a1c666..4845a2f3c78 100644 --- a/test/fixtures/type-test01/template01/metadata.json +++ b/test/fixtures/type-test01/template01/metadata.json @@ -5,6 +5,6 @@ "category": null, "descriptionText": "", "descriptionUrl": "#", - "platform": "", + "platform": "Dockerfile", "aggregation": 1 }