Repository: globocom/huskyCI (go)

Issue:
Add capabilities to scan terraform scripts
Hey team,

I was thinking would be good to add TFSec as a vendor in HuskyCI. So we can look for potential vulnerabilities in TF files.

Project:
https://github.com/liamg/tfsec

Here the example:
![image](https://github.com/liamg/tfsec/raw/master/screenshot.png)

Tfsec provides an output in JSON
```
{
    "rule_id": "AWS007",
    "link": "https://github.com/liamg/tfsec/wiki/AWS007",
    "location": {
        "filename": "cluster.tf",
        "start_line": 53,
        "end_line": 53
    },
    "description": "Resource 'aws_security_group_rule.cluster_egress_internet' defines a fully open egress security group rule.",
    "severity": "WARNING"
}
```


Pull request:
Add securityTest to scan Terraform templates: tfsec
### Description

This PR will add a new security test to scan Terraform templates using [tfsec](https://github.com/liamg/tfsec). It is a static analysis tool to spot potential security issues in this kind of templates.

Closes #477.

### Proposed Changes: 

* Add tfsec as a new securitytest.
* Add tfsec in huskyci in API and Client
* Create dockerfile for tfsec
* Update config.yaml

### How to test:

- Install all dependencies:

```shell 
make install 
```

- Replace HUSKYCI_CLIENT_REPO_URL and HUSKYCI_CLIENT_REPO_BRANCH in .env:

```shell
export HUSKYCI_CLIENT_REPO_URL=https://github.com/globocom/huskyCI.git
export HUSKYCI_CLIENT_REPO_BRANCH=poc-iac-tfsec
```
- Export env variables:

```shell
 source .env 
```

- Run scan:

```shell 
make run-client
```
### Proof:

#### Result from API

Repo analysed: https://github.com/William-Hill-Online/huskyCI/tree/poc-hcl-tfsec
```json
{
 ...
   "hclresults":{
      "tfsecoutput":{
         "mediumvulns":[
            {
               "language":"HCL",
               "securitytool":"TFSec",
               "severity":"WARNING",
               "file":"/go/code/terraform/main.tf",
               "line":"4",
               "code":"Code beetween Line 4 and Line 4.",
               "details":"AWS006 @ [Resource 'aws_security_group_rule.my-rule' defines a fully open ingress security group rule.]"
            }
         ],
         "highvulns":[
            {
               "language":"HCL",
               "securitytool":"TFSec",
               "severity":"ERROR",
               "file":"/go/code/terraform/main.tf",
               "line":"2",
               "code":"Code beetween Line 2 and Line 5.",
               "details":"AWS018 @ [Resource 'aws_security_group_rule.my-rule' should include a description for auditing purposes.]"
            },
            {
               "language":"HCL",
               "securitytool":"TFSec",
               "severity":"ERROR",
               "file":"/go/code/terraform/main.tf",
               "line":"9",
               "code":"Code beetween Line 9 and Line 9.",
               "details":"AWS004 @ [Resource 'aws_alb_listener.my-alb-listener' uses plain HTTP instead of HTTPS.]"
            },
            {
               "language":"HCL",
               "securitytool":"TFSec",
               "severity":"ERROR",
               "file":"/go/code/terraform/main.tf",
               "line":"12",
               "code":"Code beetween Line 12 and Line 14.",
               "details":"AWS003 @ [Resource 'aws_db_security_group.my-group' uses EC2 Classic. Use a VPC instead.]"
            },
            {
               "language":"HCL",
               "securitytool":"TFSec",
               "severity":"ERROR",
               "file":"/go/code/terraform/main.tf",
               "line":"18",
               "code":"Code beetween Line 18 and Line 18.",
               "details":"AZU003 @ [Resource 'azurerm_managed_disk.source' defines an unencrypted managed disk.]"
            }
         ]
      }
   }
}
```

#### Results in client:
```shell
[HUSKYCI][*] poc-hcl-tfsec -> https://github.com/william-Hill-Online/huskyCI.git
[HUSKYCI][*] huskyCI analysis started! bFxbHMRNoR1eWTwvrFpg4eqxC5ARGLwM

[HUSKYCI][!] Language: HCL
[HUSKYCI][!] Tool: TFSec
[HUSKYCI][!] Severity: WARNING
[HUSKYCI][!] Details: AWS006 @ [Resource 'aws_security_group_rule.my-rule' defines a fully open ingress security group rule.]
[HUSKYCI][!] File: /go/code/terraform/main.tf
[HUSKYCI][!] Line: 4
[HUSKYCI][!] Code: Code beetween Line 4 and Line 4.

[HUSKYCI][!] Language: HCL
[HUSKYCI][!] Tool: TFSec
[HUSKYCI][!] Severity: ERROR
[HUSKYCI][!] Details: AWS018 @ [Resource 'aws_security_group_rule.my-rule' should include a description for auditing purposes.]
[HUSKYCI][!] File: /go/code/terraform/main.tf
[HUSKYCI][!] Line: 2
[HUSKYCI][!] Code: Code beetween Line 2 and Line 5.

[HUSKYCI][!] Language: HCL
[HUSKYCI][!] Tool: TFSec
[HUSKYCI][!] Severity: ERROR
[HUSKYCI][!] Details: AWS004 @ [Resource 'aws_alb_listener.my-alb-listener' uses plain HTTP instead of HTTPS.]
[HUSKYCI][!] File: /go/code/terraform/main.tf
[HUSKYCI][!] Line: 9
[HUSKYCI][!] Code: Code beetween Line 9 and Line 9.

[HUSKYCI][!] Language: HCL
[HUSKYCI][!] Tool: TFSec
[HUSKYCI][!] Severity: ERROR
[HUSKYCI][!] Details: AWS003 @ [Resource 'aws_db_security_group.my-group' uses EC2 Classic. Use a VPC instead.]
[HUSKYCI][!] File: /go/code/terraform/main.tf
[HUSKYCI][!] Line: 12
[HUSKYCI][!] Code: Code beetween Line 12 and Line 14.

[HUSKYCI][!] Language: HCL
[HUSKYCI][!] Tool: TFSec
[HUSKYCI][!] Severity: ERROR
[HUSKYCI][!] Details: AZU003 @ [Resource 'azurerm_managed_disk.source' defines an unencrypted managed disk.]
[HUSKYCI][!] File: /go/code/terraform/main.tf
[HUSKYCI][!] Line: 18
[HUSKYCI][!] Code: Code beetween Line 18 and Line 18.

[HUSKYCI][SUMMARY] HCL -> edersonbrilhante/tfsec:v0.19.0
[HUSKYCI][SUMMARY] High: 4
[HUSKYCI][SUMMARY] Medium: 1
[HUSKYCI][SUMMARY] Low: 0
[HUSKYCI][SUMMARY] NoSecHusky: 0

[HUSKYCI][SUMMARY] Total
[HUSKYCI][SUMMARY] High: 4
[HUSKYCI][SUMMARY] Medium: 1
[HUSKYCI][SUMMARY] Low: 0
[HUSKYCI][SUMMARY] NoSecHusky: 0

[HUSKYCI][*] The following securityTests were executed and no blocking vulnerabilities were found:
[HUSKYCI][*] [huskyci/gitleaks:2.1.0]
[HUSKYCI][*] Some HIGH/MEDIUM issues were found in these securityTests:
[HUSKYCI][*] [edersonbrilhante/tfsec:v0.19.0]
```

Patch:
diff --git a/Makefile b/Makefile
index bc5c19c..bf74a78 100644
--- a/Makefile
+++ b/Makefile
@@ -177,4 +177,4 @@ test:
 	cd cli && $(GO) tool cover -func=e.out
 
 ## Builds and push securityTest containers with the latest tags
-update-containers: build-containers push-containers
+update-containers: build-containers push-containers
\ No newline at end of file
diff --git a/api/config.yaml b/api/config.yaml
index 67ede9c..d447393 100644
--- a/api/config.yaml
+++ b/api/config.yaml
@@ -333,3 +333,28 @@ gitleaks:
   type: Generic
   default: true
   timeOutInSeconds: 360
+
+tfsec:
+  name: tfsec
+  image: huskyci/tfsec
+  imageTag: "v0.19.0"
+  cmd: |+
+    mkdir -p ~/.ssh &&
+    echo 'GIT_PRIVATE_SSH_KEY' > ~/.ssh/huskyci_id_rsa &&
+    chmod 600 ~/.ssh/huskyci_id_rsa &&
+    echo "IdentityFile ~/.ssh/huskyci_id_rsa" >> /etc/ssh/ssh_config &&
+    echo "StrictHostKeyChecking no" >> /etc/ssh/ssh_config &&
+    GIT_TERMINAL_PROMPT=0 git clone -b %GIT_BRANCH% --single-branch %GIT_REPO% code --quiet 2> /tmp/errorGitCloneTFSec
+    if [ $? -eq 0 ]; then
+        ./tfsec code --format=json | grep -v "WARNING: skipped" > pre-results.json
+        cat pre-results.json | grep -v "WARNING: skipped" > results.json
+        echo "{\"warnings\":\"$(cat pre-results.json | grep "WARNING: skipped")\"}" >> warning.json
+        cat results.json warning.json | jq -s add | jq -j -M -c .
+    else
+      echo "ERROR_CLONING"
+      cat /tmp/errorGitCloneTFSec
+    fi
+  type: Language
+  language: HCL
+  default: true
+  timeOutInSeconds: 360
diff --git a/api/context/context.go b/api/context/context.go
index 24011c8..a697257 100644
--- a/api/context/context.go
+++ b/api/context/context.go
@@ -81,6 +81,7 @@ type APIConfig struct {
 	SpotBugsSecurityTest   *types.SecurityTest
 	GitleaksSecurityTest   *types.SecurityTest
 	SafetySecurityTest     *types.SecurityTest
+	TFSecSecurityTest      *types.SecurityTest
 	DBInstance             db.Requests
 }
 
@@ -124,6 +125,7 @@ func (dF DefaultConfig) SetOnceConfig() {
 			SpotBugsSecurityTest:   dF.getSecurityTestConfig("spotbugs"),
 			GitleaksSecurityTest:   dF.getSecurityTestConfig("gitleaks"),
 			SafetySecurityTest:     dF.getSecurityTestConfig("safety"),
+			TFSecSecurityTest:      dF.getSecurityTestConfig("tfsec"),
 			DBInstance:             dF.GetDB(),
 		}
 	})
diff --git a/api/log/messagecodes.go b/api/log/messagecodes.go
index 75b2fab..deb3909 100644
--- a/api/log/messagecodes.go
+++ b/api/log/messagecodes.go
@@ -73,6 +73,7 @@ var MsgCode = map[int]string{
 	1037: "Internal error running Yarnaudit: ",
 	1038: "Could not Unmarshall the following gitleaksOutput: ",
 	1039: "Could not Unmarshall the following spotbugsOutput: ",
+	1040: "Could not Unmarshall the following tfsecOutput: ",
 
 	// MongoDB infos
 	21: "Connecting to MongoDB.",
diff --git a/api/types/types.go b/api/types/types.go
index 14d3259..55afd3d 100644
--- a/api/types/types.go
+++ b/api/types/types.go
@@ -96,6 +96,7 @@ type HuskyCIResults struct {
 	JavaScriptResults JavaScriptResults `bson:"javascriptresults,omitempty" json:"javascriptresults,omitempty"`
 	RubyResults       RubyResults       `bson:"rubyresults,omitempty" json:"rubyresults,omitempty"`
 	JavaResults       JavaResults       `bson:"javaresults,omitempty" json:"javaresults,omitempty"`
+	HclResults        HclResults        `bson:"hclresults,omitempty" json:"hclresults,omitempty"`
 	GenericResults    GenericResults    `bson:"genericresults,omitempty" json:"genericresults,omitempty"`
 }
 
@@ -131,6 +132,11 @@ type GenericResults struct {
 	HuskyCIGitleaksOutput HuskyCISecurityTestOutput `bson:"gitleaksoutput,omitempty" json:"gitleaksoutput,omitempty"`
 }
 
+// HclResults represents all HCL security tests results.
+type HclResults struct {
+	HuskyCITFSecOutput HuskyCISecurityTestOutput `bson:"tfsecoutput,omitempty" json:"tfsecoutput,omitempty"`
+}
+
 // HuskyCISecurityTestOutput stores all Low, Medium and High vulnerabilities for a sec test
 type HuskyCISecurityTestOutput struct {
 	NoSecVulns  []HuskyCIVulnerability `bson:"nosecvulns,omitempty" json:"nosecvulns,omitempty"`
diff --git a/api/util/api/api.go b/api/util/api/api.go
index 04dbb40..4fc4036 100644
--- a/api/util/api/api.go
+++ b/api/util/api/api.go
@@ -117,7 +117,7 @@ func (cH *CheckUtils) checkDB(configAPI *apiContext.APIConfig) error {
 }
 
 func (cH *CheckUtils) checkEachSecurityTest(configAPI *apiContext.APIConfig) error {
-	securityTests := []string{"enry", "gitauthors", "gosec", "brakeman", "bandit", "npmaudit", "yarnaudit", "spotbugs", "gitleaks", "safety"}
+	securityTests := []string{"enry", "gitauthors", "gosec", "brakeman", "bandit", "npmaudit", "yarnaudit", "spotbugs", "gitleaks", "safety", "tfsec"}
 	for _, securityTest := range securityTests {
 		if err := checkSecurityTest(securityTest, configAPI); err != nil {
 			errMsg := fmt.Sprintf("%s %s", securityTest, err)
@@ -171,6 +171,8 @@ func checkSecurityTest(securityTestName string, configAPI *apiContext.APIConfig)
 		securityTestConfig = *configAPI.GitleaksSecurityTest
 	case "safety":
 		securityTestConfig = *configAPI.SafetySecurityTest
+	case "tfsec":
+		securityTestConfig = *configAPI.TFSecSecurityTest
 	default:
 		return errors.New("securityTest name not defined")
 	}
diff --git a/cli/analysis/analysis.go b/cli/analysis/analysis.go
index 02e3318..21bf346 100644
--- a/cli/analysis/analysis.go
+++ b/cli/analysis/analysis.go
@@ -61,7 +61,8 @@ func (a *Analysis) CheckPath(path string) error {
 	}
 
 	for language := range a.getAvailableSecurityTests(a.Languages) {
-		fmt.Println(fmt.Sprintf("[HUSKYCI] %s found.", language))
+		s := fmt.Sprintf("[HUSKYCI] %s found.", language)
+		fmt.Println(s)
 	}
 
 	return nil
@@ -174,6 +175,8 @@ func (a *Analysis) getAvailableSecurityTests(languages []string) map[string][]st
 			list[language] = []string{"huskyci/npmaudit", "huskyci/yarnaudit"}
 		case "Java":
 			list[language] = []string{"huskyci/spotbugs"}
+		case "HCL":
+			list[language] = []string{"huskyci/tfsec"}
 		}
 	}
 
diff --git a/cli/cmd/root.go b/cli/cmd/root.go
index e0ce1ee..6e2c2df 100644
--- a/cli/cmd/root.go
+++ b/cli/cmd/root.go
@@ -24,8 +24,8 @@ var (
 huskyCI is an open source tool that orchestrates security tests and
 centralizes all results into a database for further analysis and metrics.
 It can perform static security analysis in Python (Bandit and Safety),
-Ruby (Brakeman), JavaScript (Npm Audit and Yarn Audit), Golang (Gosec),
-and Java (SpotBugs plus Find Sec Bugs). It can also audit repositories
+Ruby (Brakeman), JavaScript (Npm Audit and Yarn Audit), Golang (Gosec), 
+Java (SpotBugs plus Find Sec Bugs) and HCL (TFSec). It can also audit repositories
 for secrets like AWS Secret Keys, Private SSH Keys, and many others using
 GitLeaks.
 		`,
diff --git a/client/analysis/analysis.go b/client/analysis/analysis.go
index 8dce21d..e18edcd 100644
--- a/client/analysis/analysis.go
+++ b/client/analysis/analysis.go
@@ -65,7 +65,7 @@ func StartAnalysis() (string, error) {
 
 	RID := resp.Header.Get("X-Request-Id")
 	if RID == "" {
-		errorMsg := fmt.Sprintf("Error sending request to start analysis. RID is empty!")
+		errorMsg := "Error sending request to start analysis. RID is empty!"
 		return "", errors.New(errorMsg)
 	}
 
diff --git a/client/analysis/output.go b/client/analysis/output.go
index ee90360..8ebd79f 100644
--- a/client/analysis/output.go
+++ b/client/analysis/output.go
@@ -67,6 +67,11 @@ func printSTDOUTOutput(analysis types.Analysis) {
 	printSTDOUTOutputSpotBugs(outputJSON.JavaResults.HuskyCISpotBugsOutput.MediumVulns)
 	printSTDOUTOutputSpotBugs(outputJSON.JavaResults.HuskyCISpotBugsOutput.HighVulns)
 
+	// tfsec
+	printSTDOUTOutputTFSec(outputJSON.HclResults.HuskyCITFSecOutput.LowVulns)
+	printSTDOUTOutputTFSec(outputJSON.HclResults.HuskyCITFSecOutput.MediumVulns)
+	printSTDOUTOutputTFSec(outputJSON.HclResults.HuskyCITFSecOutput.HighVulns)
+
 	printAllSummary(analysis)
 }
 
@@ -79,6 +84,7 @@ func prepareAllSummary(analysis types.Analysis) {
 	outputJSON.PythonResults = analysis.HuskyCIResults.PythonResults
 	outputJSON.RubyResults = analysis.HuskyCIResults.RubyResults
 	outputJSON.JavaResults = analysis.HuskyCIResults.JavaResults
+	outputJSON.HclResults = analysis.HuskyCIResults.HclResults
 	outputJSON.GenericResults = analysis.HuskyCIResults.GenericResults
 
 	// GoSec summary
@@ -172,22 +178,33 @@ func prepareAllSummary(analysis types.Analysis) {
 		outputJSON.Summary.GitleaksSummary.FoundVuln = true
 	}
 
+	// TFSec summary
+	outputJSON.Summary.TFSecSummary.LowVuln = len(outputJSON.HclResults.HuskyCITFSecOutput.LowVulns)
+	outputJSON.Summary.TFSecSummary.MediumVuln = len(outputJSON.HclResults.HuskyCITFSecOutput.MediumVulns)
+	outputJSON.Summary.TFSecSummary.HighVuln = len(outputJSON.HclResults.HuskyCITFSecOutput.HighVulns)
+	if len(outputJSON.HclResults.HuskyCITFSecOutput.LowVulns) > 0 || len(outputJSON.HclResults.HuskyCITFSecOutput.NoSecVulns) > 0 {
+		outputJSON.Summary.TFSecSummary.FoundInfo = true
+	}
+	if len(outputJSON.HclResults.HuskyCITFSecOutput.MediumVulns) > 0 || len(outputJSON.HclResults.HuskyCITFSecOutput.HighVulns) > 0 {
+		outputJSON.Summary.TFSecSummary.FoundVuln = true
+	}
+
 	// Total summary
-	if outputJSON.Summary.GosecSummary.FoundVuln || outputJSON.Summary.BanditSummary.FoundVuln || outputJSON.Summary.SafetySummary.FoundVuln || outputJSON.Summary.BrakemanSummary.FoundVuln || outputJSON.Summary.NpmAuditSummary.FoundVuln || outputJSON.Summary.YarnAuditSummary.FoundVuln || outputJSON.Summary.GitleaksSummary.FoundVuln || outputJSON.Summary.SpotBugsSummary.FoundVuln {
+	if outputJSON.Summary.GosecSummary.FoundVuln || outputJSON.Summary.BanditSummary.FoundVuln || outputJSON.Summary.SafetySummary.FoundVuln || outputJSON.Summary.BrakemanSummary.FoundVuln || outputJSON.Summary.NpmAuditSummary.FoundVuln || outputJSON.Summary.YarnAuditSummary.FoundVuln || outputJSON.Summary.GitleaksSummary.FoundVuln || outputJSON.Summary.SpotBugsSummary.FoundVuln || outputJSON.Summary.TFSecSummary.FoundVuln {
 		outputJSON.Summary.TotalSummary.FoundVuln = true
 		types.FoundVuln = true
-	} else if outputJSON.Summary.GosecSummary.FoundInfo || outputJSON.Summary.BanditSummary.FoundInfo || outputJSON.Summary.SafetySummary.FoundInfo || outputJSON.Summary.BrakemanSummary.FoundInfo || outputJSON.Summary.NpmAuditSummary.FoundInfo || outputJSON.Summary.YarnAuditSummary.FoundInfo || outputJSON.Summary.GitleaksSummary.FoundInfo || outputJSON.Summary.SpotBugsSummary.FoundInfo {
+	} else if outputJSON.Summary.GosecSummary.FoundInfo || outputJSON.Summary.BanditSummary.FoundInfo || outputJSON.Summary.SafetySummary.FoundInfo || outputJSON.Summary.BrakemanSummary.FoundInfo || outputJSON.Summary.NpmAuditSummary.FoundInfo || outputJSON.Summary.YarnAuditSummary.FoundInfo || outputJSON.Summary.GitleaksSummary.FoundInfo || outputJSON.Summary.SpotBugsSummary.FoundInfo || outputJSON.Summary.TFSecSummary.FoundInfo {
 		outputJSON.Summary.TotalSummary.FoundInfo = true
 		types.FoundInfo = true
 	}
 
 	totalNoSec = outputJSON.Summary.BanditSummary.NoSecVuln + outputJSON.Summary.GosecSummary.NoSecVuln + outputJSON.Summary.GitleaksSummary.NoSecVuln
 
-	totalLow = outputJSON.Summary.BrakemanSummary.LowVuln + outputJSON.Summary.SafetySummary.LowVuln + outputJSON.Summary.BanditSummary.LowVuln + outputJSON.Summary.GosecSummary.LowVuln + outputJSON.Summary.NpmAuditSummary.LowVuln + outputJSON.Summary.YarnAuditSummary.LowVuln + outputJSON.Summary.GitleaksSummary.LowVuln + outputJSON.Summary.SpotBugsSummary.LowVuln
+	totalLow = outputJSON.Summary.BrakemanSummary.LowVuln + outputJSON.Summary.SafetySummary.LowVuln + outputJSON.Summary.BanditSummary.LowVuln + outputJSON.Summary.GosecSummary.LowVuln + outputJSON.Summary.NpmAuditSummary.LowVuln + outputJSON.Summary.YarnAuditSummary.LowVuln + outputJSON.Summary.GitleaksSummary.LowVuln + outputJSON.Summary.SpotBugsSummary.LowVuln + outputJSON.Summary.TFSecSummary.LowVuln
 
-	totalMedium = outputJSON.Summary.BrakemanSummary.MediumVuln + outputJSON.Summary.SafetySummary.MediumVuln + outputJSON.Summary.BanditSummary.MediumVuln + outputJSON.Summary.GosecSummary.MediumVuln + outputJSON.Summary.NpmAuditSummary.MediumVuln + outputJSON.Summary.YarnAuditSummary.MediumVuln + outputJSON.Summary.GitleaksSummary.MediumVuln + outputJSON.Summary.SpotBugsSummary.MediumVuln
+	totalMedium = outputJSON.Summary.BrakemanSummary.MediumVuln + outputJSON.Summary.SafetySummary.MediumVuln + outputJSON.Summary.BanditSummary.MediumVuln + outputJSON.Summary.GosecSummary.MediumVuln + outputJSON.Summary.NpmAuditSummary.MediumVuln + outputJSON.Summary.YarnAuditSummary.MediumVuln + outputJSON.Summary.GitleaksSummary.MediumVuln + outputJSON.Summary.SpotBugsSummary.MediumVuln + outputJSON.Summary.TFSecSummary.MediumVuln
 
-	totalHigh = outputJSON.Summary.BrakemanSummary.HighVuln + outputJSON.Summary.SafetySummary.HighVuln + outputJSON.Summary.BanditSummary.HighVuln + outputJSON.Summary.GosecSummary.HighVuln + outputJSON.Summary.NpmAuditSummary.HighVuln + outputJSON.Summary.YarnAuditSummary.HighVuln + outputJSON.Summary.GitleaksSummary.HighVuln + outputJSON.Summary.SpotBugsSummary.HighVuln
+	totalHigh = outputJSON.Summary.BrakemanSummary.HighVuln + outputJSON.Summary.SafetySummary.HighVuln + outputJSON.Summary.BanditSummary.HighVuln + outputJSON.Summary.GosecSummary.HighVuln + outputJSON.Summary.NpmAuditSummary.HighVuln + outputJSON.Summary.YarnAuditSummary.HighVuln + outputJSON.Summary.GitleaksSummary.HighVuln + outputJSON.Summary.SpotBugsSummary.HighVuln + outputJSON.Summary.TFSecSummary.HighVuln
 
 	outputJSON.Summary.TotalSummary.HighVuln = totalHigh
 	outputJSON.Summary.TotalSummary.MediumVuln = totalMedium
@@ -198,7 +215,7 @@ func prepareAllSummary(analysis types.Analysis) {
 
 func printAllSummary(analysis types.Analysis) {
 
-	var gosecVersion, banditVersion, safetyVersion, brakemanVersion, npmauditVersion, yarnauditVersion, gitleaksVersion, spotbugsVersion string
+	var gosecVersion, banditVersion, safetyVersion, brakemanVersion, npmauditVersion, yarnauditVersion, gitleaksVersion, spotbugsVersion, tfsecVersion string
 
 	for _, container := range analysis.Containers {
 		switch container.SecurityTest.Name {
@@ -218,6 +235,8 @@ func printAllSummary(analysis types.Analysis) {
 			spotbugsVersion = fmt.Sprintf("%s:%s", container.SecurityTest.Image, container.SecurityTest.ImageTag)
 		case "gitleaks":
 			gitleaksVersion = fmt.Sprintf("%s:%s", container.SecurityTest.Image, container.SecurityTest.ImageTag)
+		case "tfsec":
+			tfsecVersion = fmt.Sprintf("%s:%s", container.SecurityTest.Image, container.SecurityTest.ImageTag)
 		}
 	}
 
@@ -293,6 +312,15 @@ func printAllSummary(analysis types.Analysis) {
 		fmt.Printf("[HUSKYCI][SUMMARY] NoSecHusky: %d\n", outputJSON.Summary.GitleaksSummary.NoSecVuln)
 	}
 
+	if outputJSON.Summary.TFSecSummary.FoundVuln || outputJSON.Summary.TFSecSummary.FoundInfo {
+		fmt.Println()
+		fmt.Printf("[HUSKYCI][SUMMARY] HCL -> %s\n", tfsecVersion)
+		fmt.Printf("[HUSKYCI][SUMMARY] High: %d\n", outputJSON.Summary.TFSecSummary.HighVuln)
+		fmt.Printf("[HUSKYCI][SUMMARY] Medium: %d\n", outputJSON.Summary.TFSecSummary.MediumVuln)
+		fmt.Printf("[HUSKYCI][SUMMARY] Low: %d\n", outputJSON.Summary.TFSecSummary.LowVuln)
+		fmt.Printf("[HUSKYCI][SUMMARY] NoSecHusky: %d\n", outputJSON.Summary.TFSecSummary.NoSecVuln)
+	}
+
 	if outputJSON.Summary.TotalSummary.FoundVuln || outputJSON.Summary.TotalSummary.FoundInfo {
 		fmt.Println()
 		fmt.Printf("[HUSKYCI][SUMMARY] Total\n")
@@ -406,6 +434,18 @@ func printSTDOUTOutputSpotBugs(issues []types.HuskyCIVulnerability) {
 		fmt.Printf("[HUSKYCI][!] Type: %s\n", issue.Type)
 	}
 }
+func printSTDOUTOutputTFSec(issues []types.HuskyCIVulnerability) {
+	for _, issue := range issues {
+		fmt.Println()
+		fmt.Printf("[HUSKYCI][!] Language: %s\n", issue.Language)
+		fmt.Printf("[HUSKYCI][!] Tool: %s\n", issue.SecurityTool)
+		fmt.Printf("[HUSKYCI][!] Severity: %s\n", issue.Severity)
+		fmt.Printf("[HUSKYCI][!] Details: %s\n", issue.Details)
+		fmt.Printf("[HUSKYCI][!] File: %s\n", issue.File)
+		fmt.Printf("[HUSKYCI][!] Line: %s\n", issue.Line)
+		fmt.Printf("[HUSKYCI][!] Code: %s\n", issue.Code)
+	}
+}
 
 func printSTDOUTOutputGitleaks(issues []types.HuskyCIVulnerability) {
 	for _, issue := range issues {
diff --git a/client/cmd/main.go b/client/cmd/main.go
index 894b7c8..460719d 100644
--- a/client/cmd/main.go
+++ b/client/cmd/main.go
@@ -35,7 +35,8 @@ func main() {
 
 	// step 1: start analysis and get its RID.
 	if !types.IsJSONoutput {
-		fmt.Println(fmt.Sprintf("[HUSKYCI][*] %s -> %s", config.RepositoryBranch, config.RepositoryURL))
+		s := fmt.Sprintf("[HUSKYCI][*] %s -> %s", config.RepositoryBranch, config.RepositoryURL)
+		fmt.Println(s)
 	}
 	RID, err := analysis.StartAnalysis()
 	if err != nil {
@@ -49,7 +50,8 @@ func main() {
 	// step 2.1: keep querying huskyCI API to check if a given analysis has already finished.
 	huskyAnalysis, err := analysis.MonitorAnalysis(RID)
 	if err != nil {
-		fmt.Println(fmt.Sprintf("[HUSKYCI][ERROR] Monitoring analysis %s: %s", RID, err))
+		s := fmt.Sprintf("[HUSKYCI][ERROR] Monitoring analysis %s: %s", RID, err)
+		fmt.Println(s)
 		os.Exit(1)
 	}
 
diff --git a/client/types/tfsec.go b/client/types/tfsec.go
new file mode 100644
index 0000000..c98b0ac
--- /dev/null
+++ b/client/types/tfsec.go
@@ -0,0 +1,29 @@
+// Copyright 2019 Globo.com authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package types
+
+import "encoding/json"
+
+// TFSecOutput is the struct that holds all data from TFSec output.
+type TFSecOutput struct {
+	Warnings json.RawMessage `json:"warnings"`
+	Results  []TFSecResult   `json:"results"`
+}
+
+// TFSecResult is the struct that holds detailed information of results from TFSec output.
+type TFSecResult struct {
+	RuleID      string   `json:"rule_id"`
+	Link        string   `json:"link"`
+	Location    Location `json:"location"`
+	Description string   `json:"description"`
+	Severity    string   `json:"severity"`
+}
+
+// Location is the struct that holds detailed information of location from each result
+type Location struct {
+	Filename  string `json:"filename"`
+	StartLine int    `json:"start_line"`
+	EndLine   int    `json:"end_line"`
+}
diff --git a/client/types/types.go b/client/types/types.go
index b5c0f2e..73b04c4 100644
--- a/client/types/types.go
+++ b/client/types/types.go
@@ -62,6 +62,7 @@ type HuskyCIResults struct {
 	JavaScriptResults JavaScriptResults `bson:"javascriptresults,omitempty" json:"javascriptresults,omitempty"`
 	RubyResults       RubyResults       `bson:"rubyresults,omitempty" json:"rubyresults,omitempty"`
 	JavaResults       JavaResults       `bson:"javaresults,omitempty" json:"javaresults,omitempty"`
+	HclResults        HclResults        `bson:"hclresults,omitempty" json:"hclresults,omitempty"`
 	GenericResults    GenericResults    `bson:"genericresults,omitempty" json:"genericresults,omitempty"`
 }
 
@@ -112,6 +113,7 @@ type JSONOutput struct {
 	JavaScriptResults JavaScriptResults `json:"javascriptresults,omitempty"`
 	RubyResults       RubyResults       `json:"rubyresults,omitempty"`
 	JavaResults       JavaResults       `json:"javaresults,omitempty"`
+	HclResults        HclResults        `json:"hclresults,omitempty"`
 	GenericResults    GenericResults    `json:"genericresults,omitempty"`
 	Summary           Summary           `json:"summary,omitempty"`
 }
@@ -148,6 +150,11 @@ type GenericResults struct {
 	HuskyCIGitleaksOutput HuskyCISecurityTestOutput `bson:"gitleaksoutput,omitempty" json:"gitleaksoutput,omitempty"`
 }
 
+// HclResults represents all HCL security tests results.
+type HclResults struct {
+	HuskyCITFSecOutput HuskyCISecurityTestOutput `bson:"tfsecoutput,omitempty" json:"tfsecoutput,omitempty"`
+}
+
 // HuskyCISecurityTestOutput stores all Low, Medium and High vulnerabilities for a sec test
 type HuskyCISecurityTestOutput struct {
 	NoSecVulns  []HuskyCIVulnerability `bson:"nosecvulns,omitempty" json:"nosecvulns,omitempty"`
@@ -169,6 +176,7 @@ type Summary struct {
 	BrakemanSummary  HuskyCISummary `json:"brakemansummary,omitempty"`
 	SpotBugsSummary  HuskyCISummary `json:"spotbugssummary,omitempty"`
 	GitleaksSummary  HuskyCISummary `json:"gitleakssummary,omitempty"`
+	TFSecSummary     HuskyCISummary `json:"tfsecsummary,omitempty"`
 	TotalSummary     HuskyCISummary `json:"totalsummary,omitempty"`
 }
 
diff --git a/deployments/dockerfiles/tfsec/Dockerfile b/deployments/dockerfiles/tfsec/Dockerfile
new file mode 100644
index 0000000..41f9863
--- /dev/null
+++ b/deployments/dockerfiles/tfsec/Dockerfile
@@ -0,0 +1,14 @@
+# Dockerfile used to create "husyci/tfsec" image
+# https://hub.docker.com/r/huskyci/tfsec/
+FROM golang:1.13-alpine
+
+ARG TFSEC_VERSION=v0.19.0
+
+RUN apk update && apk upgrade \
+	&& apk add git jq openssh-client
+
+RUN wget https://github.com/liamg/tfsec/releases/download/${TFSEC_VERSION}/tfsec-linux-amd64
+
+RUN mv tfsec-linux-amd64 tfsec
+
+RUN chmod +x tfsec
\ No newline at end of file
diff --git a/deployments/scripts/build-containers.sh b/deployments/scripts/build-containers.sh
index b034277..11756ca 100755
--- a/deployments/scripts/build-containers.sh
+++ b/deployments/scripts/build-containers.sh
@@ -16,4 +16,5 @@ docker build deployments/dockerfiles/npmaudit/ -t huskyci/npmaudit:latest
 docker build deployments/dockerfiles/npmaudit/ -t huskyci/yarnaudit:latest
 docker build deployments/dockerfiles/safety/ -t huskyci/safety:latest
 docker build deployments/dockerfiles/gitleaks/ -t huskyci/gitleaks:latest
-docker build deployments/dockerfiles/spotbugs/ -t huskyci/spotbugs:latest
\ No newline at end of file
+docker build deployments/dockerfiles/spotbugs/ -t huskyci/spotbugs:latest
+docker build deployments/dockerfiles/tfsec/ -t huskyci/tfsec:latest
\ No newline at end of file
diff --git a/deployments/scripts/check-containers-version.sh b/deployments/scripts/check-containers-version.sh
index 317e6d4..fc43fdf 100755
--- a/deployments/scripts/check-containers-version.sh
+++ b/deployments/scripts/check-containers-version.sh
@@ -18,6 +18,7 @@ yarnAuditVersion=$(docker run --rm huskyci/yarnaudit:latest yarn audit --version
 safetyVersion=$(docker run --rm huskyci/safety:latest safety --version | awk -F " " '{print $3}')
 gitleaksVersion=$(docker run --rm huskyci/gitleaks:latest gitleaks --version)
 spotbugsVersion=$(docker run --rm huskyci/spotbugs:latest cat /opt/spotbugs/version)
+tfsecVersion=$(docker run --rm huskyci/tfsec:latest ./tfsec -v)
 
 echo "bandit: $banditVersion"
 echo "brakeman: $brakemanVersion"
@@ -28,4 +29,5 @@ echo "npmauditVersion: $npmAuditVersion"
 echo "yarnauditVersion: $yarnAuditVersion"
 echo "safetyVersion: $safetyVersion"
 echo "gitleaksVersion: $gitleaksVersion"
-echo "spotbugsVersion: $spotbugsVersion"
\ No newline at end of file
+echo "spotbugsVersion: $spotbugsVersion"
+echo "tfsecVersion: $tfsecVersion"
\ No newline at end of file
diff --git a/deployments/scripts/push-containers.sh b/deployments/scripts/push-containers.sh
index c270304..9146970 100755
--- a/deployments/scripts/push-containers.sh
+++ b/deployments/scripts/push-containers.sh
@@ -17,6 +17,7 @@ yarnAuditVersion=$(docker run --rm huskyci/yarnaudit:latest yarn audit --version
 safetyVersion=$(docker run --rm huskyci/safety:latest safety --version | awk -F " " '{print $3}')
 gitleaksVersion=$(docker run --rm huskyci/gitleaks:latest gitleaks --version)
 spotbugsVersion=$(docker run --rm huskyci/spotbugs:latest cat /opt/spotbugs/version)
+tfsecVersion=$(docker run --rm huskyci/tfsec:latest ./tfsec -v)
 
 docker tag "huskyci/bandit:latest" "huskyci/bandit:$banditVersion"
 docker tag "huskyci/brakeman:latest" "huskyci/brakeman:$brakemanVersion"
@@ -28,6 +29,7 @@ docker tag "huskyci/yarnaudit:latest" "huskyci/yarnaudit:$yarnAuditVersion"
 docker tag "huskyci/safety:latest" "huskyci/safety:$safetyVersion"
 docker tag "huskyci/gitleaks:latest" "huskyci/gitleaks:$gitleaksVersion"
 docker tag "huskyci/spotbugs:latest" "huskyci/spotbugs:$spotbugsVersion"
+docker tag "huskyci/tfsec:latest" "huskyci/tfsec:$tfsecVersion"
 
 docker push "huskyci/bandit:latest" && docker push "huskyci/bandit:$banditVersion"
 docker push "huskyci/brakeman:latest" && docker push "huskyci/brakeman:$brakemanVersion"
@@ -39,3 +41,4 @@ docker push "huskyci/yarnaudit:latest" && docker push "huskyci/yarnaudit:$yarnAu
 docker push "huskyci/safety:latest" && docker push "huskyci/safety:$safetyVersion"
 docker push "huskyci/gitleaks:latest" && docker push "huskyci/gitleaks:$gitleaksVersion"
 docker push "huskyci/spotbugs:latest" && docker push "huskyci/spotbugs:$spotbugsVersion"
+docker push "huskyci/tfsec:latest" && docker push "huskyci/tfsec:$tfsecVersion"


Test patch:
diff --git a/api/context/context_test.go b/api/context/context_test.go
index 8ba9791..859d135 100644
--- a/api/context/context_test.go
+++ b/api/context/context_test.go
@@ -427,6 +427,16 @@ var _ = Describe("Context", func() {
 						Default:          fakeCaller.expectedBoolFromConfig,
 						TimeOutInSeconds: fakeCaller.expectedIntFromConfig,
 					},
+					TFSecSecurityTest: &types.SecurityTest{
+						Name:             fakeCaller.expectedStringFromConfig,
+						Image:            fakeCaller.expectedStringFromConfig,
+						ImageTag:         fakeCaller.expectedStringFromConfig,
+						Cmd:              fakeCaller.expectedStringFromConfig,
+						Type:             fakeCaller.expectedStringFromConfig,
+						Language:         fakeCaller.expectedStringFromConfig,
+						Default:          fakeCaller.expectedBoolFromConfig,
+						TimeOutInSeconds: fakeCaller.expectedIntFromConfig,
+					},
 					DBInstance: &db.MongoRequests{},
 				}
 				Expect(apiConfig).To(Equal(expectedConfig))
diff --git a/api/securitytest/run.go b/api/securitytest/run.go
index f48e57d..abbe61e 100644
--- a/api/securitytest/run.go
+++ b/api/securitytest/run.go
@@ -28,6 +28,7 @@ const npmaudit = "npmaudit"
 const yarnaudit = "yarnaudit"
 const spotbugs = "spotbugs"
 const gitleaks = "gitleaks"
+const tfsec = "tfsec"
 
 // Start runs both generic and language security
 func (results *RunAllInfo) Start(enryScan SecTestScanInfo) error {
@@ -221,6 +222,8 @@ func (results *RunAllInfo) setVulns(securityTestScan SecTestScanInfo) {
 			results.HuskyCIResults.JavaResults.HuskyCISpotBugsOutput.HighVulns = append(results.HuskyCIResults.JavaResults.HuskyCISpotBugsOutput.HighVulns, highVuln)
 		case gitleaks:
 			results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.HighVulns = append(results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.HighVulns, highVuln)
+		case tfsec:
+			results.HuskyCIResults.HclResults.HuskyCITFSecOutput.HighVulns = append(results.HuskyCIResults.HclResults.HuskyCITFSecOutput.HighVulns, highVuln)
 		}
 	}
 
@@ -242,6 +245,8 @@ func (results *RunAllInfo) setVulns(securityTestScan SecTestScanInfo) {
 			results.HuskyCIResults.JavaResults.HuskyCISpotBugsOutput.MediumVulns = append(results.HuskyCIResults.JavaResults.HuskyCISpotBugsOutput.MediumVulns, mediumVuln)
 		case gitleaks:
 			results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.MediumVulns = append(results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.MediumVulns, mediumVuln)
+		case tfsec:
+			results.HuskyCIResults.HclResults.HuskyCITFSecOutput.MediumVulns = append(results.HuskyCIResults.HclResults.HuskyCITFSecOutput.MediumVulns, mediumVuln)
 		}
 	}
 
@@ -263,6 +268,8 @@ func (results *RunAllInfo) setVulns(securityTestScan SecTestScanInfo) {
 			results.HuskyCIResults.JavaResults.HuskyCISpotBugsOutput.LowVulns = append(results.HuskyCIResults.JavaResults.HuskyCISpotBugsOutput.LowVulns, lowVuln)
 		case gitleaks:
 			results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.LowVulns = append(results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.LowVulns, lowVuln)
+		case tfsec:
+			results.HuskyCIResults.HclResults.HuskyCITFSecOutput.LowVulns = append(results.HuskyCIResults.HclResults.HuskyCITFSecOutput.LowVulns, lowVuln)
 		}
 	}
 
@@ -284,6 +291,8 @@ func (results *RunAllInfo) setVulns(securityTestScan SecTestScanInfo) {
 			results.HuskyCIResults.JavaResults.HuskyCISpotBugsOutput.NoSecVulns = append(results.HuskyCIResults.JavaResults.HuskyCISpotBugsOutput.NoSecVulns, noSec)
 		case gitleaks:
 			results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.NoSecVulns = append(results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.NoSecVulns, noSec)
+		case tfsec:
+			results.HuskyCIResults.HclResults.HuskyCITFSecOutput.NoSecVulns = append(results.HuskyCIResults.HclResults.HuskyCITFSecOutput.NoSecVulns, noSec)
 		}
 	}
 }
diff --git a/api/securitytest/securitytest.go b/api/securitytest/securitytest.go
index 45140e3..460add0 100644
--- a/api/securitytest/securitytest.go
+++ b/api/securitytest/securitytest.go
@@ -23,6 +23,7 @@ var securityTestAnalyze = map[string]func(scanInfo *SecTestScanInfo) error{
 	"spotbugs":   analyzeSpotBugs,
 	"gitleaks":   analyseGitleaks,
 	"safety":     analyzeSafety,
+	"tfsec":      analyzeTFSec,
 }
 
 // SecTestScanInfo holds all information of securityTest scan.
diff --git a/api/securitytest/tfsec.go b/api/securitytest/tfsec.go
new file mode 100644
index 0000000..44fd6a8
--- /dev/null
+++ b/api/securitytest/tfsec.go
@@ -0,0 +1,90 @@
+// Copyright 2019 Globo.com authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package securitytest
+
+import (
+	"encoding/json"
+	"fmt"
+	"strconv"
+
+	"github.com/globocom/huskyCI/api/log"
+	"github.com/globocom/huskyCI/api/types"
+)
+
+// TFSecOutput is the struct that holds all data from TFSec output.
+type TFSecOutput struct {
+	Warnings json.RawMessage `json:"warnings"`
+	Results  []TFSecResult   `json:"results"`
+}
+
+// TFSecResult is the struct that holds detailed information of results from TFSec output.
+type TFSecResult struct {
+	RuleID      string   `json:"rule_id"`
+	Link        string   `json:"link"`
+	Location    Location `json:"location"`
+	Description string   `json:"description"`
+	Severity    string   `json:"severity"`
+}
+
+// Location is the struct that holds detailed information of location from each result
+type Location struct {
+	Filename  string `json:"filename"`
+	StartLine int    `json:"start_line"`
+	EndLine   int    `json:"end_line"`
+}
+
+func analyzeTFSec(tfsecScan *SecTestScanInfo) error {
+
+	tfsecOutput := TFSecOutput{}
+
+	// Unmarshall rawOutput into finalOutput, that is a TFSec struct.
+	if err := json.Unmarshal([]byte(tfsecScan.Container.COutput), &tfsecOutput); err != nil {
+		log.Error("analyzeTFSec", "TFSEC", 1040, tfsecScan.Container.COutput, err)
+		tfsecScan.ErrorFound = err
+		return err
+	}
+	tfsecScan.FinalOutput = tfsecOutput
+
+	// an empty Results slice states that no Issues were found.
+	if tfsecOutput.Results == nil {
+		tfsecScan.prepareContainerAfterScan()
+		return nil
+	}
+
+	// check results and prepare all vulnerabilities found
+	tfsecScan.prepareTFSecVulns()
+	tfsecScan.prepareContainerAfterScan()
+	return nil
+}
+
+func (tfsecScan *SecTestScanInfo) prepareTFSecVulns() {
+
+	huskyCItfsecResults := types.HuskyCISecurityTestOutput{}
+	tfsecOutput := tfsecScan.FinalOutput.(TFSecOutput)
+
+	for _, result := range tfsecOutput.Results {
+		tfsecVuln := types.HuskyCIVulnerability{}
+		tfsecVuln.Language = "HCL"
+		tfsecVuln.SecurityTool = "TFSec"
+		tfsecVuln.Severity = result.Severity
+		tfsecVuln.Details = result.RuleID + " @ [" + result.Description + "]"
+		startLine := strconv.Itoa(result.Location.StartLine)
+		endLine := strconv.Itoa(result.Location.EndLine)
+		tfsecVuln.Line = startLine
+		tfsecVuln.Code = fmt.Sprintf("Code beetween Line %s and Line %s.", startLine, endLine)
+		tfsecVuln.File = result.Location.Filename
+
+		switch tfsecVuln.Severity {
+		case "INFO":
+			huskyCItfsecResults.LowVulns = append(huskyCItfsecResults.LowVulns, tfsecVuln)
+		case "WARNING":
+			huskyCItfsecResults.MediumVulns = append(huskyCItfsecResults.MediumVulns, tfsecVuln)
+		case "ERROR":
+			huskyCItfsecResults.HighVulns = append(huskyCItfsecResults.HighVulns, tfsecVuln)
+		}
+	}
+
+	tfsecScan.Vulnerabilities = huskyCItfsecResults
+}