Repository: tfsec/tfsec
Pull request #765 (2021-06-01 15:24:01)

Create a public scanner for external tools


Patch:
diff --git a/example/external/main.go b/example/external/main.go
new file mode 100644
index 000000000..1085b1fd6
--- /dev/null
+++ b/example/external/main.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+	"fmt"
+
+	"github.com/tfsec/tfsec/pkg/externalscan"
+)
+
+func main() {
+	scanner := externalscan.NewExternalScanner()
+
+	_ = scanner.AddPath("../custom/custom_check.tf")
+	_ = scanner.AddPath("../custom/modules/public_custom_bucket/main.tf")
+	_ = scanner.AddPath("../good/good.tf")
+	_ = scanner.AddPath("../withVars/main.tf")
+	_ = scanner.AddPath("../withVars/variables.tf")
+
+	results, err := scanner.Scan()
+	if err != nil {
+		panic(err)
+	}
+
+	for _, result := range results {
+		fmt.Printf("%s: %s\n", result.RuleID, result.Description)
+	}
+}
diff --git a/pkg/externalscan/external_scan.go b/pkg/externalscan/external_scan.go
new file mode 100644
index 000000000..950150f0a
--- /dev/null
+++ b/pkg/externalscan/external_scan.go
@@ -0,0 +1,99 @@
+package externalscan
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	_ "github.com/tfsec/tfsec/internal/app/tfsec/checks"
+	"github.com/tfsec/tfsec/internal/app/tfsec/parser"
+	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
+)
+
+type ExternalScanner struct {
+	paths []string
+}
+
+func NewExternalScanner() *ExternalScanner {
+	return &ExternalScanner{}
+}
+
+func (t *ExternalScanner) AddPath(path string) error {
+	abs, err := filepath.Abs(path)
+	if err != nil {
+		return err
+	}
+	t.paths = append(t.paths, abs)
+	return nil
+}
+
+func (t *ExternalScanner) Scan() ([]scanner.Result, error) {
+
+	projectBlocks := make(map[string]parser.Blocks)
+
+	dirs, err := findTFRootModules(t.paths)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, dir := range dirs {
+		blocks, err := parser.New(dir, "").ParseDirectory()
+		if err != nil {
+			return nil, err
+		}
+		projectBlocks[dir] = blocks
+	}
+
+	var results []scanner.Result
+
+	for _, blockset := range projectBlocks {
+		projectResults := scanner.New().Scan(blockset, nil)
+		results = append(results, projectResults...)
+	}
+
+	return results, nil
+}
+
+func findTFRootModules(paths []string) ([]string, error) {
+
+	var output []string
+
+	if len(paths) == 0 {
+		return nil, fmt.Errorf("no files to scan")
+	}
+
+	dirMap := make(map[string]bool)
+	for _, path := range paths {
+		stat, err := os.Stat(path)
+		if err != nil {
+			return nil, err
+		}
+		if stat.IsDir() {
+			dirMap[path] = true
+		} else {
+			dirMap[filepath.Dir(path)] = true
+		}
+
+	}
+
+	var dirs []string
+	for dir := range dirMap {
+		dirs = append(dirs, dir)
+	}
+
+	sort.Strings(dirs)
+
+	previous := dirs[0]
+	output = append(output, previous)
+
+	for i := 1; i < len(dirs); i++ {
+		if !strings.HasPrefix(dirs[i], fmt.Sprintf("%s%c", previous, os.PathSeparator)) {
+			output = append(output, dirs[i])
+		}
+		previous = dirs[i]
+	}
+
+	return output, nil
+}


Test patch:
diff --git a/pkg/externalscan/external_scan_test.go b/pkg/externalscan/external_scan_test.go
new file mode 100644
index 000000000..a7d961191
--- /dev/null
+++ b/pkg/externalscan/external_scan_test.go
@@ -0,0 +1,37 @@
+package externalscan
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestExternal(t *testing.T) {
+
+	tmp := os.TempDir()
+	testDir := filepath.Join(tmp, fmt.Sprintf("tfsec-test-%d", time.Now().UnixNano()))
+
+	example := []string{
+		filepath.Join(testDir, "tf", "main.tf"),
+		filepath.Join(testDir, "tf", "modules", "main.tf"),
+		filepath.Join(testDir, "tf", "modules", "something", "main.tf"),
+	}
+	defer os.RemoveAll(testDir)
+
+	for _, path := range example {
+		require.NoError(t, os.MkdirAll(filepath.Dir(path), 0700))
+		f, err := os.Create(path)
+		require.NoError(t, err)
+		f.Close()
+	}
+
+	results, err := findTFRootModules(example)
+	require.NoError(t, err)
+	require.Len(t, results, 1)
+	assert.Equal(t, filepath.Join(testDir, "tf"), results[0])
+}