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

tfsec:chore - improve tests and code cleaning #901

Merged
merged 1 commit into from
Dec 27, 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
40 changes: 17 additions & 23 deletions internal/services/formatters/hcl/tfsec/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,18 @@ package tfsec

import (
"encoding/json"
"fmt"
"errors"
"strings"

"github.com/ZupIT/horusec-devkit/pkg/entities/vulnerability"
"github.com/ZupIT/horusec-devkit/pkg/enums/languages"
"github.com/ZupIT/horusec-devkit/pkg/enums/tools"
"github.com/ZupIT/horusec-devkit/pkg/utils/logger"

dockerEntities "github.com/ZupIT/horusec/internal/entities/docker"
"github.com/ZupIT/horusec/internal/entities/docker"
"github.com/ZupIT/horusec/internal/enums/images"
"github.com/ZupIT/horusec/internal/helpers/messages"
"github.com/ZupIT/horusec/internal/services/formatters"
"github.com/ZupIT/horusec/internal/services/formatters/hcl/tfsec/entities"
vulnhash "github.com/ZupIT/horusec/internal/utils/vuln_hash"
)

Expand Down Expand Up @@ -64,8 +63,8 @@ func (f *Formatter) startTfSec(projectSubPath string) (string, error) {
return output, f.parseOutput(output)
}

func (f *Formatter) getDockerConfig(projectSubPath string) *dockerEntities.AnalysisData {
analysisData := &dockerEntities.AnalysisData{
func (f *Formatter) getDockerConfig(projectSubPath string) *docker.AnalysisData {
analysisData := &docker.AnalysisData{
CMD: f.AddWorkDirInCmd(CMD, projectSubPath, tools.TfSec),
Language: languages.HCL,
}
Expand All @@ -74,37 +73,32 @@ func (f *Formatter) getDockerConfig(projectSubPath string) *dockerEntities.Analy
}

func (f *Formatter) parseOutput(output string) error {
var vulnerabilities *entities.Vulnerabilities
var vulnerabilities *tfsecVulnerabilities

if err := json.Unmarshal([]byte(output), &vulnerabilities); err != nil {
if !strings.Contains(output, "panic") {
return fmt.Errorf("{HORUSEC_CLI} Error %s", output)
return errors.New(output)
}

return err
}

for index := range vulnerabilities.Results {
f.AddNewVulnerabilityIntoAnalysis(f.setVulnerabilityData(index, vulnerabilities.Results))
f.AddNewVulnerabilityIntoAnalysis(f.newVulnerability(&vulnerabilities.Results[index]))
}

return nil
}

func (f *Formatter) setVulnerabilityData(index int, results []entities.Result) *vulnerability.Vulnerability {
vuln := f.getDefaultVulnerabilityData()
vuln.Severity = results[index].GetSeverity()
vuln.Details = results[index].GetDetails()
vuln.Line = results[index].GetStartLine()
vuln.Code = f.GetCodeWithMaxCharacters(results[index].GetCode(), 0)
vuln.File = f.RemoveSrcFolderFromPath(results[index].GetFilename())
func (f *Formatter) newVulnerability(result *tfsecResult) *vulnerability.Vulnerability {
vuln := &vulnerability.Vulnerability{
SecurityTool: tools.TfSec,
Language: languages.HCL,
Severity: result.getSeverity(),
Details: result.getDetails(),
Line: result.getStartLine(),
Code: f.GetCodeWithMaxCharacters(result.getCode(), 0),
File: f.RemoveSrcFolderFromPath(result.getFilename()),
}
vuln = vulnhash.Bind(vuln)
return f.SetCommitAuthor(vuln)
}

func (f *Formatter) getDefaultVulnerabilityData() *vulnerability.Vulnerability {
vulnerabilitySeverity := &vulnerability.Vulnerability{}
vulnerabilitySeverity.SecurityTool = tools.TfSec
vulnerabilitySeverity.Language = languages.HCL
return vulnerabilitySeverity
}
133 changes: 99 additions & 34 deletions internal/services/formatters/hcl/tfsec/formatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,83 +18,148 @@ import (
"errors"
"testing"

entitiesAnalysis "github.com/ZupIT/horusec-devkit/pkg/entities/analysis"
"github.com/ZupIT/horusec-devkit/pkg/entities/analysis"
"github.com/ZupIT/horusec-devkit/pkg/enums/languages"
"github.com/ZupIT/horusec-devkit/pkg/enums/tools"
"github.com/stretchr/testify/assert"

cliConfig "github.com/ZupIT/horusec/config"
"github.com/ZupIT/horusec/config"
"github.com/ZupIT/horusec/internal/entities/toolsconfig"
"github.com/ZupIT/horusec/internal/entities/workdir"
"github.com/ZupIT/horusec/internal/services/formatters"
"github.com/ZupIT/horusec/internal/utils/testutil"
)

func TestStartHCLTfSec(t *testing.T) {
t.Run("should success execute container and process output", func(t *testing.T) {
func TestTFSecParseOutput(t *testing.T) {
t.Run("should add 5 vulnerabilities on analysis with no errors", func(t *testing.T) {
dockerAPIControllerMock := testutil.NewDockerMock()
analysis := &entitiesAnalysis.Analysis{}
config := &cliConfig.Config{}
config.WorkDir = &workdir.WorkDir{}

output := `{"results":[{"rule_id":"AWS018","link":"https://github.com/liamg/tfsec/wiki/AWS018","location":{"filename":"/src/terraform/main.tf","start_line":2,"end_line":5},"description":"Resource 'aws_security_group_rule.my-rule' should include a description for auditing purposes.","severity":"ERROR"},{"rule_id":"AWS006","link":"https://github.com/liamg/tfsec/wiki/AWS006","location":{"filename":"/src/terraform/main.tf","start_line":4,"end_line":4},"description":"Resource 'aws_security_group_rule.my-rule' defines a fully open ingress security group rule.","severity":"WARNING"},{"rule_id":"AWS004","link":"https://github.com/liamg/tfsec/wiki/AWS004","location":{"filename":"/src/terraform/main.tf","start_line":9,"end_line":9},"description":"Resource 'aws_alb_listener.my-alb-listener' uses plain HTTP instead of HTTPS.","severity":"ERROR"},{"rule_id":"AWS003","link":"https://github.com/liamg/tfsec/wiki/AWS003","location":{"filename":"/src/terraform/main.tf","start_line":12,"end_line":14},"description":"Resource 'aws_db_security_group.my-group' uses EC2 Classic. Use a VPC instead.","severity":"ERROR"},{"rule_id":"AZU003","link":"https://github.com/liamg/tfsec/wiki/AZU003","location":{"filename":"/src/terraform/main.tf","start_line":18,"end_line":18},"description":"Resource 'azurerm_managed_disk.source' defines an unencrypted managed disk.","severity":"ERROR"}]}`

dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil)

analysis := new(analysis.Analysis)
config := config.New()

service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config)
formatter := NewFormatter(service)

formatter.StartAnalysis("")

assert.NotEmpty(t, analysis)
assert.Len(t, analysis.AnalysisVulnerabilities, 5)
for _, v := range analysis.AnalysisVulnerabilities {
vuln := v.Vulnerability

assert.Equal(t, tools.TfSec, vuln.SecurityTool)
assert.Equal(t, languages.HCL, vuln.Language)
assert.NotEmpty(t, vuln.Details, "Expected not empty details")
assert.NotEmpty(t, vuln.Code, "Expected not empty code")
assert.NotEmpty(t, vuln.File, "Expected not emppty file name")
assert.NotEmpty(t, vuln.Line, "Expected not empty line")
assert.NotEmpty(t, vuln.Severity, "Expected not empty severity")

}
})

t.Run("should return error when invalid output", func(t *testing.T) {
t.Run("should add error on analysis when invalid output", func(t *testing.T) {
dockerAPIControllerMock := testutil.NewDockerMock()
analysis := &entitiesAnalysis.Analysis{}
config := &cliConfig.Config{}
config.WorkDir = &workdir.WorkDir{}
dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return("invalid", nil)

output := "!@#!@#"
analysis := new(analysis.Analysis)

dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil)
config := config.New()

service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config)
formatter := NewFormatter(service)
formatter.StartAnalysis("")

assert.NotPanics(t, func() {
formatter.StartAnalysis("")
})
assert.True(t, analysis.HasErrors(), "Expected errors on analysis")
})

t.Run("should return error when executing container", func(t *testing.T) {
t.Run("should add error of executing container on analysis", func(t *testing.T) {
dockerAPIControllerMock := testutil.NewDockerMock()
analysis := &entitiesAnalysis.Analysis{}
config := &cliConfig.Config{}
config.WorkDir = &workdir.WorkDir{}

dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return("", errors.New("test"))

analysis := new(analysis.Analysis)

config := config.New()
service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config)
formatter := NewFormatter(service)
formatter.StartAnalysis("")

assert.NotPanics(t, func() {
formatter.StartAnalysis("")
})
assert.True(t, analysis.HasErrors(), "Expected errors on analysis")
})
t.Run("Should not execute tool because it's ignored", func(t *testing.T) {
analysis := &entitiesAnalysis.Analysis{}
analysis := new(analysis.Analysis)

dockerAPIControllerMock := testutil.NewDockerMock()
config := &cliConfig.Config{}
config.WorkDir = &workdir.WorkDir{}

config := config.New()
config.ToolsConfig = toolsconfig.ToolsConfig{
tools.TfSec: toolsconfig.Config{
IsToIgnore: true,
},
}

service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config)
formatter := NewFormatter(service)

formatter.StartAnalysis("")
})
}

const output = `
{
"results": [
{
"rule_id": "AWS018",
"link": "https://github.com/liamg/tfsec/wiki/AWS018",
"location": {
"filename": "/src/terraform/main.tf",
"start_line": 2,
"end_line": 5
},
"description": "Resource 'aws_security_group_rule.my-rule' should include a description for auditing purposes.",
"severity": "ERROR"
},
{
"rule_id": "AWS006",
"link": "https://github.com/liamg/tfsec/wiki/AWS006",
"location": {
"filename": "/src/terraform/main.tf",
"start_line": 4,
"end_line": 4
},
"description": "Resource 'aws_security_group_rule.my-rule' defines a fully open ingress security group rule.",
"severity": "WARNING"
},
{
"rule_id": "AWS004",
"link": "https://github.com/liamg/tfsec/wiki/AWS004",
"location": {
"filename": "/src/terraform/main.tf",
"start_line": 9,
"end_line": 9
},
"description": "Resource 'aws_alb_listener.my-alb-listener' uses plain HTTP instead of HTTPS.",
"severity": "ERROR"
},
{
"rule_id": "AWS003",
"link": "https://github.com/liamg/tfsec/wiki/AWS003",
"location": {
"filename": "/src/terraform/main.tf",
"start_line": 12,
"end_line": 14
},
"description": "Resource 'aws_db_security_group.my-group' uses EC2 Classic. Use a VPC instead.",
"severity": "ERROR"
},
{
"rule_id": "AZU003",
"link": "https://github.com/liamg/tfsec/wiki/AZU003",
"location": {
"filename": "/src/terraform/main.tf",
"start_line": 18,
"end_line": 18
},
"description": "Resource 'azurerm_managed_disk.source' defines an unencrypted managed disk.",
"severity": "ERROR"
}
]
}
`
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package entities
package tfsec

type Location struct {
Filename string `json:"filename"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package entities
package tfsec

import (
"fmt"
Expand All @@ -21,35 +21,35 @@ import (
"github.com/ZupIT/horusec-devkit/pkg/enums/severities"
)

type Result struct {
type tfsecResult struct {
RuleID string `json:"rule_id"`
Link string `json:"link"`
Location Location `json:"location"`
Description string `json:"description"`
Severity string `json:"severity"`
}

func (r *Result) GetDetails() string {
func (r *tfsecResult) getDetails() string {
return r.RuleID + " -> [" + r.Description + "]"
}

func (r *Result) GetStartLine() string {
func (r *tfsecResult) getStartLine() string {
return strconv.Itoa(r.Location.StartLine)
}

func (r *Result) GetCode() string {
func (r *tfsecResult) getCode() string {
return fmt.Sprintf("code beetween line %d and %d.", r.Location.StartLine, r.Location.EndLine)
}

func (r *Result) GetFilename() string {
func (r *tfsecResult) getFilename() string {
return r.Location.Filename
}

func (r *Result) GetSeverity() severities.Severity {
func (r *tfsecResult) getSeverity() severities.Severity {
return r.mapSeverityValues()[r.Severity]
}

func (r *Result) mapSeverityValues() map[string]severities.Severity {
func (r *tfsecResult) mapSeverityValues() map[string]severities.Severity {
return map[string]severities.Severity{
"ERROR": severities.High,
"WARNING": severities.Medium,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package entities
package tfsec

import (
"testing"

"github.com/stretchr/testify/assert"
)

func resultMock() *Result {
return &Result{
func resultMock() *tfsecResult {
return &tfsecResult{
RuleID: "AWS123",
Link: "test",
Location: Location{
Expand All @@ -36,24 +36,24 @@ func resultMock() *Result {

func TestGetDetails(t *testing.T) {
t.Run("should success get result details", func(t *testing.T) {
assert.NotEmpty(t, resultMock().GetDetails())
assert.NotEmpty(t, resultMock().getDetails())
})
}

func TestGetStartLine(t *testing.T) {
t.Run("should success get start line", func(t *testing.T) {
assert.NotEmpty(t, resultMock().GetStartLine())
assert.NotEmpty(t, resultMock().getStartLine())
})
}

func TestGetCode(t *testing.T) {
t.Run("should success get code", func(t *testing.T) {
assert.NotEmpty(t, resultMock().GetCode())
assert.NotEmpty(t, resultMock().getCode())
})
}

func TestGetFilename(t *testing.T) {
t.Run("should success get filename", func(t *testing.T) {
assert.NotEmpty(t, resultMock().GetFilename())
assert.NotEmpty(t, resultMock().getFilename())
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package entities
package tfsec

type Vulnerabilities struct {
Results []Result `json:"results"`
type tfsecVulnerabilities struct {
Results []tfsecResult `json:"results"`
}