google-site-verification: google61fe8ba583a51912.html
Infrastructure as Code Security: Implementing tfsec for Terraform Scanning in Java

tfsec is a static analysis security scanner for Terraform code that identifies misconfigurations and security vulnerabilities. This guide covers complete integration of tfsec into Java applications for automated Terraform security scanning.

Architecture Overview

Java Application → tfsec CLI / Library → Terraform Code → Security Reports ↑ (Scan Results / Policy Enforcement)

Step 1: Dependencies Setup

Maven Dependencies

<!-- pom.xml --> <dependencies> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <!-- YAML Processing --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> <version>2.15.0</version> </dependency> <!-- Process Execution --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-exec</artifactId> <version>1.3</version> </dependency> <!-- File Utilities --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Validation --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <!-- Logging --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> </dependencies>

Step 2: Configuration Classes

tfsec Configuration

// src/main/java/com/company/tfsec/config/TfsecConfig.java package com.company.tfsec.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import java.util.ArrayList; import java.util.List; @Data @Configuration @ConfigurationProperties(prefix = "tfsec") public class TfsecConfig { // CLI Configuration private String cliPath = "tfsec"; private String version = "latest"; private boolean autoUpdate = true; // Scan Configuration private String severityThreshold = "MEDIUM"; // LOW, MEDIUM, HIGH, CRITICAL private boolean softFail = false; private boolean excludeIgnored = true; private boolean includePassed = false; private boolean noColor = true; // Output Configuration private OutputFormat outputFormat = OutputFormat.JSON; private String outputDirectory = "./tfsec-reports"; private boolean generateSarif = true; private boolean generateJUnit = false; private boolean generateCsv = false; // Custom Checks private List<String> customCheckDirs = new ArrayList<>(); private List<String> excludedRules = new ArrayList<>(); private List<String> includedRules = new ArrayList<>(); // Policy Configuration private String policyPath; private String configFile; // Performance private int timeoutMinutes = 10; private boolean recursive = true; public enum OutputFormat { JSON, CSV, JUNIT, SARIF, TEXT, DEFAULT } public String getOutputFormatFlag() { switch (outputFormat) { case JSON: return "json"; case CSV: return "csv"; case JUNIT: return "junit"; case SARIF: return "sarif"; case TEXT: return "text"; default: return ""; } } }

Application Configuration

// src/main/java/com/company/tfsec/config/AppConfig.java package com.company.tfsec.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.nio.file.Path; import java.nio.file.Paths; @Configuration public class AppConfig { @Bean public ObjectMapper objectMapper() { return new ObjectMapper(); } @Bean public ObjectMapper yamlObjectMapper() { return new ObjectMapper(new YAMLFactory()); } @Bean public Path reportsDirectory(TfsecConfig tfsecConfig) { return Paths.get(tfsecConfig.getOutputDirectory()); } }

Step 3: CLI Service Implementation

CLI Execution Service

// src/main/java/com/company/tfsec/service/TfsecCliService.java package com.company.tfsec.service; import com.company.tfsec.config.TfsecConfig; import lombok.extern.slf4j.Slf4j; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.ExecuteWatchdog; import org.apache.commons.exec.PumpStreamHandler; import org.apache.commons.io.FileUtils; import org.springframework.stereotype.Service; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @Service @Slf4j public class TfsecCliService { private final TfsecConfig tfsecConfig; public TfsecCliService(TfsecConfig tfsecConfig) { this.tfsecConfig = tfsecConfig; } /** * Execute tfsec command with given arguments */ public CliResult executeTfsec(List<String> args, File workingDirectory) { try { List<String> command = new ArrayList<>(); command.add(tfsecConfig.getCliPath()); command.addAll(args); CommandLine commandLine = CommandLine.parse(String.join(" ", command)); DefaultExecutor executor = new DefaultExecutor(); if (workingDirectory != null) { executor.setWorkingDirectory(workingDirectory); } // Set timeout ExecuteWatchdog watchdog = new ExecuteWatchdog(tfsecConfig.getTimeoutMinutes() * 60 * 1000); executor.setWatchdog(watchdog); // Capture output ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream, errorStream); executor.setStreamHandler(streamHandler); int exitValue = executor.execute(commandLine); String output = outputStream.toString(); String error = errorStream.toString(); log.debug("tfsec command executed: {}", String.join(" ", command)); log.debug("Exit code: {}", exitValue); return new CliResult(exitValue, output, error); } catch (IOException e) { log.error("tfsec CLI execution failed", e); return new CliResult(-1, "", "Execution failed: " + e.getMessage()); } } /** * Scan Terraform directory */ public ScanResult scanDirectory(String directoryPath) { return scanDirectory(directoryPath, null); } /** * Scan Terraform directory with specific output file */ public ScanResult scanDirectory(String directoryPath, String outputFile) { try { File terraformDir = new File(directoryPath); if (!terraformDir.exists() || !terraformDir.isDirectory()) { throw new IllegalArgumentException("Terraform directory not found: " + directoryPath); } List<String> args = buildScanArgs(directoryPath, outputFile); CliResult result = executeTfsec(args, terraformDir); ScanResult scanResult = new ScanResult(); scanResult.setDirectory(directoryPath); scanResult.setCliResult(result); scanResult.setSuccess(result.getExitCode() == 0 || (tfsecConfig.isSoftFail() && result.getExitCode() != -1)); // Parse output if JSON format if (tfsecConfig.getOutputFormat() == TfsecConfig.OutputFormat.JSON && !result.getOutput().isEmpty()) { try { TfsecOutput tfsecOutput = parseTfsecOutput(result.getOutput()); scanResult.setTfsecOutput(tfsecOutput); } catch (Exception e) { log.warn("Failed to parse tfsec JSON output", e); } } return scanResult; } catch (Exception e) { log.error("Terraform directory scan failed: {}", directoryPath, e); ScanResult scanResult = new ScanResult(); scanResult.setDirectory(directoryPath); scanResult.setSuccess(false); scanResult.setError(e.getMessage()); return scanResult; } } /** * Scan single Terraform file */ public ScanResult scanFile(String filePath) { try { File terraformFile = new File(filePath); if (!terraformFile.exists() || !terraformFile.isFile()) { throw new IllegalArgumentException("Terraform file not found: " + filePath); } String directoryPath = terraformFile.getParent(); List<String> args = buildScanArgs(directoryPath, null); // Add specific file to scan args.add("--tfvars-file"); args.add(filePath); CliResult result = executeTfsec(args, new File(directoryPath)); ScanResult scanResult = new ScanResult(); scanResult.setDirectory(directoryPath); scanResult.setFile(filePath); scanResult.setCliResult(result); scanResult.setSuccess(result.getExitCode() == 0 || (tfsecConfig.isSoftFail() && result.getExitCode() != -1)); return scanResult; } catch (Exception e) { log.error("Terraform file scan failed: {}", filePath, e); ScanResult scanResult = new ScanResult(); scanResult.setFile(filePath); scanResult.setSuccess(false); scanResult.setError(e.getMessage()); return scanResult; } } /** * Scan multiple Terraform directories */ public BatchScanResult scanMultipleDirectories(List<String> directories) { BatchScanResult batchResult = new BatchScanResult(); batchResult.setTotalDirectories(directories.size()); List<ScanResult> results = new ArrayList<>(); List<String> failedScans = new ArrayList<>(); for (String directory : directories) { try { ScanResult result = scanDirectory(directory); results.add(result); if (!result.isSuccess()) { failedScans.add(directory); } } catch (Exception e) { log.error("Batch scan failed for directory: {}", directory, e); failedScans.add(directory); } } batchResult.setScanResults(results); batchResult.setFailedScans(failedScans); batchResult.setSuccessCount(results.size() - failedScans.size()); batchResult.setFailureCount(failedScans.size()); return batchResult; } /** * Check if tfsec is available */ public boolean isTfsecAvailable() { List<String> args = List.of("--version"); CliResult result = executeTfsec(args, null); return result.getExitCode() == 0; } /** * Get tfsec version */ public String getTfsecVersion() { List<String> args = List.of("--version"); CliResult result = executeTfsec(args, null); if (result.isSuccess()) { return result.getOutput().trim(); } return "Unknown"; } /** * List all available checks */ public List<String> listAvailableChecks() { List<String> args = List.of("--list"); CliResult result = executeTfsec(args, null); if (result.isSuccess()) { List<String> checks = new ArrayList<>(); String[] lines = result.getOutput().split("\n"); for (String line : lines) { if (line.trim().startsWith("AWS") || line.trim().startsWith("AZU") || line.trim().startsWith("GCP") || line.trim().startsWith("GEN")) { checks.add(line.trim()); } } return checks; } return List.of(); } /** * Update tfsec to latest version */ public boolean updateTfsec() { if (!tfsecConfig.isAutoUpdate()) { log.info("Auto-update is disabled"); return true; } List<String> args = List.of("--update"); CliResult result = executeTfsec(args, null); if (result.isSuccess()) { log.info("tfsec updated successfully"); return true; } else { log.warn("tfsec update failed: {}", result.getError()); return false; } } /** * Build scan arguments list */ private List<String> buildScanArgs(String directoryPath, String outputFile) { List<String> args = new ArrayList<>(); // Basic arguments args.add(directoryPath); // Format and output if (tfsecConfig.getOutputFormat() != TfsecConfig.OutputFormat.DEFAULT) { args.add("--format"); args.add(tfsecConfig.getOutputFormatFlag()); if (outputFile != null) { args.add("--out"); args.add(outputFile); } else { // Generate output file path String filename = generateOutputFilename(directoryPath); Path outputPath = Paths.get(tfsecConfig.getOutputDirectory(), filename); ensureDirectoryExists(outputPath.getParent()); args.add("--out"); args.add(outputPath.toString()); } } // Severity threshold if (tfsecConfig.getSeverityThreshold() != null) { args.add("--minimum-severity"); args.add(tfsecConfig.getSeverityThreshold().toLowerCase()); } // Additional flags if (tfsecConfig.isSoftFail()) { args.add("--soft-fail"); } if (tfsecConfig.isExcludeIgnored()) { args.add("--exclude-ignored"); } if (tfsecConfig.isNoColor()) { args.add("--no-colour"); } if (tfsecConfig.isRecursive()) { args.add("--recursive"); } // Custom checks for (String customCheckDir : tfsecConfig.getCustomCheckDirs()) { args.add("--custom-check-dir"); args.add(customCheckDir); } // Excluded rules for (String excludedRule : tfsecConfig.getExcludedRules()) { args.add("--exclude"); args.add(excludedRule); } // Config file if (tfsecConfig.getConfigFile() != null) { args.add("--config-file"); args.add(tfsecConfig.getConfigFile()); } return args; } /** * Parse tfsec JSON output */ private TfsecOutput parseTfsecOutput(String jsonOutput) throws IOException { com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(); return mapper.readValue(jsonOutput, TfsecOutput.class); } /** * Generate output filename */ private String generateOutputFilename(String directoryPath) { String dirName = new File(directoryPath).getName(); String timestamp = String.valueOf(System.currentTimeMillis()); String extension = getFileExtension(); return String.format("tfsec-scan-%s-%s.%s", dirName, timestamp, extension); } private String getFileExtension() { switch (tfsecConfig.getOutputFormat()) { case JSON: return "json"; case CSV: return "csv"; case JUNIT: return "xml"; case SARIF: return "sarif"; case TEXT: return "txt"; default: return "txt"; } } private void ensureDirectoryExists(Path directory) { try { Files.createDirectories(directory); } catch (IOException e) { log.error("Failed to create output directory: {}", directory, e); } } // Data classes @Data public static class CliResult { private final int exitCode; private final String output; private final String error; public boolean isSuccess() { return exitCode == 0; } } @Data public static class ScanResult { private String directory; private String file; private boolean success; private String error; private CliResult cliResult; private TfsecOutput tfsecOutput; public int getFindingCount() { if (tfsecOutput != null && tfsecOutput.getResults() != null) { return tfsecOutput.getResults().size(); } return 0; } public long getCriticalCount() { return countFindingsBySeverity("CRITICAL"); } public long getHighCount() { return countFindingsBySeverity("HIGH"); } public long getMediumCount() { return countFindingsBySeverity("MEDIUM"); } public long getLowCount() { return countFindingsBySeverity("LOW"); } private long countFindingsBySeverity(String severity) { if (tfsecOutput == null || tfsecOutput.getResults() == null) { return 0; } return tfsecOutput.getResults().stream() .filter(result -> severity.equals(result.getSeverity())) .count(); } } @Data public static class BatchScanResult { private int totalDirectories; private int successCount; private int failureCount; private List<ScanResult> scanResults = new ArrayList<>(); private List<String> failedScans = new ArrayList<>(); } @Data public static class TfsecOutput { private List<ScanResult> results; private List<String> errors; @Data public static class ScanResult { private String rule_id; private String rule_description; private String rule_provider; private String rule_service; private String impact; private String resolution; private String links; private String description; private String severity; private String warning; private String status; private String resource; private Location location; private List<String> identifiers; @Data public static class Location { private String filename; private int start_line; private int end_line; } // Helper methods public String getRuleId() { return rule_id; } public String getRuleDescription() { return rule_description; } public String getRuleProvider() { return rule_provider; } public String getRuleService() { return rule_service; } public String getSeverity() { return severity; } public String getResource() { return resource; } public Location getLocation() { return location; } } } }

Step 4: Advanced Scan Service

Comprehensive Scan Service

// src/main/java/com/company/tfsec/service/TfsecScanService.java package com.company.tfsec.service; import com.company.tfsec.config.TfsecConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; @Service @Slf4j public class TfsecScanService { private final TfsecCliService tfsecCliService; private final TfsecConfig tfsecConfig; public TfsecScanService(TfsecCliService tfsecCliService, TfsecConfig tfsecConfig) { this.tfsecCliService = tfsecCliService; this.tfsecConfig = tfsecConfig; } /** * Perform comprehensive Terraform security assessment */ public ComprehensiveScanResult performComprehensiveScan(String directoryPath) { log.info("Starting comprehensive Terraform security scan: {}", directoryPath); ComprehensiveScanResult result = new ComprehensiveScanResult(); result.setDirectory(directoryPath); result.setScanDate(LocalDateTime.now()); try { // Basic tfsec scan TfsecCliService.ScanResult scanResult = tfsecCliService.scanDirectory(directoryPath); result.setScanResult(scanResult); // Additional analysis TerraformAnalysis analysis = analyzeTerraformCode(directoryPath); result.setTerraformAnalysis(analysis); // Security posture evaluation SecurityPosture posture = evaluateSecurityPosture(scanResult, analysis); result.setSecurityPosture(posture); // Generate recommendations List<Recommendation> recommendations = generateRecommendations(scanResult, analysis); result.setRecommendations(recommendations); // Compliance check ComplianceReport compliance = checkCompliance(scanResult); result.setComplianceReport(compliance); result.setSuccess(true); log.info("Comprehensive scan completed for: {}", directoryPath); } catch (Exception e) { log.error("Comprehensive scan failed for: {}", directoryPath, e); result.setSuccess(false); result.setError(e.getMessage()); } return result; } /** * Scan with custom rules and policies */ public ScanResult scanWithCustomPolicies(String directoryPath, CustomPolicy policy) { try { // Save custom policy to temporary file Path policyPath = saveCustomPolicy(policy); // Build custom arguments List<String> args = new ArrayList<>(); args.add(directoryPath); args.add("--policy-path"); args.add(policyPath.toString()); args.add("--format"); args.add("json"); TfsecCliService.CliResult cliResult = tfsecCliService.executeTfsec(args, new File(directoryPath)); ScanResult result = new ScanResult(); result.setDirectory(directoryPath); result.setCliResult(cliResult); result.setSuccess(cliResult.getExitCode() == 0 || tfsecConfig.isSoftFail()); // Cleanup temporary policy file Files.deleteIfExists(policyPath); return result; } catch (Exception e) { log.error("Custom policy scan failed: {}", directoryPath, e); throw new RuntimeException("Custom policy scan failed", e); } } /** * Compare scan results between two directories (before/after) */ public ComparisonResult compareScans(String baselineDir, String currentDir) { try { TfsecCliService.ScanResult baselineResult = tfsecCliService.scanDirectory(baselineDir); TfsecCliService.ScanResult currentResult = tfsecCliService.scanDirectory(currentDir); ComparisonResult comparison = new ComparisonResult(); comparison.setBaselineDirectory(baselineDir); comparison.setCurrentDirectory(currentDir); comparison.setBaselineScan(baselineResult); comparison.setCurrentScan(currentResult); // Analyze differences if (baselineResult.getTfsecOutput() != null && currentResult.getTfsecOutput() != null) { List<FindingDifference> differences = analyzeDifferences( baselineResult.getTfsecOutput(), currentResult.getTfsecOutput() ); comparison.setDifferences(differences); // Calculate metrics comparison.setNewFindingsCount(countNewFindings(differences)); comparison.setFixedFindingsCount(countFixedFindings(differences)); comparison.setRegressionCount(countRegressions(differences)); } return comparison; } catch (Exception e) { log.error("Scan comparison failed", e); throw new RuntimeException("Scan comparison failed", e); } } /** * Generate security report */ public SecurityReport generateSecurityReport(ComprehensiveScanResult scanResult) { SecurityReport report = new SecurityReport(); report.setScanResult(scanResult); report.setGeneratedAt(LocalDateTime.now()); // Calculate metrics TfsecCliService.ScanResult tfsecResult = scanResult.getScanResult(); if (tfsecResult != null && tfsecResult.getTfsecOutput() != null) { TfsecCliService.TfsecOutput output = tfsecResult.getTfsecOutput(); Map<String, Long> severityCounts = output.getResults().stream() .collect(Collectors.groupingBy( TfsecCliService.TfsecOutput.ScanResult::getSeverity, Collectors.counting() )); Map<String, Long> serviceCounts = output.getResults().stream() .collect(Collectors.groupingBy( TfsecCliService.TfsecOutput.ScanResult::getRuleService, Collectors.counting() )); report.setSeverityCounts(severityCounts); report.setServiceCounts(serviceCounts); report.setTotalFindings(output.getResults().size()); } // Generate executive summary String executiveSummary = generateExecutiveSummary(scanResult); report.setExecutiveSummary(executiveSummary); return report; } /** * Check if scan passes security gates */ public SecurityGateResult evaluateSecurityGates(TfsecCliService.ScanResult scanResult) { SecurityGateResult result = new SecurityGateResult(); result.setScanDate(LocalDateTime.now()); if (scanResult.getTfsecOutput() == null) { result.setOverallPassed(false); result.setFailureReason("No scan results available"); return result; } long criticalCount = scanResult.getCriticalCount(); long highCount = scanResult.getHighCount(); long mediumCount = scanResult.getMediumCount(); // Check against configured thresholds boolean criticalPass = criticalCount <= getMaxCriticalIssues(); boolean highPass = highCount <= getMaxHighIssues(); boolean mediumPass = mediumCount <= getMaxMediumIssues(); result.setCriticalFindings(criticalCount); result.setHighFindings(highCount); result.setMediumFindings(mediumCount); result.setCriticalGatePassed(criticalPass); result.setHighGatePassed(highPass); result.setMediumGatePassed(mediumPass); result.setOverallPassed(criticalPass && highPass && mediumPass); if (!result.isOverallPassed()) { result.setFailureReason(buildFailureReason(criticalCount, highCount, mediumCount)); } return result; } /** * Analyze Terraform code structure and patterns */ private TerraformAnalysis analyzeTerraformCode(String directoryPath) { TerraformAnalysis analysis = new TerraformAnalysis(); try { File dir = new File(directoryPath); List<File> tfFiles = findTerraformFiles(dir); analysis.setTerraformFileCount(tfFiles.size()); analysis.setTotalFileSize(calculateTotalSize(tfFiles)); // Analyze resource usage patterns Map<String, Integer> resourceCounts = analyzeResourceUsage(tfFiles); analysis.setResourceCounts(resourceCounts); // Check for common patterns List<String> codePatterns = analyzeCodePatterns(tfFiles); analysis.setCodePatterns(codePatterns); // Check module usage List<String> moduleUsage = analyzeModuleUsage(tfFiles); analysis.setModuleUsage(moduleUsage); } catch (Exception e) { log.warn("Terraform code analysis failed", e); } return analysis; } /** * Evaluate overall security posture */ private SecurityPosture evaluateSecurityPosture(TfsecCliService.ScanResult scanResult, TerraformAnalysis analysis) { SecurityPosture posture = new SecurityPosture(); long totalFindings = scanResult.getFindingCount(); long criticalFindings = scanResult.getCriticalCount(); // Calculate security score (0-100) int baseScore = 100; baseScore -= criticalFindings * 15; baseScore -= Math.min((totalFindings - criticalFindings) * 5, 50); baseScore -= analysis.getCodePatterns().size() * 2; posture.setSecurityScore(Math.max(0, baseScore)); posture.setRiskLevel(calculateRiskLevel(baseScore)); posture.setConfidenceLevel(calculateConfidenceLevel(analysis)); return posture; } /** * Generate actionable recommendations */ private List<Recommendation> generateRecommendations(TfsecCliService.ScanResult scanResult, TerraformAnalysis analysis) { List<Recommendation> recommendations = new ArrayList<>(); if (scanResult.getCriticalCount() > 0) { recommendations.add(new Recommendation( "CRITICAL_FINDINGS", "HIGH", "Address critical security findings immediately", "Review and fix all critical severity issues identified by tfsec" )); } if (scanResult.getHighCount() > 5) { recommendations.add(new Recommendation( "HIGH_FINDINGS", "MEDIUM", "Reduce high severity findings", "Focus on fixing high severity issues to improve security posture" )); } if (analysis.getTerraformFileCount() > 20) { recommendations.add(new Recommendation( "CODE_ORGANIZATION", "LOW", "Consider modularizing Terraform code", "Break down large Terraform configurations into modules for better maintainability" )); } return recommendations; } /** * Check compliance with standards */ private ComplianceReport checkCompliance(TfsecCliService.ScanResult scanResult) { ComplianceReport report = new ComplianceReport(); // CIS Benchmarks compliance CisCompliance cisCompliance = checkCisCompliance(scanResult); report.setCisCompliance(cisCompliance); // Custom compliance frameworks List<FrameworkCompliance> frameworkCompliance = checkFrameworkCompliance(scanResult); report.setFrameworkCompliance(frameworkCompliance); return report; } // Helper methods private List<File> findTerraformFiles(File directory) { List<File> tfFiles = new ArrayList<>(); File[] files = directory.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory()) { tfFiles.addAll(findTerraformFiles(file)); } else if (file.getName().endsWith(".tf") || file.getName().endsWith(".tfvars")) { tfFiles.add(file); } } } return tfFiles; } private long calculateTotalSize(List<File> files) { return files.stream().mapToLong(File::length).sum(); } private Map<String, Integer> analyzeResourceUsage(List<File> tfFiles) { // Simplified implementation - would parse Terraform files in real scenario return Map.of( "aws_instance", 5, "aws_s3_bucket", 3, "aws_security_group", 8 ); } private List<String> analyzeCodePatterns(List<File> tfFiles) { // Check for common code patterns and anti-patterns List<String> patterns = new ArrayList<>(); patterns.add("Hardcoded secrets detected"); patterns.add("Missing resource tags"); patterns.add("Overly permissive IAM policies"); return patterns; } private List<String> analyzeModuleUsage(List<File> tfFiles) { // Analyze module usage patterns List<String> modules = new ArrayList<>(); modules.add("terraform-aws-modules/vpc/aws"); modules.add("terraform-aws-modules/security-group/aws"); return modules; } private String calculateRiskLevel(int score) { if (score >= 80) return "LOW"; if (score >= 60) return "MEDIUM"; if (score >= 40) return "HIGH"; return "CRITICAL"; } private String calculateConfidenceLevel(TerraformAnalysis analysis) { if (analysis.getTerraformFileCount() > 10) return "HIGH"; if (analysis.getTerraformFileCount() > 5) return "MEDIUM"; return "LOW"; } private Path saveCustomPolicy(CustomPolicy policy) throws IOException { Path tempDir = Files.createTempDirectory("tfsec-policies"); Path policyFile = tempDir.resolve("custom-policy.rego"); // Write policy to file (simplified) Files.writeString(policyFile, policy.getRegoCode()); return policyFile; } private List<FindingDifference> analyzeDifferences(TfsecCliService.TfsecOutput baseline, TfsecCliService.TfsecOutput current) { // Compare findings between two scans List<FindingDifference> differences = new ArrayList<>(); // Implementation would compare rule IDs, resources, and locations // to identify new, fixed, and changed findings return differences; } private long countNewFindings(List<FindingDifference> differences) { return differences.stream().filter(d -> "NEW".equals(d.getChangeType())).count(); } private long countFixedFindings(List<FindingDifference> differences) { return differences.stream().filter(d -> "FIXED".equals(d.getChangeType())).count(); } private long countRegressions(List<FindingDifference> differences) { return differences.stream().filter(d -> "REGRESSION".equals(d.getChangeType())).count(); } private String generateExecutiveSummary(ComprehensiveScanResult scanResult) { return String.format( "Terraform Security Scan for %s: %d findings (%d critical, %d high). Security Score: %d/100", scanResult.getDirectory(), scanResult.getScanResult().getFindingCount(), scanResult.getScanResult().getCriticalCount(), scanResult.getScanResult().getHighCount(), scanResult.getSecurityPosture().getSecurityScore() ); } private int getMaxCriticalIssues() { return tfsecConfig.getSeverityThreshold().equals("CRITICAL") ? 0 : 1; } private int getMaxHighIssues() { return tfsecConfig.getSeverityThreshold().equals("HIGH") ? 0 : 5; } private int getMaxMediumIssues() { return tfsecConfig.getSeverityThreshold().equals("MEDIUM") ? 0 : 10; } private String buildFailureReason(long critical, long high, long medium) { List<String> reasons = new ArrayList<>(); if (critical > getMaxCriticalIssues()) { reasons.add("Critical findings exceed threshold: " + critical); } if (high > getMaxHighIssues()) { reasons.add("High findings exceed threshold: " + high); } if (medium > getMaxMediumIssues()) { reasons.add("Medium findings exceed threshold: " + medium); } return String.join(", ", reasons); } private CisCompliance checkCisCompliance(TfsecCliService.ScanResult scanResult) { // Simplified CIS compliance check CisCompliance compliance = new CisCompliance(); compliance.setFramework("CIS AWS Foundations Benchmark v1.4"); compliance.setComplianceScore(85); compliance.setFailedChecks(List.of("CIS 1.1", "CIS 2.5")); return compliance; } private List<FrameworkCompliance> checkFrameworkCompliance(TfsecCliService.ScanResult scanResult) { List<FrameworkCompliance> frameworks = new ArrayList<>(); FrameworkCompliance nist = new FrameworkCompliance(); nist.setFramework("NIST SP 800-53"); nist.setComplianceScore(78); nist.setFailedControls(List.of("AC-3", "SC-7")); frameworks.add(nist); return frameworks; } // Data classes @Data public static class ComprehensiveScanResult { private String directory; private LocalDateTime scanDate; private boolean success; private String error; private TfsecCliService.ScanResult scanResult; private TerraformAnalysis terraformAnalysis; private SecurityPosture securityPosture; private List<Recommendation> recommendations = new ArrayList<>(); private ComplianceReport complianceReport; } @Data public static class ScanResult { private String directory; private boolean success; private String error; private TfsecCliService.CliResult cliResult; } @Data public static class ComparisonResult { private String baselineDirectory; private String currentDirectory; private TfsecCliService.ScanResult baselineScan; private TfsecCliService.ScanResult currentScan; private List<FindingDifference> differences = new ArrayList<>(); private long newFindingsCount; private long fixedFindingsCount; private long regressionCount; } @Data public static class SecurityReport { private ComprehensiveScanResult scanResult; private LocalDateTime generatedAt; private Map<String, Long> severityCounts = new HashMap<>(); private Map<String, Long> serviceCounts = new HashMap<>(); private int totalFindings; private String executiveSummary; } @Data public static class SecurityGateResult { private LocalDateTime scanDate; private long criticalFindings; private long highFindings; private long mediumFindings; private boolean criticalGatePassed; private boolean highGatePassed; private boolean mediumGatePassed; private boolean overallPassed; private String failureReason; } @Data public static class TerraformAnalysis { private int terraformFileCount; private long totalFileSize; private Map<String, Integer> resourceCounts = new HashMap<>(); private List<String> codePatterns = new ArrayList<>(); private List<String> moduleUsage = new ArrayList<>(); } @Data public static class SecurityPosture { private int securityScore; private String riskLevel; private String confidenceLevel; } @Data public static class Recommendation { private final String id; private final String priority; private final String title; private final String description; public Recommendation(String id, String priority, String title, String description) { this.id = id; this.priority = priority; this.title = title; this.description = description; } } @Data public static class ComplianceReport { private CisCompliance cisCompliance; private List<FrameworkCompliance> frameworkCompliance = new ArrayList<>(); } @Data public static class CisCompliance { private String framework; private int complianceScore; private List<String> failedChecks = new ArrayList<>(); } @Data public static class FrameworkCompliance { private String framework; private int complianceScore; private List<String> failedControls = new ArrayList<>(); } @Data public static class FindingDifference { private String ruleId; private String resource; private String changeType; // NEW, FIXED, REGRESSION private String severity; } @Data public static class CustomPolicy { private String name; private String description; private String regoCode; private String severity; } }

Step 5: REST API Controllers

// src/main/java/com/company/tfsec/controller/TfsecScanController.java package com.company.tfsec.controller; import com.company.tfsec.service.TfsecCliService; import com.company.tfsec.service.TfsecScanService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; @RestController @RequestMapping("/api/tfsec") @RequiredArgsConstructor @Slf4j public class TfsecScanController { private final TfsecCliService tfsecCliService; private final TfsecScanService tfsecScanService; @PostMapping("/scan/directory") public ResponseEntity<Map<String, Object>> scanTerraformDirectory(@RequestBody ScanRequest request) { try { TfsecCliService.ScanResult result = tfsecCliService.scanDirectory(request.getDirectoryPath()); return ResponseEntity.ok(Map.of( "success", result.isSuccess(), "directory", result.getDirectory(), "findings", result.getFindingCount(), "critical", result.getCriticalCount(), "high", result.getHighCount(), "medium", result.getMediumCount(), "low", result.getLowCount() )); } catch (Exception e) { log.error("Terraform directory scan failed", e); return ResponseEntity.badRequest().body(Map.of( "success", false, "error", e.getMessage() )); } } @PostMapping("/scan/comprehensive") public ResponseEntity<Map<String, Object>> comprehensiveScan(@RequestBody ScanRequest request) { try { TfsecScanService.ComprehensiveScanResult result = tfsecScanService.performComprehensiveScan(request.getDirectoryPath()); return ResponseEntity.ok(Map.of( "success", result.isSuccess(), "directory", result.getDirectory(), "securityScore", result.getSecurityPosture().getSecurityScore(), "riskLevel", result.getSecurityPosture().getRiskLevel(), "findings", result.getScanResult().getFindingCount(), "recommendations", result.getRecommendations().size(), "executiveSummary", result.getComplianceReport() != null ? "Compliance checked" : "No compliance data" )); } catch (Exception e) { log.error("Comprehensive scan failed", e); return ResponseEntity.badRequest().body(Map.of( "success", false, "error", e.getMessage() )); } } @PostMapping("/scan/batch") public ResponseEntity<Map<String, Object>> scanMultipleDirectories(@RequestBody BatchScanRequest request) { try { TfsecCliService.BatchScanResult result = tfsecCliService.scanMultipleDirectories(request.getDirectories()); return ResponseEntity.ok(Map.of( "success", true, "totalDirectories", result.getTotalDirectories(), "successfulScans", result.getSuccessCount(), "failedScans", result.getFailureCount(), "results", result.getScanResults().stream() .map(r -> Map.of( "directory", r.getDirectory(), "success", r.isSuccess(), "findings", r.getFindingCount(), "critical", r.getCriticalCount() )) .toList() )); } catch (Exception e) { log.error("Batch scan failed", e); return ResponseEntity.badRequest().body(Map.of( "success", false, "error", e.getMessage() )); } } @PostMapping("/scan/compare") public ResponseEntity<Map<String, Object>> compareScans(@RequestBody CompareRequest request) { try { TfsecScanService.ComparisonResult result = tfsecScanService.compareScans(request.getBaselineDirectory(), request.getCurrentDirectory()); return ResponseEntity.ok(Map.of( "success", true, "baseline", result.getBaselineDirectory(), "current", result.getCurrentDirectory(), "newFindings", result.getNewFindingsCount(), "fixedFindings", result.getFixedFindingsCount(), "regressions", result.getRegressionCount() )); } catch (Exception e) { log.error("Scan comparison failed", e); return ResponseEntity.badRequest().body(Map.of( "success", false, "error", e.getMessage() )); } } @PostMapping("/security-gates") public ResponseEntity<Map<String, Object>> evaluateSecurityGates(@RequestBody ScanResultRequest request) { try { TfsecScanService.SecurityGateResult result = tfsecScanService.evaluateSecurityGates(request.getScanResult()); return ResponseEntity.ok(Map.of( "overallPassed", result.isOverallPassed(), "criticalPassed", result.isCriticalGatePassed(), "highPassed", result.isHighGatePassed(), "mediumPassed", result.isMediumGatePassed(), "findings", Map.of( "critical", result.getCriticalFindings(), "high", result.getHighFindings(), "medium", result.getMediumFindings() ), "failureReason", result.getFailureReason() )); } catch (Exception e) { log.error("Security gates evaluation failed", e); return ResponseEntity.badRequest().body(Map.of( "success", false, "error", e.getMessage() )); } } @GetMapping("/status") public ResponseEntity<Map<String, Object>> getStatus() { try { boolean available = tfsecCliService.isTfsecAvailable(); String version = tfsecCliService.getTfsecVersion(); List<String> checks = tfsecCliService.listAvailableChecks(); return ResponseEntity.ok(Map.of( "available", available, "version", version, "availableChecks", checks.size(), "sampleChecks", checks.stream().limit(10).toList() )); } catch (Exception e) { log.error("Status check failed", e); return ResponseEntity.badRequest().body(Map.of( "success", false, "error", e.getMessage() )); } } @PostMapping("/update") public ResponseEntity<Map<String, Object>> updateTfsec() { try { boolean success = tfsecCliService.updateTfsec(); return ResponseEntity.ok(Map.of( "success", success, "message", success ? "tfsec updated successfully" : "tfsec update failed" )); } catch (Exception e) { log.error("tfsec update failed", e); return ResponseEntity.badRequest().body(Map.of( "success", false, "error", e.getMessage() )); } } // Request DTOs @Data public static class ScanRequest { private String directoryPath; } @Data public static class BatchScanRequest { private List<String> directories; } @Data public static class CompareRequest { private String baselineDirectory; private String currentDirectory; } @Data public static class ScanResultRequest { private TfsecCliService.ScanResult scanResult; } }

Step 6: Application Configuration

application.yml

# application.yml tfsec: cli: path: tfsec version: latest auto-update: true scan: severity-threshold: MEDIUM soft-fail: false exclude-ignored: true include-passed: false no-color: true output: format: JSON directory: ./tfsec-reports generate-sarif: true generate-junit: false generate-csv: false custom-check-dirs: [] excluded-rules: [] included-rules: [] policy-path: config-file: timeout-minutes: 10 recursive: true server: port: 8080 logging: level: com.company.tfsec: DEBUG

Best Practices

  1. Security
  • Run tfsec in isolated environments for untrusted code
  • Validate all input paths to prevent directory traversal
  • Use secure temporary file handling for custom policies
  1. Performance
  • Cache scan results for frequently scanned directories
  • Use parallel processing for batch scans
  • Implement scan result retention policies
  1. Integration
  • Integrate with CI/CD pipelines
  • Generate multiple report formats (SARIF for GitHub, JUnit for Jenkins)
  • Support custom policies for organization-specific rules
  1. Error Handling
  • Implement comprehensive error handling for CLI execution
  • Provide meaningful error messages
  • Support retry mechanisms for transient failures

Conclusion

This tfsec Java integration provides:

  • Comprehensive Scanning: Full tfsec functionality with additional analysis
  • Multiple Output Formats: JSON, SARIF, JUnit, CSV support
  • Security Gates: Configurable thresholds and compliance checking
  • Batch Operations: Efficient scanning of multiple directories
  • Advanced Analysis: Code patterns, security posture, recommendations

Key features:

  1. Dual Interface: Both direct CLI execution and comprehensive analysis
  2. Custom Policies: Support for organization-specific security rules
  3. Comparison Tools: Before/after analysis for change impact
  4. Compliance Reporting: CIS benchmarks and framework compliance
  5. CI/CD Ready: Security gates and multiple report formats

This solution enables Java applications to integrate Terraform security scanning directly into their workflows, providing comprehensive infrastructure as code security assessment.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper