From 510c16ac49de7fb0db7756f2bc28d14a32a68141 Mon Sep 17 00:00:00 2001 From: vnandusekar Date: Mon, 12 Apr 2021 18:43:47 -0700 Subject: [PATCH] Adding tfsec parser for lintly --- README.md | 14 ++++++--- lintly/parsers.py | 51 +++++++++++++++++++++++++++++++++ setup.py | 2 +- test.tf | 38 ++++++++++++++++++++++++ tests/linters_output/tfsec.json | 32 +++++++++++++++++++++ tests/test_parsers.py | 15 ++++++++++ 6 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 test.tf create mode 100644 tests/linters_output/tfsec.json diff --git a/README.md b/README.md index 4b51e53..4d833de 100644 --- a/README.md +++ b/README.md @@ -63,16 +63,21 @@ Now you will see a review with linting errors... ``` - [hadolint](https://github.com/hadolint/hadolint) ``` - $ hadolint path/to/Dockerfile --format json |lintly --format=hadolint + $ hadolint path/to/Dockerfile --format json | lintly --format=hadolint ``` - [terrascan](https://github.com/accurics/terrascan) ``` - $ terrascan scan -d path/to/terraform/file -o json |lintly --format=terrascan + $ terrascan scan -d path/to/terraform/file -o json | lintly --format=terrascan ``` - [trivy](https://github.com/aquasecurity/trivy) ``` - $ trivy --quiet fs -f json path/to/directory/ |lintly --format=trivy + $ trivy --quiet fs -f json path/to/directory/ | lintly --format=trivy + ``` + +- [tfsec](https://github.com/tfsec/tfsec) + ``` + $ tfsec path/to/directory/ -f json | lintly --format=tfsec ``` - [cfn-lint](https://github.com/aws-cloudformation/cfn-python-lint) @@ -123,7 +128,8 @@ Options: (required) --commit-sha TEXT The commit Lintly is running against (required) - --format [unix|flake8|pylint-json|eslint|eslint-unix|stylelint|black|cfn-lint|cfn-nag|bandit-json|gitleaks|hadolint|terrascan|trivy] + --format [unix|flake8|pylint-json|eslint|eslint-unix|stylelint|black|cfn-lint| + cfn-nag|bandit-json|gitleaks|hadolint|terrascan|trivy|tfsec] The linting output format Lintly should expect to receive. Default "flake8" --context TEXT Override the commit status context diff --git a/lintly/parsers.py b/lintly/parsers.py index fbce593..33afede 100644 --- a/lintly/parsers.py +++ b/lintly/parsers.py @@ -527,6 +527,54 @@ def parse_violations(self, output): return violations +class TfsecParser(BaseLintParser): + """ + Tfsec JSON format + { + "results": [ + { + "rule_id": "AWS025", + "rule_description": "API Gateway domain name uses outdated SSL/TLS protocols.", + "rule_provider": "aws", + "link": "See https://tfsec.dev/docs/aws/AWS025/ for more information.", + "location": { + "filename": "path/to/file", + "start_line": 29, + "end_line": 29 + }, + "description": "aws_api_gateway_domain_name.empty_security_policy defines outdated SSL/TLS).", + "severity": "ERROR", + "passed": false + } + ] + } + + """ + + def parse_violations(self, output): + if not output: + return dict() + + json_data = json.loads(output) + + violations = collections.defaultdict(list) + + for violation_json in json_data["results"]: + violation = Violation( + line=violation_json["location"]["start_line"], + column=0, + code="{} ({})".format( + violation_json["rule_id"], violation_json["rule_description"] + ), + message=violation_json["description"], + ) + + path = self._normalize_path(violation_json["location"]["filename"]) + violations[path].append(violation) + + return violations + + DEFAULT_PARSER = LineRegexParser(r'^(?P.*):(?P\d+):(?P\d+): (?P\w\d+) (?P.*)$') @@ -579,4 +627,7 @@ def parse_violations(self, output): # trivy JSON output "trivy": TrivyParser(), + + # tfsec JSON output + "tfsec": TfsecParser(), } diff --git a/setup.py b/setup.py index cebbc07..fe86402 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ def read(*parts): setup( name='ttam-lintly', - version='0.6.11', + version='0.6.13', url='https://github.com/23andMe/Lintly', license='MIT', author='Veda Nandusekar', diff --git a/test.tf b/test.tf new file mode 100644 index 0000000..a422f50 --- /dev/null +++ b/test.tf @@ -0,0 +1,38 @@ +resource "aws_security_group_rule" "my-rule" { + type = "ingress" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_alb_listener" "my-alb-listener"{ + port = "80" + protocol = "HTTP" +} + +resource "aws_db_security_group" "my-group" { + +} + +variable "enableEncryption" { + default = false +} + +resource "azurerm_managed_disk" "source" { + encryption_settings { + enabled = var.enableEncryption + } +} + +resource "aws_api_gateway_domain_name" "missing_security_policy" { +} + +resource "aws_api_gateway_domain_name" "empty_security_policy" { + security_policy = "" +} + +resource "aws_api_gateway_domain_name" "outdated_security_policy" { + security_policy = "TLS_1_0" +} + +resource "aws_api_gateway_domain_name" "valid_security_policy" { + security_policy = "TLS_1_2" +} diff --git a/tests/linters_output/tfsec.json b/tests/linters_output/tfsec.json new file mode 100644 index 0000000..42b9f35 --- /dev/null +++ b/tests/linters_output/tfsec.json @@ -0,0 +1,32 @@ +{ + "results": [ + { + "rule_id": "AWS004", + "rule_description": "Use of plain HTTP.", + "rule_provider": "aws", + "link": "See https://tfsec.dev/docs/aws/AWS004/ for more information.", + "location": { + "filename": "relative/path/to/file1", + "start_line": 8, + "end_line": 8 + }, + "description": "Resource aws_alb_listener.my-alb-listener uses plain HTTP instead of HTTPS.", + "severity": "ERROR", + "passed": false + }, + { + "rule_id": "AZU003", + "rule_description": "Unencrypted managed disk.", + "rule_provider": "azure", + "link": "See https://tfsec.dev/docs/azure/AZU003/ for more information.", + "location": { + "filename": "relative/path/to/file2", + "start_line": 21, + "end_line": 21 + }, + "description": "Resource azurerm_managed_disk.source defines an unencrypted managed disk.", + "severity": "ERROR", + "passed": false + } + ] +} diff --git a/tests/test_parsers.py b/tests/test_parsers.py index c3e7093..b330f52 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -186,6 +186,21 @@ class TrivyParserTestCase(ParserTestCaseMixin, unittest.TestCase): } +class TfsecParserTestCase(ParserTestCaseMixin, unittest.TestCase): + parser = PARSERS['tfsec'] + linter_output_file_name = 'tfsec.json' + expected_violations = { + 'relative/path/to/file1': [ + {'line': 8, 'column': 0, 'code': 'AWS004 (Use of plain HTTP.)', + 'message': 'Resource aws_alb_listener.my-alb-listener uses plain HTTP instead of HTTPS.'} + ], + 'relative/path/to/file2': [ + {'line': 21, 'column': 0, 'code': 'AZU003 (Unencrypted managed disk.)', + 'message': 'Resource azurerm_managed_disk.source defines an unencrypted managed disk.'} + ] + } + + class PylintJSONParserTestCase(ParserTestCaseMixin, unittest.TestCase): parser = PARSERS['pylint-json'] linter_output_file_name = 'pylint-json.txt'