Introduction to KubeLinter
KubeLinter is a static analysis tool that checks Kubernetes YAML files and Helm charts for security misconfigurations and best practices. This guide covers Java integration with KubeLinter for validating Kubernetes manifests in CI/CD pipelines and development workflows.
Key KubeLinter Features
- Security Misconfiguration Detection
- Best Practices Enforcement
- Custom Check Creation
- Helm Chart Support
- CI/CD Integration
- JSON Output Format
Dependencies and Setup
Maven Configuration
<properties>
<jackson.version>2.15.2</jackson.version>
<snakeyaml.version>2.0</snakeyaml.version>
<junit.version>5.9.3</junit.version>
</properties>
<dependencies>
<!-- YAML Processing -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Apache Commons for file operations -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>1.3</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Core KubeLinter Integration
KubeLinter Client Implementation
package com.kubelinter.client;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.PumpStreamHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class KubeLinterClient {
private static final Logger logger = LoggerFactory.getLogger(KubeLinterClient.class);
private final String kubeLinterPath;
private final ObjectMapper objectMapper;
public KubeLinterClient() {
this("kubelinter");
}
public KubeLinterClient(String kubeLinterPath) {
this.kubeLinterPath = kubeLinterPath;
this.objectMapper = new ObjectMapper();
}
/**
* Check if KubeLinter is available in the system PATH
*/
public boolean isAvailable() {
try {
CommandLine cmd = new CommandLine(kubeLinterPath);
cmd.addArgument("version");
DefaultExecutor executor = new DefaultExecutor();
executor.setExitValue(0);
executor.execute(cmd);
logger.info("KubeLinter is available");
return true;
} catch (Exception e) {
logger.warn("KubeLinter is not available: {}", e.getMessage());
return false;
}
}
/**
* Lint a single YAML file
*/
public LintResult lintFile(String filePath) throws IOException {
return lintFiles(List.of(filePath));
}
/**
* Lint multiple YAML files
*/
public LintResult lintFiles(List<String> filePaths) throws IOException {
CommandLine cmd = new CommandLine(kubeLinterPath);
cmd.addArgument("lint");
// Add files
for (String filePath : filePaths) {
cmd.addArgument(filePath);
}
// Add output format
cmd.addArgument("--format");
cmd.addArgument("json");
// Execute command
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
DefaultExecutor executor = new DefaultExecutor();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
executor.setStreamHandler(streamHandler);
try {
int exitCode = executor.execute(cmd);
String output = outputStream.toString();
return parseLintResult(output, exitCode);
} catch (IOException e) {
logger.error("KubeLinter execution failed: {}", e.getMessage());
throw new KubeLinterException("Failed to execute KubeLinter", e);
}
}
/**
* Lint a directory containing YAML files
*/
public LintResult lintDirectory(String directoryPath) throws IOException {
CommandLine cmd = new CommandLine(kubeLinterPath);
cmd.addArgument("lint");
cmd.addArgument(directoryPath);
cmd.addArgument("--format");
cmd.addArgument("json");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
DefaultExecutor executor = new DefaultExecutor();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
executor.setStreamHandler(streamHandler);
try {
int exitCode = executor.execute(cmd);
String output = outputStream.toString();
return parseLintResult(output, exitCode);
} catch (IOException e) {
logger.error("KubeLinter directory lint failed: {}", e.getMessage());
throw new KubeLinterException("Failed to lint directory", e);
}
}
/**
* Lint with custom checks
*/
public LintResult lintWithCustomChecks(List<String> filePaths,
List<String> includeChecks,
List<String> excludeChecks) throws IOException {
CommandLine cmd = new CommandLine(kubeLinterPath);
cmd.addArgument("lint");
// Add files
for (String filePath : filePaths) {
cmd.addArgument(filePath);
}
// Add include checks
if (includeChecks != null && !includeChecks.isEmpty()) {
cmd.addArgument("--include");
cmd.addArgument(String.join(",", includeChecks));
}
// Add exclude checks
if (excludeChecks != null && !excludeChecks.isEmpty()) {
cmd.addArgument("--exclude");
cmd.addArgument(String.join(",", excludeChecks));
}
cmd.addArgument("--format");
cmd.addArgument("json");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
DefaultExecutor executor = new DefaultExecutor();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
executor.setStreamHandler(streamHandler);
try {
int exitCode = executor.execute(cmd);
String output = outputStream.toString();
return parseLintResult(output, exitCode);
} catch (IOException e) {
logger.error("KubeLinter custom checks failed: {}", e.getMessage());
throw new KubeLinterException("Failed to execute custom checks", e);
}
}
/**
* Parse KubeLinter JSON output
*/
private LintResult parseLintResult(String jsonOutput, int exitCode) throws IOException {
LintResult result = objectMapper.readValue(jsonOutput, LintResult.class);
result.setExitCode(exitCode);
return result;
}
/**
* Get available checks from KubeLinter
*/
public List<Check> getAvailableChecks() throws IOException {
CommandLine cmd = new CommandLine(kubeLinterPath);
cmd.addArgument("checks");
cmd.addArgument("--format");
cmd.addArgument("json");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
DefaultExecutor executor = new DefaultExecutor();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
executor.setStreamHandler(streamHandler);
try {
executor.execute(cmd);
String output = outputStream.toString();
return objectMapper.readValue(
output,
objectMapper.getTypeFactory().constructCollectionType(List.class, Check.class)
);
} catch (IOException e) {
logger.error("Failed to get available checks: {}", e.getMessage());
throw new KubeLinterException("Failed to get available checks", e);
}
}
public static class KubeLinterException extends RuntimeException {
public KubeLinterException(String message) {
super(message);
}
public KubeLinterException(String message, Throwable cause) {
super(message, cause);
}
}
}
Data Models
Response Data Classes
package com.kubelinter.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Map;
// Main lint result
@JsonIgnoreProperties(ignoreUnknown = true)
public class LintResult {
@JsonProperty("Reports")
private List<Report> reports;
private int exitCode;
// Helper methods
public boolean hasErrors() {
return reports != null && reports.stream()
.anyMatch(report -> report.getDiagnostics() != null &&
!report.getDiagnostics().isEmpty());
}
public int getErrorCount() {
if (reports == null) return 0;
return reports.stream()
.mapToInt(report -> report.getDiagnostics() != null ?
report.getDiagnostics().size() : 0)
.sum();
}
public List<Diagnostic> getAllDiagnostics() {
return reports.stream()
.filter(report -> report.getDiagnostics() != null)
.flatMap(report -> report.getDiagnostics().stream())
.toList();
}
// Getters and setters
public List<Report> getReports() { return reports; }
public void setReports(List<Report> reports) { this.reports = reports; }
public int getExitCode() { return exitCode; }
public void setExitCode(int exitCode) { this.exitCode = exitCode; }
}
// Individual report for each file
@JsonIgnoreProperties(ignoreUnknown = true)
class Report {
@JsonProperty("Object")
private K8sObject object;
@JsonProperty("Diagnostics")
private List<Diagnostic> diagnostics;
// Getters and setters
public K8sObject getObject() { return object; }
public void setObject(K8sObject object) { this.object = object; }
public List<Diagnostic> getDiagnostics() { return diagnostics; }
public void setDiagnostics(List<Diagnostic> diagnostics) { this.diagnostics = diagnostics; }
}
// Kubernetes object information
@JsonIgnoreProperties(ignoreUnknown = true)
class K8sObject {
@JsonProperty("K8sObject")
private ObjectDetails objectDetails;
// Getters and setters
public ObjectDetails getObjectDetails() { return objectDetails; }
public void setObjectDetails(ObjectDetails objectDetails) { this.objectDetails = objectDetails; }
}
// Object details
@JsonIgnoreProperties(ignoreUnknown = true)
class ObjectDetails {
private String name;
private String kind;
private String namespace;
private String filePath;
private Map<String, Object> metadata;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getKind() { return kind; }
public void setKind(String kind) { this.kind = kind; }
public String getNamespace() { return namespace; }
public void setNamespace(String namespace) { this.namespace = namespace; }
public String getFilePath() { return filePath; }
public void setFilePath(String filePath) { this.filePath = filePath; }
public Map<String, Object> getMetadata() { return metadata; }
public void setMetadata(Map<String, Object> metadata) { this.metadata = metadata; }
}
// Diagnostic information
@JsonIgnoreProperties(ignoreUnknown = true)
class Diagnostic {
private String message;
private String remediation;
private String check;
private String severity;
private Map<String, Object> properties;
// Getters and setters
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getRemediation() { return remediation; }
public void setRemediation(String remediation) { this.remediation = remediation; }
public String getCheck() { return check; }
public void setCheck(String check) { this.check = check; }
public String getSeverity() { return severity; }
public void setSeverity(String severity) { this.severity = severity; }
public Map<String, Object> getProperties() { return properties; }
public void setProperties(Map<String, Object> properties) { this.properties = properties; }
}
// Available check
@JsonIgnoreProperties(ignoreUnknown = true)
class Check {
private String name;
private String description;
private String remediation;
private String template;
private Map<String, Object> parameters;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getRemediation() { return remediation; }
public void setRemediation(String remediation) { this.remediation = remediation; }
public String getTemplate() { return template; }
public void setTemplate(String template) { this.template = template; }
public Map<String, Object> getParameters() { return parameters; }
public void setParameters(Map<String, Object> parameters) { this.parameters = parameters; }
}
YAML Analysis Service
Comprehensive KubeLinter Service
package com.kubelinter.service;
import com.kubelinter.client.KubeLinterClient;
import com.kubelinter.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
public class KubeLinterService {
private static final Logger logger = LoggerFactory.getLogger(KubeLinterService.class);
private final KubeLinterClient client;
private final Set<String> excludedChecks;
private final Set<String> includedChecks;
public KubeLinterService() {
this(new KubeLinterClient());
}
public KubeLinterService(KubeLinterClient client) {
this.client = client;
this.excludedChecks = new HashSet<>();
this.includedChecks = new HashSet<>();
}
/**
* Check if KubeLinter is available
*/
public boolean isAvailable() {
return client.isAvailable();
}
/**
* Analyze Kubernetes YAML files with comprehensive reporting
*/
public AnalysisResult analyzeFiles(List<String> filePaths) throws IOException {
logger.info("Analyzing {} Kubernetes YAML files", filePaths.size());
LintResult result;
if (!includedChecks.isEmpty() || !excludedChecks.isEmpty()) {
result = client.lintWithCustomChecks(
filePaths,
includedChecks.isEmpty() ? null : new ArrayList<>(includedChecks),
excludedChecks.isEmpty() ? null : new ArrayList<>(excludedChecks)
);
} else {
result = client.lintFiles(filePaths);
}
return buildAnalysisResult(result);
}
/**
* Analyze directory containing Kubernetes manifests
*/
public AnalysisResult analyzeDirectory(String directoryPath) throws IOException {
logger.info("Analyzing Kubernetes manifests in directory: {}", directoryPath);
LintResult result = client.lintDirectory(directoryPath);
return buildAnalysisResult(result);
}
/**
* Build comprehensive analysis result
*/
private AnalysisResult buildAnalysisResult(LintResult lintResult) {
AnalysisResult result = new AnalysisResult();
result.setLintResult(lintResult);
result.setTotalFiles(lintResult.getReports().size());
result.setTotalErrors(lintResult.getErrorCount());
// Categorize by severity
Map<String, Long> severityCounts = lintResult.getAllDiagnostics().stream()
.collect(Collectors.groupingBy(
Diagnostic::getSeverity,
Collectors.counting()
));
result.setSeverityCounts(severityCounts);
// Group by check type
Map<String, List<Diagnostic>> diagnosticsByCheck = lintResult.getAllDiagnostics().stream()
.collect(Collectors.groupingBy(Diagnostic::getCheck));
result.setDiagnosticsByCheck(diagnosticsByCheck);
// Group by file
Map<String, List<Diagnostic>> diagnosticsByFile = lintResult.getReports().stream()
.collect(Collectors.toMap(
report -> report.getObject().getObjectDetails().getFilePath(),
Report::getDiagnostics
));
result.setDiagnosticsByFile(diagnosticsByFile);
return result;
}
/**
* Get security-focused analysis (only high/critical severity issues)
*/
public SecurityAnalysis getSecurityAnalysis(List<String> filePaths) throws IOException {
AnalysisResult fullAnalysis = analyzeFiles(filePaths);
SecurityAnalysis securityAnalysis = new SecurityAnalysis();
securityAnalysis.setTotalFiles(fullAnalysis.getTotalFiles());
// Filter only high severity issues
List<Diagnostic> securityIssues = fullAnalysis.getLintResult().getAllDiagnostics().stream()
.filter(diagnostic -> isSecurityRelevant(diagnostic.getSeverity()))
.collect(Collectors.toList());
securityAnalysis.setSecurityIssues(securityIssues);
securityAnalysis.setSecurityIssueCount(securityIssues.size());
// Categorize security issues
Map<String, List<Diagnostic>> securityIssuesByType = securityIssues.stream()
.collect(Collectors.groupingBy(this::categorizeSecurityIssue));
securityAnalysis.setSecurityIssuesByType(securityIssuesByType);
return securityAnalysis;
}
private boolean isSecurityRelevant(String severity) {
return "error".equalsIgnoreCase(severity) || "warning".equalsIgnoreCase(severity);
}
private String categorizeSecurityIssue(Diagnostic diagnostic) {
String checkName = diagnostic.getCheck().toLowerCase();
if (checkName.contains("privilege") || checkName.contains("root")) {
return "Privilege Escalation";
} else if (checkName.contains("mount") || checkName.contains("volume")) {
return "Volume Security";
} else if (checkName.contains("network") || checkName.contains("port")) {
return "Network Security";
} else if (checkName.contains("resource") || checkName.contains("limit")) {
return "Resource Management";
} else if (checkName.contains("image") || checkName.contains("tag")) {
return "Image Security";
} else if (checkName.contains("env") || checkName.contains("secret")) {
return "Secrets Management";
} else {
return "Other Security";
}
}
/**
* Generate compliance report
*/
public ComplianceReport generateComplianceReport(List<String> filePaths,
ComplianceStandard standard) throws IOException {
AnalysisResult analysis = analyzeFiles(filePaths);
ComplianceReport report = new ComplianceReport();
report.setStandard(standard);
report.setAnalysisResult(analysis);
// Evaluate against compliance standard
boolean compliant = evaluateCompliance(analysis, standard);
report.setCompliant(compliant);
// Generate compliance details
Map<String, ComplianceCheck> complianceChecks = generateComplianceChecks(analysis, standard);
report.setComplianceChecks(complianceChecks);
return report;
}
private boolean evaluateCompliance(AnalysisResult analysis, ComplianceStandard standard) {
// Implementation depends on compliance requirements
// For example, no high severity issues for certain standards
Long errorCount = analysis.getSeverityCounts().get("error");
return errorCount == null || errorCount == 0;
}
private Map<String, ComplianceCheck> generateComplianceChecks(AnalysisResult analysis,
ComplianceStandard standard) {
// Generate compliance check details based on standard
Map<String, ComplianceCheck> checks = new HashMap<>();
// Example compliance checks
checks.put("no_privileged_containers", checkPrivilegedContainers(analysis));
checks.put("no_root_containers", checkRootContainers(analysis));
checks.put("resource_limits_set", checkResourceLimits(analysis));
return checks;
}
private ComplianceCheck checkPrivilegedContainers(AnalysisResult analysis) {
long privilegedCount = analysis.getLintResult().getAllDiagnostics().stream()
.filter(d -> d.getCheck().contains("privileged"))
.count();
ComplianceCheck check = new ComplianceCheck();
check.setName("No Privileged Containers");
check.setPassed(privilegedCount == 0);
check.setDescription("Containers should not run in privileged mode");
return check;
}
private ComplianceCheck checkRootContainers(AnalysisResult analysis) {
long rootCount = analysis.getLintResult().getAllDiagnostics().stream()
.filter(d -> d.getCheck().contains("root") || d.getMessage().contains("runAsRoot"))
.count();
ComplianceCheck check = new ComplianceCheck();
check.setName("No Root Containers");
check.setPassed(rootCount == 0);
check.setDescription("Containers should not run as root user");
return check;
}
private ComplianceCheck checkResourceLimits(AnalysisResult analysis) {
long missingLimits = analysis.getLintResult().getAllDiagnostics().stream()
.filter(d -> d.getCheck().contains("resource") || d.getMessage().contains("limit"))
.count();
ComplianceCheck check = new ComplianceCheck();
check.setName("Resource Limits Set");
check.setPassed(missingLimits == 0);
check.setDescription("All containers should have resource limits set");
return check;
}
// Configuration methods
public void excludeCheck(String checkName) {
excludedChecks.add(checkName);
}
public void includeCheck(String checkName) {
includedChecks.add(checkName);
}
public void setExcludedChecks(Set<String> checks) {
excludedChecks.clear();
excludedChecks.addAll(checks);
}
public void setIncludedChecks(Set<String> checks) {
includedChecks.clear();
includedChecks.addAll(checks);
}
// Result classes
public static class AnalysisResult {
private LintResult lintResult;
private int totalFiles;
private int totalErrors;
private Map<String, Long> severityCounts;
private Map<String, List<Diagnostic>> diagnosticsByCheck;
private Map<String, List<Diagnostic>> diagnosticsByFile;
// Getters and setters
public LintResult getLintResult() { return lintResult; }
public void setLintResult(LintResult lintResult) { this.lintResult = lintResult; }
public int getTotalFiles() { return totalFiles; }
public void setTotalFiles(int totalFiles) { this.totalFiles = totalFiles; }
public int getTotalErrors() { return totalErrors; }
public void setTotalErrors(int totalErrors) { this.totalErrors = totalErrors; }
public Map<String, Long> getSeverityCounts() { return severityCounts; }
public void setSeverityCounts(Map<String, Long> severityCounts) { this.severityCounts = severityCounts; }
public Map<String, List<Diagnostic>> getDiagnosticsByCheck() { return diagnosticsByCheck; }
public void setDiagnosticsByCheck(Map<String, List<Diagnostic>> diagnosticsByCheck) { this.diagnosticsByCheck = diagnosticsByCheck; }
public Map<String, List<Diagnostic>> getDiagnosticsByFile() { return diagnosticsByFile; }
public void setDiagnosticsByFile(Map<String, List<Diagnostic>> diagnosticsByFile) { this.diagnosticsByFile = diagnosticsByFile; }
}
public static class SecurityAnalysis {
private int totalFiles;
private int securityIssueCount;
private List<Diagnostic> securityIssues;
private Map<String, List<Diagnostic>> securityIssuesByType;
// Getters and setters
public int getTotalFiles() { return totalFiles; }
public void setTotalFiles(int totalFiles) { this.totalFiles = totalFiles; }
public int getSecurityIssueCount() { return securityIssueCount; }
public void setSecurityIssueCount(int securityIssueCount) { this.securityIssueCount = securityIssueCount; }
public List<Diagnostic> getSecurityIssues() { return securityIssues; }
public void setSecurityIssues(List<Diagnostic> securityIssues) { this.securityIssues = securityIssues; }
public Map<String, List<Diagnostic>> getSecurityIssuesByType() { return securityIssuesByType; }
public void setSecurityIssuesByType(Map<String, List<Diagnostic>> securityIssuesByType) { this.securityIssuesByType = securityIssuesByType; }
}
public static class ComplianceReport {
private ComplianceStandard standard;
private boolean compliant;
private AnalysisResult analysisResult;
private Map<String, ComplianceCheck> complianceChecks;
// Getters and setters
public ComplianceStandard getStandard() { return standard; }
public void setStandard(ComplianceStandard standard) { this.standard = standard; }
public boolean isCompliant() { return compliant; }
public void setCompliant(boolean compliant) { this.compliant = compliant; }
public AnalysisResult getAnalysisResult() { return analysisResult; }
public void setAnalysisResult(AnalysisResult analysisResult) { this.analysisResult = analysisResult; }
public Map<String, ComplianceCheck> getComplianceChecks() { return complianceChecks; }
public void setComplianceChecks(Map<String, ComplianceCheck> complianceChecks) { this.complianceChecks = complianceChecks; }
}
public static class ComplianceCheck {
private String name;
private boolean passed;
private String description;
private String remediation;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public boolean isPassed() { return passed; }
public void setPassed(boolean passed) { this.passed = passed; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getRemediation() { return remediation; }
public void setRemediation(String remediation) { this.remediation = remediation; }
}
public enum ComplianceStandard {
CIS_KUBERNETES,
NSA_CISA,
PCI_DSS,
HIPAA,
CUSTOM
}
}
Spring Boot Integration
Configuration Class
package com.kubelinter.config;
import com.kubelinter.client.KubeLinterClient;
import com.kubelinter.service.KubeLinterService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@Configuration
public class KubeLinterConfig {
@Value("${kubelinter.path:kubelinter}")
private String kubeLinterPath;
@Value("${kubelinter.exclude.checks:}")
private String excludedChecks;
@Value("${kubelinter.include.checks:}")
private String includedChecks;
@Bean
public KubeLinterClient kubeLinterClient() {
return new KubeLinterClient(kubeLinterPath);
}
@Bean
public KubeLinterService kubeLinterService() {
KubeLinterService service = new KubeLinterService(kubeLinterClient());
// Configure excluded checks
if (!excludedChecks.isEmpty()) {
Set<String> excluded = new HashSet<>(Arrays.asList(excludedChecks.split(",")));
service.setExcludedChecks(excluded);
}
// Configure included checks
if (!includedChecks.isEmpty()) {
Set<String> included = new HashSet<>(Arrays.asList(includedChecks.split(",")));
service.setIncludedChecks(included);
}
return service;
}
}
REST Controller
package com.kubelinter.controller;
import com.kubelinter.service.KubeLinterService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/kubelinter")
public class KubeLinterController {
private static final Logger logger = LoggerFactory.getLogger(KubeLinterController.class);
@Autowired
private KubeLinterService kubeLinterService;
@GetMapping("/status")
public ResponseEntity<Map<String, Object>> getStatus() {
Map<String, Object> response = new HashMap<>();
boolean available = kubeLinterService.isAvailable();
response.put("available", available);
response.put("service", "KubeLinter");
return ResponseEntity.ok(response);
}
@PostMapping("/analyze/files")
public ResponseEntity<Map<String, Object>> analyzeFiles(@RequestBody List<String> filePaths) {
Map<String, Object> response = new HashMap<>();
try {
var result = kubeLinterService.analyzeFiles(filePaths);
response.put("success", true);
response.put("totalFiles", result.getTotalFiles());
response.put("totalErrors", result.getTotalErrors());
response.put("severityCounts", result.getSeverityCounts());
response.put("hasErrors", result.getTotalErrors() > 0);
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("File analysis failed: {}", e.getMessage(), e);
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@PostMapping("/analyze/security")
public ResponseEntity<Map<String, Object>> analyzeSecurity(@RequestBody List<String> filePaths) {
Map<String, Object> response = new HashMap<>();
try {
var securityAnalysis = kubeLinterService.getSecurityAnalysis(filePaths);
response.put("success", true);
response.put("totalFiles", securityAnalysis.getTotalFiles());
response.put("securityIssueCount", securityAnalysis.getSecurityIssueCount());
response.put("securityIssuesByType", securityAnalysis.getSecurityIssuesByType());
response.put("hasSecurityIssues", securityAnalysis.getSecurityIssueCount() > 0);
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("Security analysis failed: {}", e.getMessage(), e);
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@PostMapping("/analyze/compliance")
public ResponseEntity<Map<String, Object>> analyzeCompliance(
@RequestBody List<String> filePaths,
@RequestParam(defaultValue = "CIS_KUBERNETES") String standard) {
Map<String, Object> response = new HashMap<>();
try {
var complianceStandard = KubeLinterService.ComplianceStandard.valueOf(standard);
var complianceReport = kubeLinterService.generateComplianceReport(filePaths, complianceStandard);
response.put("success", true);
response.put("standard", standard);
response.put("compliant", complianceReport.isCompliant());
response.put("complianceChecks", complianceReport.getComplianceChecks());
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("Compliance analysis failed: {}", e.getMessage(), e);
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@GetMapping("/checks")
public ResponseEntity<Map<String, Object>> getAvailableChecks() {
Map<String, Object> response = new HashMap<>();
try {
var checks = kubeLinterService.getAvailableChecks();
response.put("success", true);
response.put("checks", checks);
response.put("totalChecks", checks.size());
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("Failed to get available checks: {}", e.getMessage(), e);
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@PostMapping("/analyze/directory")
public ResponseEntity<Map<String, Object>> analyzeDirectory(@RequestParam String directoryPath) {
Map<String, Object> response = new HashMap<>();
try {
var result = kubeLinterService.analyzeDirectory(directoryPath);
response.put("success", true);
response.put("directory", directoryPath);
response.put("totalFiles", result.getTotalFiles());
response.put("totalErrors", result.getTotalErrors());
response.put("severityCounts", result.getSeverityCounts());
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("Directory analysis failed: {}", e.getMessage(), e);
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
}
CI/CD Pipeline Integration
Jenkins Pipeline Integration
package com.kubelinter.jenkins;
import com.kubelinter.service.KubeLinterService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
public class JenkinsKubeLinterIntegration {
private static final Logger logger = LoggerFactory.getLogger(JenkinsKubeLinterIntegration.class);
private final KubeLinterService kubeLinterService;
private final boolean failOnError;
private final boolean failOnWarning;
private final int maxErrors;
public JenkinsKubeLinterIntegration(KubeLinterService kubeLinterService,
boolean failOnError,
boolean failOnWarning,
int maxErrors) {
this.kubeLinterService = kubeLinterService;
this.failOnError = failOnError;
this.failOnWarning = failOnWarning;
this.maxErrors = maxErrors;
}
/**
* Execute KubeLinter in CI/CD pipeline
*/
public PipelineResult executeLintCheck(List<String> yamlFiles) {
PipelineResult result = new PipelineResult();
try {
var analysis = kubeLinterService.analyzeFiles(yamlFiles);
result.setTotalFiles(analysis.getTotalFiles());
result.setTotalErrors(analysis.getTotalErrors());
result.setAnalysisResult(analysis);
// Check if we should fail the build
boolean shouldFail = false;
String failureReason = "";
Long errorCount = analysis.getSeverityCounts().get("error");
Long warningCount = analysis.getSeverityCounts().get("warning");
if (failOnError && errorCount != null && errorCount > 0) {
shouldFail = true;
failureReason = String.format("Found %d error(s)", errorCount);
} else if (failOnWarning && warningCount != null && warningCount > 0) {
shouldFail = true;
failureReason = String.format("Found %d warning(s)", warningCount);
} else if (analysis.getTotalErrors() > maxErrors) {
shouldFail = true;
failureReason = String.format("Exceeded maximum allowed errors: %d > %d",
analysis.getTotalErrors(), maxErrors);
}
result.setPassed(!shouldFail);
result.setFailureReason(failureReason);
if (shouldFail) {
logger.warn("KubeLinter check failed: {}", failureReason);
} else {
logger.info("KubeLinter check passed");
}
} catch (IOException e) {
result.setPassed(false);
result.setFailureReason("KubeLinter execution failed: " + e.getMessage());
logger.error("KubeLinter pipeline check failed: {}", e.getMessage(), e);
}
return result;
}
/**
* Execute security-focused lint check
*/
public SecurityPipelineResult executeSecurityCheck(List<String> yamlFiles) {
SecurityPipelineResult result = new SecurityPipelineResult();
try {
var securityAnalysis = kubeLinterService.getSecurityAnalysis(yamlFiles);
result.setTotalFiles(securityAnalysis.getTotalFiles());
result.setSecurityIssueCount(securityAnalysis.getSecurityIssueCount());
result.setSecurityAnalysis(securityAnalysis);
// Fail if any security issues found
boolean hasSecurityIssues = securityAnalysis.getSecurityIssueCount() > 0;
result.setPassed(!hasSecurityIssues);
if (hasSecurityIssues) {
result.setFailureReason(String.format("Found %d security issues",
securityAnalysis.getSecurityIssueCount()));
logger.warn("Security check failed: {}", result.getFailureReason());
} else {
logger.info("Security check passed");
}
} catch (IOException e) {
result.setPassed(false);
result.setFailureReason("Security analysis failed: " + e.getMessage());
logger.error("Security pipeline check failed: {}", e.getMessage(), e);
}
return result;
}
public static class PipelineResult {
private boolean passed = true;
private String failureReason;
private int totalFiles;
private int totalErrors;
private KubeLinterService.AnalysisResult analysisResult;
// Getters and setters
public boolean isPassed() { return passed; }
public void setPassed(boolean passed) { this.passed = passed; }
public String getFailureReason() { return failureReason; }
public void setFailureReason(String failureReason) { this.failureReason = failureReason; }
public int getTotalFiles() { return totalFiles; }
public void setTotalFiles(int totalFiles) { this.totalFiles = totalFiles; }
public int getTotalErrors() { return totalErrors; }
public void setTotalErrors(int totalErrors) { this.totalErrors = totalErrors; }
public KubeLinterService.AnalysisResult getAnalysisResult() { return analysisResult; }
public void setAnalysisResult(KubeLinterService.AnalysisResult analysisResult) { this.analysisResult = analysisResult; }
}
public static class SecurityPipelineResult {
private boolean passed = true;
private String failureReason;
private int totalFiles;
private int securityIssueCount;
private KubeLinterService.SecurityAnalysis securityAnalysis;
// Getters and setters
public boolean isPassed() { return passed; }
public void setPassed(boolean passed) { this.passed = passed; }
public String getFailureReason() { return failureReason; }
public void setFailureReason(String failureReason) { this.failureReason = failureReason; }
public int getTotalFiles() { return totalFiles; }
public void setTotalFiles(int totalFiles) { this.totalFiles = totalFiles; }
public int getSecurityIssueCount() { return securityIssueCount; }
public void setSecurityIssueCount(int securityIssueCount) { this.securityIssueCount = securityIssueCount; }
public KubeLinterService.SecurityAnalysis getSecurityAnalysis() { return securityAnalysis; }
public void setSecurityAnalysis(KubeLinterService.SecurityAnalysis securityAnalysis) { this.securityAnalysis = securityAnalysis; }
}
}
Advanced Features
Custom Check Configuration
package com.kubelinter.advanced;
import com.kubelinter.service.KubeLinterService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class CustomKubeLinterConfig {
private static final Logger logger = LoggerFactory.getLogger(CustomKubeLinterConfig.class);
/**
* Configure KubeLinter for security-focused scanning
*/
public static KubeLinterService createSecurityFocusedService() {
KubeLinterService service = new KubeLinterService();
Set<String> securityChecks = new HashSet<>(Arrays.asList(
"privileged-containers",
"run-as-root",
"dangerous-capabilities",
"host-network",
"host-pid",
"host-ipc",
"non-read-only-root-fs",
"no-anti-affinity",
"no-liveness-probe",
"no-readiness-probe"
));
service.setIncludedChecks(securityChecks);
logger.info("Configured security-focused KubeLinter with {} checks", securityChecks.size());
return service;
}
/**
* Configure KubeLinter for compliance scanning
*/
public static KubeLinterService createComplianceService() {
KubeLinterService service = new KubeLinterService();
Set<String> excludedChecks = new HashSet<>(Arrays.asList(
"deprecated-service-account-field",
"no-extra-args"
));
service.setExcludedChecks(excludedChecks);
logger.info("Configured compliance-focused KubeLinter, excluded {} checks", excludedChecks.size());
return service;
}
/**
* Configure KubeLinter for production readiness
*/
public static KubeLinterService createProductionReadyService() {
KubeLinterService service = new KubeLinterService();
Set<String> productionChecks = new HashSet<>(Arrays.asList(
"resource-limits",
"liveness-probe",
"readiness-probe",
"image-tag",
"non-existent-service-account",
"cpu-requests",
"memory-requests"
));
service.setIncludedChecks(productionChecks);
logger.info("Configured production-ready KubeLinter with {} checks", productionChecks.size());
return service;
}
}
Testing
Unit Tests
package com.kubelinter.test;
import com.kubelinter.client.KubeLinterClient;
import com.kubelinter.service.KubeLinterService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.IOException;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
class KubeLinterServiceTest {
@Mock
private KubeLinterClient mockClient;
private KubeLinterService kubeLinterService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
kubeLinterService = new KubeLinterService(mockClient);
}
@Test
void testServiceAvailable() {
when(mockClient.isAvailable()).thenReturn(true);
assertTrue(kubeLinterService.isAvailable());
}
@Test
void testFileAnalysis() throws IOException {
// Mock KubeLinter response
// Test analysis result parsing and processing
}
@Test
void testSecurityAnalysis() throws IOException {
// Test security-focused analysis
// Verify filtering of security-relevant issues
}
@Test
void testComplianceReport() throws IOException {
// Test compliance report generation
// Verify compliance standard evaluation
}
}
Application Properties
Spring Boot Configuration
# KubeLinter Configuration kubelinter.path=kubelinter kubelinter.exclude.checks=deprecated-service-account-field,no-extra-args kubelinter.include.checks= # Pipeline Settings pipeline.fail.on.error=true pipeline.fail.on.warning=false pipeline.max.errors=10 # Logging logging.level.com.kubelinter=INFO
Conclusion
This comprehensive KubeLinter integration for Java provides:
- Complete KubeLinter client for YAML validation
- Security-focused analysis with severity filtering
- Compliance reporting against standards like CIS Kubernetes
- CI/CD pipeline integration with configurable failure thresholds
- Custom check configuration for specific use cases
- Spring Boot integration for easy deployment
Key benefits:
- Automated Kubernetes manifest validation in development pipelines
- Security misconfiguration detection before deployment
- Compliance enforcement with organizational standards
- Customizable check sets for different environments
- Comprehensive reporting with severity categorization
This implementation enables Java applications to leverage KubeLinter for Kubernetes security and best practices, making it ideal for DevOps teams implementing security in their Kubernetes workflows.