Skip to content

Instantly share code, notes, and snippets.

@cmur2
Last active February 3, 2022 13:09
Show Gist options
  • Save cmur2/247f0b09b050ec34151f1baed2dfae2d to your computer and use it in GitHub Desktop.
Save cmur2/247f0b09b050ec34151f1baed2dfae2d to your computer and use it in GitHub Desktop.
Automatically add TFSec ignore comments to onboard new Terraform code bases gracefully.
#!/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