Last active
February 3, 2022 13:09
-
-
Save cmur2/247f0b09b050ec34151f1baed2dfae2d to your computer and use it in GitHub Desktop.
Automatically add TFSec ignore comments to onboard new Terraform code bases gracefully.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# A script that processes TFSec JSON output (via stdin) and automatically adds | |
# TFSec ignore comments to all affected locations. This allows to onboard a new | |
# Terraform code base to TFSec gracefully: | |
# | |
# 1. run tfsec-ignore.py to quickly add ignore comments for existing problems | |
# 2. integrate TFSec into your CI pipeline to avoid introducing new problems | |
# 3. over time fix existing problems in the code base | |
# | |
# With tfsec-ignore.py the TFSec problems become discoverable by searching for | |
# the ignore comments in the code base. | |
# | |
# Usage: | |
# tfsec --config-file your-tfsec.yml --format json | python3 tfsec-ignore.py | |
# | |
# Warning: This script may have bugs so keep a backup of your code base. | |
# | |
# See: https://aquasecurity.github.io/tfsec/v1.0.0/getting-started/configuration/ignores/ | |
import json | |
import sys | |
IGNORE_COMMENT_START = '#tfsec:ignore:' | |
def generate_ignore_comment(line_entry: dict) -> str: | |
comment = '' | |
for tfsec_rule_long_id, _ in line_entry.items(): | |
if len(comment) > 0: | |
comment += ' ' | |
comment += f'tfsec:ignore:{tfsec_rule_long_id}' | |
return comment | |
def generate_file_db(tfsec_results: dict) -> dict: | |
file_db = {} | |
for result in tfsec_results: | |
file_entry = file_db.get(result['location']['filename'], {}) | |
line_entry = file_entry.get(result['location']['start_line'], {}) | |
line_entry[result['long_id']] = True | |
file_entry[result['location']['start_line']] = line_entry | |
file_db[result['location']['filename']] = file_entry | |
return file_db | |
def main(): | |
results = json.load(sys.stdin).get('results', None) | |
if results is None: | |
print('No results to ignore so nothing to do, exiting') | |
sys.exit(0) | |
file_db = generate_file_db(results) | |
# print(json.dumps(file_db, indent=2)) | |
for file_name, file_entry in file_db.items(): | |
# by sorting the line DB by line number in ascending order and iterating | |
# over it, we can keep track of inserted lines via a single offset variable | |
sorted_line_db = dict(sorted(file_entry.items())) | |
offset = 0 | |
with open(file_name, 'r') as tf_file: | |
lines = tf_file.read().split('\n') | |
for line_number, line_entry in sorted_line_db.items(): | |
# accounts for offset (due to inserted lines) and 1-based counting | |
effective_line_number = line_number - 1 + offset | |
line_indent = len(lines[effective_line_number]) - len(lines[effective_line_number].lstrip()) | |
# we need to check whether the line before the problem already includes | |
# an ignore comment for TFSec and then either extend it or add a new | |
# ignore comment line | |
if lines[effective_line_number - 1].lstrip().startswith(IGNORE_COMMENT_START): | |
print(f'Extending existing ignore comment for {file_name}:{line_number}') | |
lines[effective_line_number - 1] += ' ' + generate_ignore_comment(line_entry) | |
else: | |
print(f'Adding new ignore comment for {file_name}:{line_number}') | |
new_line = ' ' * line_indent + '#' + generate_ignore_comment(line_entry) | |
lines.insert(effective_line_number, new_line) | |
offset += 1 | |
with open(file_name, 'w') as tf_file: | |
tf_file.write('\n'.join(lines)) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment