As Java applications increasingly leverage infrastructure-as-code (IaC) for cloud deployments, ensuring infrastructure security and compliance becomes critical. Checkov is a powerful static code analysis tool that scans cloud infrastructure configurations for misconfigurations and security violations before they reach production. Let's explore how Java teams can integrate Checkov into their development workflows.
Why Checkov for Java Teams?
Checkov addresses critical needs for Java applications:
- Infrastructure Security - Scan Terraform, CloudFormation, Kubernetes, and Docker configurations
- Policy as Code - Define custom policies using Python
- Shift-Left Security - Catch misconfigurations during development
- Cloud Compliance - Enforce CIS benchmarks, HIPAA, PCI-DSS requirements
- IDE Integration - Real-time feedback in development environments
Checkov Policy Architecture
Key Components:
- Built-in Policies - 1000+ pre-configured security checks
- Custom Policies - Organization-specific requirements
- Policy Packs - Bundled policies for specific frameworks
- CI/CD Integration - Automated scanning in pipelines
Maven/Gradle Integration
1. Maven Plugin Configuration
<!-- pom.xml -->
<project>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>checkov-scan</id>
<phase>verify</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>checkov</executable>
<arguments>
<argument>-d</argument>
<argument>${project.basedir}/src/main/terraform</argument>
<argument>-o</argument>
<argument>json</argument>
<argument>--output-file-path</argument>
<argument>${project.build.directory}/checkov-report.json</argument>
<argument>--soft-fail</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2. Gradle Task Integration
// build.gradle
task checkovScan(type: Exec) {
group = 'Verification'
description = 'Run Checkov security scan on infrastructure code'
commandLine 'checkov', '-d', 'src/main/terraform',
'-o', 'json',
'--output-file-path', 'build/reports/checkov-report.json',
'--soft-fail'
doLast {
println "Checkov scan completed. Report: build/reports/checkov-report.json"
}
}
check.dependsOn checkovScan
Java Service for Checkov Integration
1. Checkov Scanner Service
@Service
@Slf4j
public class CheckovScannerService {
private final ObjectMapper objectMapper;
private final ProcessBuilder processBuilder;
public CheckovScannerService() {
this.objectMapper = new ObjectMapper();
this.processBuilder = new ProcessBuilder();
}
public ScanResult scanTerraformDirectory(String terraformDir) throws ScanException {
return scanDirectory(terraformDir, "terraform");
}
public ScanResult scanKubernetesManifests(String k8sDir) throws ScanException {
return scanDirectory(k8sDir, "kubernetes");
}
public ScanResult scanDockerfile(String dockerfilePath) throws ScanException {
return scanFile(dockerfilePath, "dockerfile");
}
private ScanResult scanDirectory(String directory, String framework) throws ScanException {
try {
List<String> command = Arrays.asList(
"checkov", "-d", directory,
"-f", framework,
"-o", "json",
"--quiet"
);
Process process = processBuilder.command(command).start();
String jsonOutput = new String(process.getInputStream().readAllBytes());
String errorOutput = new String(process.getErrorStream().readAllBytes());
int exitCode = process.waitFor();
if (exitCode != 0 && !errorOutput.isEmpty()) {
log.warn("Checkov scan completed with warnings: {}", errorOutput);
}
return parseScanResult(jsonOutput);
} catch (IOException | InterruptedException e) {
throw new ScanException("Checkov scan failed for directory: " + directory, e);
}
}
private ScanResult scanFile(String filePath, String framework) throws ScanException {
try {
List<String> command = Arrays.asList(
"checkov", "-f", filePath,
"-f", framework,
"-o", "json",
"--quiet"
);
Process process = processBuilder.command(command).start();
String jsonOutput = new String(process.getInputStream().readAllBytes());
int exitCode = process.waitFor();
if (exitCode != 0) {
log.warn("Checkov scan completed with exit code: {}", exitCode);
}
return parseScanResult(jsonOutput);
} catch (IOException | InterruptedException e) {
throw new ScanException("Checkov scan failed for file: " + filePath, e);
}
}
private ScanResult parseScanResult(String jsonOutput) throws ScanException {
try {
JsonNode root = objectMapper.readTree(jsonOutput);
ScanResult result = new ScanResult();
result.setSummary(parseSummary(root));
result.setChecks(parseChecks(root));
result.setPassed(evaluateQualityGate(result));
return result;
} catch (JsonProcessingException e) {
throw new ScanException("Failed to parse Checkov scan results", e);
}
}
private ScanSummary parseSummary(JsonNode root) {
JsonNode summaryNode = root.path("summary");
ScanSummary summary = new ScanSummary();
summary.setPassed(summaryNode.path("passed").asInt());
summary.setFailed(summaryNode.path("failed").asInt());
summary.setSkipped(summaryNode.path("skipped").asInt());
summary.setTotal(summaryNode.path("passed").asInt() +
summaryNode.path("failed").asInt());
return summary;
}
private List<SecurityCheck> parseChecks(JsonNode root) {
List<SecurityCheck> checks = new ArrayList<>();
JsonNode resultsNode = root.path("results");
if (resultsNode.isObject()) {
resultsNode.fieldNames().forEachRemaining(framework -> {
JsonNode frameworkResults = resultsNode.path(framework);
if (frameworkResults.isArray()) {
frameworkResults.forEach(check -> {
SecurityCheck securityCheck = parseSecurityCheck(check, framework);
checks.add(securityCheck);
});
}
});
}
return checks;
}
private SecurityCheck parseSecurityCheck(JsonNode checkNode, String framework) {
SecurityCheck check = new SecurityCheck();
check.setCheckId(checkNode.path("check_id").asText());
check.setCheckName(checkNode.path("check_name").asText());
check.setCheckResult(checkNode.path("check_result").path("result").asText());
check.setFilePath(checkNode.path("file_path").asText());
check.setResource(checkNode.path("resource").asText());
check.setGuideline(checkNode.path("guideline").asText());
check.setFramework(framework);
// Parse failed checks details
if ("FAILED".equals(check.getCheckResult())) {
List<ViolationDetail> violations = parseViolations(checkNode);
check.setViolations(violations);
}
return check;
}
private List<ViolationDetail> parseViolations(JsonNode checkNode) {
List<ViolationDetail> violations = new ArrayList<>();
JsonNode fileResults = checkNode.path("file_results");
if (fileResults.isArray()) {
fileResults.forEach(fileResult -> {
JsonNode violationsNode = fileResult.path("violations");
if (violationsNode.isArray()) {
violationsNode.forEach(violation -> {
ViolationDetail detail = new ViolationDetail();
detail.setLineNumber(violation.path("line_number").asInt());
detail.setCodeBlock(violation.path("code_block").asText());
violations.add(detail);
});
}
});
}
return violations;
}
private boolean evaluateQualityGate(ScanResult result) {
// Fail quality gate if any high-severity checks failed
return result.getChecks().stream()
.filter(check -> "FAILED".equals(check.getCheckResult()))
.noneMatch(this::isHighSeverityCheck);
}
private boolean isHighSeverityCheck(SecurityCheck check) {
// Define high-severity check patterns
List<String> highSeverityPatterns = Arrays.asList(
"CKV_.*_1$", // Generally high severity checks
".*PUBLIC_ACCESS.*",
".*ENCRYPTION.*",
".*PASSWORD.*"
);
return highSeverityPatterns.stream()
.anyMatch(pattern -> check.getCheckId().matches(pattern));
}
// DTOs for scan results
@Data
public static class ScanResult {
private ScanSummary summary;
private List<SecurityCheck> checks;
private boolean passed;
private Date scanDate = new Date();
}
@Data
public static class ScanSummary {
private int passed;
private int failed;
private int skipped;
private int total;
public double getPassRate() {
return total > 0 ? (double) passed / total * 100 : 100.0;
}
}
@Data
public static class SecurityCheck {
private String checkId;
private String checkName;
private String checkResult; // PASSED, FAILED, SKIPPED
private String filePath;
private String resource;
private String guideline;
private String framework;
private List<ViolationDetail> violations = new ArrayList<>();
}
@Data
public static class ViolationDetail {
private int lineNumber;
private String codeBlock;
}
}
Custom Checkov Policies for Java Applications
1. Java-Specific Custom Policies
# policies/custom/java_app_policies.py
from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
class JavaAppMemoryLimitsCheck(BaseResourceCheck):
def __init__(self):
name = "Ensure Java applications have adequate memory limits"
id = "CKV_CUSTOM_JAVA_001"
supported_resources = ['kubernetes_deployment', 'kubernetes_pod']
categories = [CheckCategories.KUBERNETES]
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
def scan_resource_conf(self, conf):
# Check if it's a Java application
containers = conf.get('spec', [{}])[0].get('template', [{}])[0].get('spec', [{}])[0].get('container', [])
for container in containers:
container_name = container.get('name', [''])[0]
image = container.get('image', [''])[0]
# Identify Java applications
if any(java_indicator in image.lower() for java_indicator in ['openjdk', 'java', 'jre', 'spring']):
resources = container.get('resources', [{}])[0]
limits = resources.get('limits', [{}])[0]
memory_limit = limits.get('memory', [''])[0]
if not memory_limit:
self.evaluated_keys = [f'spec/[0]/template/[0]/spec/[0]/container/{{{{?}}}}/resources/[0]/limits/[0]/memory']
return CheckResult.FAILED
# Ensure minimum memory for Java applications
if self.parse_memory(memory_limit) < 512: # 512MB minimum
return CheckResult.FAILED
return CheckResult.PASSED
def parse_memory(self, memory_string):
"""Parse Kubernetes memory string to megabytes"""
if memory_string.endswith('Mi'):
return int(memory_string[:-2])
elif memory_string.endswith('Gi'):
return int(memory_string[:-2]) * 1024
else:
return 0
class JavaAppProbesCheck(BaseResourceCheck):
def __init__(self):
name = "Ensure Java applications have liveness and readiness probes"
id = "CKV_CUSTOM_JAVA_002"
supported_resources = ['kubernetes_deployment']
categories = [CheckCategories.KUBERNETES]
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
def scan_resource_conf(self, conf):
containers = conf.get('spec', [{}])[0].get('template', [{}])[0].get('spec', [{}])[0].get('container', [])
for container in containers:
image = container.get('image', [''])[0]
# Check only Java applications
if any(java_indicator in image.lower() for java_indicator in ['openjdk', 'java', 'spring']):
if not container.get('liveness_probe') or not container.get('readiness_probe'):
return CheckResult.FAILED
return CheckResult.PASSED
class SpringBootActuatorSecurityCheck(BaseResourceCheck):
def __init__(self):
name = "Ensure Spring Boot Actuator endpoints are secured"
id = "CKV_CUSTOM_JAVA_003"
supported_resources = ['kubernetes_service', 'kubernetes_ingress']
categories = [CheckCategories.KUBERNETES]
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
def scan_resource_conf(self, conf):
# Check if actuator endpoints are exposed without authentication
if conf.get('metadata', [{}])[0].get('labels', [{}])[0].get('app', [''])[0] == 'spring-boot':
# Check for exposed actuator endpoints
if self.has_exposed_actuator_endpoints(conf):
return CheckResult.FAILED
return CheckResult.PASSED
def has_exposed_actuator_endpoints(self, conf):
# Implementation to check for exposed actuator endpoints
# This would check service ports, ingress rules, etc.
return False
2. Custom Policy Configuration
# .checkov.yaml branch: main compact: true download-external-modules: false evaluate-variables: true external-modules-download-path: .external_modules framework: - terraform - cloudformation - kubernetes - dockerfile - secrets - serverless - arm - helm - custom quiet: true skip-path: - .git - .github - .terraform - node_modules soft-fail: false skip-check: - CKV_AWS_115 # Lambda function should not have public access (we manage via API Gateway) - CKV_AWS_108 # IAM policies should not allow data exfiltration (false positive) use-enforcement-rules: false output: json checks: - id: CKV_CUSTOM_JAVA_001 severity: HIGH - id: CKV_CUSTOM_JAVA_002 severity: MEDIUM - id: CKV_CUSTOM_JAVA_003 severity: HIGH external-checks-dir: - policies/custom/
GitHub Actions Integration
1. Comprehensive Checkov Pipeline
# .github/workflows/checkov-scan.yml name: Checkov Infrastructure Security Scan on: push: branches: [ main, develop ] paths: - 'src/main/terraform/**' - 'src/main/kubernetes/**' - 'Dockerfile' - 'docker-compose.yml' pull_request: branches: [ main ] jobs: terraform-checkov: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.9' - name: Install Checkov run: | pip install checkov checkov --version - name: Run Checkov on Terraform id: terraform-scan run: | checkov -d src/main/terraform/ \ -o json \ --output-file-path checkov-terraform-report.json \ --soft-fail echo "TERRAFORM_EXIT_CODE=$?" >> $GITHUB_OUTPUT - name: Upload Terraform scan results uses: actions/upload-artifact@v3 with: name: checkov-terraform-report path: checkov-terraform-report.json - name: Fail if critical issues found if: steps.terraform-scan.outputs.TERRAFORM_EXIT_CODE != 0 run: | echo "❌ Critical infrastructure issues detected" echo "Check the Checkov report for details" exit 1 kubernetes-checkov: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Checkov run: pip install checkov - name: Run Checkov on Kubernetes manifests id: k8s-scan run: | checkov -d src/main/kubernetes/ \ -o json \ --output-file-path checkov-kubernetes-report.json \ --soft-fail \ --check CKV_CUSTOM_JAVA_001,CKV_CUSTOM_JAVA_002,CKV_CUSTOM_JAVA_003 echo "K8S_EXIT_CODE=$?" >> $GITHUB_OUTPUT - name: Upload Kubernetes scan results uses: actions/upload-artifact@v3 with: name: checkov-kubernetes-report path: checkov-kubernetes-report.json docker-checkov: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Checkov run: pip install checkov - name: Run Checkov on Dockerfile run: | checkov -f Dockerfile \ -o json \ --output-file-path checkov-docker-report.json \ --soft-fail - name: Upload Docker scan results uses: actions/upload-artifact@v3 with: name: checkov-docker-report path: checkov-docker-report.json security-report: runs-on: ubuntu-latest needs: [terraform-checkov, kubernetes-checkov, docker-checkov] steps: - uses: actions/checkout@v4 - name: Download all scan reports uses: actions/download-artifact@v3 - name: Generate security report run: | echo "# Infrastructure Security Report" > security-report.md echo "## Scan Summary" >> security-report.md echo "- ✅ Terraform: Completed" >> security-report.md echo "- ✅ Kubernetes: Completed" >> security-report.md echo "- ✅ Docker: Completed" >> security-report.md echo "" >> security-report.md echo "Detailed reports available in artifacts." >> security-report.md - name: Upload security report uses: actions/upload-artifact@v3 with: name: security-report path: security-report.md
Jenkins Pipeline Integration
1. Jenkinsfile with Checkov
// Jenkinsfile
pipeline {
agent any
environment {
CHECKOV_VERSION = '2.3.257'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Infrastructure Security Scan') {
parallel {
stage('Terraform Scan') {
steps {
script {
sh """
python3 -m pip install checkov==${CHECKOV_VERSION}
checkov -d src/main/terraform/ \
-o json \
--output-file-path checkov-terraform-report.json \
--soft-fail
"""
// Parse results and set quality gate
def terraformReport = readJSON file: 'checkov-terraform-report.json'
def failedChecks = terraformReport.results.failed_checks ?: []
def criticalFailures = failedChecks.findAll { it.severity == 'HIGH' }
if (criticalFailures.size() > 0) {
unstable "Found ${criticalFailures.size()} critical Terraform issues"
}
}
}
post {
always {
archiveArtifacts artifacts: 'checkov-terraform-report.json', fingerprint: true
publishHTML([
allowMissing: false,
reportDir: '.',
reportFiles: 'checkov-terraform-report.json',
reportName: 'Checkov Terraform Report'
])
}
}
}
stage('Kubernetes Scan') {
steps {
script {
sh """
checkov -d src/main/kubernetes/ \
-o json \
--output-file-path checkov-kubernetes-report.json \
--soft-fail \
--check CKV_CUSTOM_JAVA_001,CKV_CUSTOM_JAVA_002
"""
}
}
post {
always {
archiveArtifacts artifacts: 'checkov-kubernetes-report.json', fingerprint: true
}
}
}
}
}
stage('Security Quality Gate') {
steps {
script {
// Load and analyze all scan reports
def terraformReport = readJSON file: 'checkov-terraform-report.json'
def k8sReport = readJSON file: 'checkov-kubernetes-report.json'
def totalCritical = countCriticalFailures(terraformReport) + countCriticalFailures(k8sReport)
if (totalCritical > 0) {
error("Security quality gate failed: ${totalCritical} critical issues found")
}
}
}
}
}
post {
always {
emailext (
subject: "Infrastructure Security Scan: ${currentBuild.result} - ${env.JOB_NAME}",
body: """
Checkov security scan completed for ${env.JOB_NAME}
Build Result: ${currentBuild.result}
Build URL: ${env.BUILD_URL}
Check Jenkins artifacts for detailed reports.
""",
to: "${env.BUILD_USER_EMAIL}",
attachLog: false
)
}
}
}
def countCriticalFailures(report) {
def failedChecks = report.results?.failed_checks ?: []
return failedChecks.findAll { it.severity == 'HIGH' }.size()
}
IDE Integration for Java Developers
1. Pre-commit Hook for Infrastructure Code
// src/main/scripts/PreCommitCheckov.java
public class PreCommitCheckov {
public static void main(String[] args) {
try {
System.out.println("Running pre-commit Checkov scan...");
// Scan changed infrastructure files
List<String> changedFiles = getChangedFiles();
List<String> infraFiles = filterInfrastructureFiles(changedFiles);
if (!infraFiles.isEmpty()) {
System.out.println("Scanning infrastructure files: " + infraFiles);
for (String file : infraFiles) {
runCheckovScan(file);
}
} else {
System.out.println("No infrastructure files changed.");
}
} catch (Exception e) {
System.err.println("Pre-commit check failed: " + e.getMessage());
System.exit(1);
}
}
private static List<String> getChangedFiles() throws IOException, InterruptedException {
Process process = new ProcessBuilder("git", "diff", "--cached", "--name-only")
.start();
String output = new String(process.getInputStream().readAllBytes());
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new RuntimeException("Failed to get changed files");
}
return Arrays.stream(output.split("\n"))
.filter(line -> !line.trim().isEmpty())
.collect(Collectors.toList());
}
private static List<String> filterInfrastructureFiles(List<String> files) {
return files.stream()
.filter(file -> file.endsWith(".tf") ||
file.endsWith(".yaml") ||
file.endsWith(".yml") ||
file.equals("Dockerfile"))
.collect(Collectors.toList());
}
private static void runCheckovScan(String filePath) throws IOException, InterruptedException {
String framework = determineFramework(filePath);
Process process = new ProcessBuilder("checkov", "-f", filePath, "-f", framework)
.start();
String output = new String(process.getInputStream().readAllBytes());
int exitCode = process.waitFor();
if (exitCode != 0) {
System.err.println("Checkov scan failed for: " + filePath);
System.err.println(output);
System.exit(1);
}
System.out.println("✓ " + filePath + " passed Checkov scan");
}
private static String determineFramework(String filePath) {
if (filePath.endsWith(".tf")) return "terraform";
if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) return "kubernetes";
if (filePath.equals("Dockerfile")) return "dockerfile";
return "terraform"; // default
}
}
Testing Checkov Integration
1. Unit Tests for Checkov Service
@ExtendWith(MockitoExtension.class)
class CheckovScannerServiceTest {
@InjectMocks
private CheckovScannerService checkovService;
@Test
void shouldParseValidCheckovOutput() throws Exception {
// Given
String validJsonOutput = """
{
"summary": {
"passed": 10,
"failed": 2,
"skipped": 1
},
"results": {
"terraform": [
{
"check_id": "CKV_AWS_1",
"check_name": "Ensure something",
"check_result": {"result": "PASSED"},
"file_path": "main.tf",
"resource": "aws_s3_bucket.example"
}
]
}
}
""";
// When
CheckovScannerService.ScanResult result =
checkovService.parseScanResult(validJsonOutput);
// Then
assertNotNull(result);
assertEquals(10, result.getSummary().getPassed());
assertEquals(2, result.getSummary().getFailed());
assertTrue(result.isPassed());
}
@Test
void shouldFailQualityGateForCriticalIssues() throws Exception {
// Given
String jsonWithCriticalIssues = """
{
"summary": {"passed": 5, "failed": 3, "skipped": 0},
"results": {
"terraform": [
{
"check_id": "CKV_AWS_1",
"check_name": "Critical security issue",
"check_result": {"result": "FAILED"},
"file_path": "main.tf",
"resource": "aws_s3_bucket.public"
}
]
}
}
""";
// When
CheckovScannerService.ScanResult result =
checkovService.parseScanResult(jsonWithCriticalIssues);
// Then
assertFalse(result.isPassed());
}
}
Best Practices for Checkov in Java Projects
- Policy as Code - Store custom policies in version control
- Shift-Left Scanning - Integrate early in development workflow
- Quality Gates - Fail builds on critical security issues
- Custom Policies - Create organization-specific checks
- Regular Updates - Keep Checkov and policies updated
- Comprehensive Coverage - Scan all infrastructure components
- Reporting - Generate actionable security reports
- Remediation Guidance - Provide clear fix instructions
Conclusion
Integrating Checkov into Java development pipelines transforms infrastructure security from an operational concern to a development responsibility. By catching misconfigurations early, Java teams can:
- Prevent Security Breaches - Stop vulnerable configurations from reaching production
- Ensure Compliance - Enforce organizational and regulatory requirements
- Accelerate Development - Provide immediate feedback to developers
- Reduce Costs - Fix issues when they're cheapest to address
- Improve Quality - Maintain consistent, secure infrastructure patterns
The combination of Checkov's extensive built-in policies with Java-specific custom checks creates a robust security foundation for cloud-native Java applications. By implementing the patterns and integrations shown here, Java teams can confidently deliver secure, compliant infrastructure alongside their application code.