|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +# This script scans Terraform files (.tf) to ensure that any outputs |
| 4 | +# with potentially sensitive names are explicitly marked as 'sensitive = true'. |
| 5 | +# |
| 6 | +# It exits with status 0 if no issues are found. |
| 7 | +# It exits with status 1 if 'hcledit' is not installed or if any sensitive |
| 8 | +# outputs are found that are not properly marked. |
| 9 | +# |
| 10 | +# If files are passed to the script `./tf-check-sensitive-output.sh [FILE...]` |
| 11 | +# then only those files are checked. If no files are passed in all *.tf files are checked. |
| 12 | +# |
| 13 | +# False positives can be ignored (a WARNING will be reported) by setting "sensitive = false" |
| 14 | + |
| 15 | +# Bash strict mode |
| 16 | +set -euo pipefail |
| 17 | +IFS=$'\n\t' |
| 18 | + |
| 19 | +# --- Main Script --- |
| 20 | + |
| 21 | +main() { |
| 22 | + # Check for the 'hcledit' dependency. |
| 23 | + if ! command -v hcledit &>/dev/null; then |
| 24 | + echo "ERROR: hcledit is not installed. Please install it to run this script." >&2 |
| 25 | + echo "Installation instructions: https://github.com/minamijoyo/hcledit" >&2 |
| 26 | + echo "Homebrew install: \`brew install minamijoyo/hcledit/hcledit\`" >&2 |
| 27 | + exit 1 |
| 28 | + fi |
| 29 | + |
| 30 | + local error_found=0 |
| 31 | + # Regex pattern for names that suggest sensitive content. |
| 32 | + # This is case-insensitive. |
| 33 | + local sensitive_pattern='pass|password|secret|key|private|token|cred|credential|conn_str' |
| 34 | + |
| 35 | + # --- Helper function to process a single file --- |
| 36 | + process_file() { |
| 37 | + local file=$1 |
| 38 | + |
| 39 | + # When used with pre-commit, non-.tf files might be passed. Skip them. |
| 40 | + if ! [[ "$file" == *.tf ]]; then |
| 41 | + return |
| 42 | + fi |
| 43 | + |
| 44 | + # Get all output block addresses (e.g., "output.my_secret") from the current file. |
| 45 | + # The '|| true' prevents the script from exiting if grep finds no matches. |
| 46 | + local sensitive_outputs |
| 47 | + sensitive_outputs=$(hcledit block list -f "$file" | grep -E '^output\.' | grep -iE "$sensitive_pattern" || true) |
| 48 | + |
| 49 | + # If no sensitive outputs were found in this file, continue. |
| 50 | + if [[ -z "$sensitive_outputs" ]]; then |
| 51 | + return |
| 52 | + fi |
| 53 | + |
| 54 | + # For each potentially sensitive output, check if it's marked as sensitive. |
| 55 | + while IFS= read -r output_address; do |
| 56 | + # Get the value of the 'sensitive' attribute for the output block. |
| 57 | + local is_sensitive |
| 58 | + is_sensitive=$(hcledit attribute get "${output_address}.sensitive" -f "$file" 2>/dev/null || true) |
| 59 | + |
| 60 | + # Check the status of the 'sensitive' attribute and act accordingly. |
| 61 | + if [[ "$is_sensitive" == "true" ]]; then |
| 62 | + # Correct: The output is properly marked as sensitive. |
| 63 | + : |
| 64 | + elif [[ "$is_sensitive" == "false" ]]; then |
| 65 | + # Warn: The developer has explicitly marked this as not sensitive. |
| 66 | + echo "WARNING: Output '$output_address' in file '$file' is explicitly marked 'sensitive = false'. Please double-check this is intended." >&2 |
| 67 | + else |
| 68 | + # Error: The 'sensitive' attribute is missing entirely. |
| 69 | + echo "ERROR: Output '$output_address' in file '$file' appears to be sensitive but is missing the 'sensitive = true' attribute. Set 'sensitive = false' to ignore." >&2 |
| 70 | + error_found=1 # Modifies 'error_found' in the parent 'main' scope. |
| 71 | + fi |
| 72 | + done <<<"$sensitive_outputs" |
| 73 | + } |
| 74 | + |
| 75 | + # --- Execution Logic --- |
| 76 | + |
| 77 | + if [ "$#" -gt 0 ]; then |
| 78 | + # Case 1: Files are passed as arguments (pre-commit use case). |
| 79 | + echo "Scanning provided files..." |
| 80 | + for file in "$@"; do |
| 81 | + process_file "$file" |
| 82 | + done |
| 83 | + else |
| 84 | + # Case 2: No arguments. Find all '.tf' files to scan. |
| 85 | + echo "Scanning for sensitive outputs in all .tf files..." |
| 86 | + while IFS= read -r file; do |
| 87 | + process_file "$file" |
| 88 | + done < <(find . -type f -name "*.tf" -not -path "*.terraform*") |
| 89 | + fi |
| 90 | + |
| 91 | + # After checking all files, exit with the appropriate status code. |
| 92 | + if [[ "$error_found" -ne 0 ]]; then |
| 93 | + echo "" |
| 94 | + echo "Scan complete. Found sensitive outputs that are not correctly marked." >&2 |
| 95 | + exit 1 |
| 96 | + fi |
| 97 | + |
| 98 | + echo "Scan complete. No issues found." |
| 99 | + exit 0 |
| 100 | +} |
| 101 | + |
| 102 | +# Execute the main function, passing all script arguments to it. |
| 103 | +main "$@" |
0 commit comments