Introduction to AppArmor
AppArmor (Application Armor) is a Linux security module that allows system administrators to restrict programs' capabilities with per-program profiles. While Java applications don't directly interact with AppArmor, you can manage AppArmor profiles and integrate them with Java applications for enhanced security.
AppArmor Concepts for Java Applications
- Profile Enforcement - Restricting application capabilities
- Policy Management - Creating and managing security profiles
- Java Native Interface (JNI) - Interacting with system-level security
- Process Confinement - Restricting Java processes capabilities
Dependencies and Setup
Maven Configuration
<properties>
<jna.version>5.13.0</jna.version>
<commons-exec.version>1.3</commons-exec.version>
</properties>
<dependencies>
<!-- JNA for native system calls -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>${jna.version}</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>${jna.version}</version>
</dependency>
<!-- Apache Commons Exec for process execution -->
<dependency>
<groupId>org.apache.commons</groupId>
artifactId>commons-exec</artifactId>
<version>${commons-exec.version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>
Core AppArmor Integration
AppArmor Native Interface
package com.apparmor.integration;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
public interface AppArmorJNA extends Library {
AppArmorJNA INSTANCE = Native.load("apparmor", AppArmorJNA.class);
int aa_change_hat(String hat, long token);
int aa_change_profile(String profile);
int aa_find_mountpoint(String mountpoint, Pointer buffer, IntByReference size);
int aa_getcon(String[] label, String[] mode);
}
public class AppArmorNativeAccess {
private static final AppArmorJNA appArmor = AppArmorJNA.INSTANCE;
public static boolean changeProfile(String profile) {
int result = appArmor.aa_change_profile(profile);
return result == 0;
}
public static boolean changeHat(String hat, long token) {
int result = appArmor.aa_change_hat(hat, token);
return result == 0;
}
public static String getCurrentContext() {
String[] label = new String[1];
String[] mode = new String[1];
int result = appArmor.aa_getcon(label, mode);
if (result == 0) {
return label[0] + " (" + mode[0] + ")";
}
return null;
}
}
AppArmor Profile Manager
package com.apparmor.manager;
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.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public class AppArmorProfileManager {
private static final Logger logger = LoggerFactory.getLogger(AppArmorProfileManager.class);
private static final String APPARMOR_PROFILES_DIR = "/etc/apparmor.d";
private static final String APPARMOR_STATUS_CMD = "aa-status";
private static final String APPARMOR_PARSER_CMD = "apparmor_parser";
/**
* Check if AppArmor is enabled on the system
*/
public static boolean isAppArmorEnabled() {
try {
String output = executeCommand(APPARMOR_STATUS_CMD);
return output.contains("apparmor module is loaded") &&
output.contains("apparmor filesystem is mounted");
} catch (Exception e) {
logger.warn("AppArmor status check failed: {}", e.getMessage());
return false;
}
}
/**
* Get loaded AppArmor profiles
*/
public static List<String> getLoadedProfiles() throws IOException {
String output = executeCommand(APPARMOR_STATUS_CMD);
List<String> profiles = new ArrayList<>();
// Parse the output to extract profile names
String[] lines = output.split("\n");
boolean inProfilesSection = false;
for (String line : lines) {
if (line.contains("profiles are loaded")) {
inProfilesSection = true;
continue;
}
if (inProfilesSection && line.contains("profiles are in enforce mode")) {
break;
}
if (inProfilesSection && !line.trim().isEmpty()) {
String profileName = line.trim().split(" ")[0];
profiles.add(profileName);
}
}
return profiles;
}
/**
* Create a new AppArmor profile for a Java application
*/
public static boolean createJavaProfile(String profileName, String javaExecutable,
String applicationPath) throws IOException {
String profileContent = generateJavaProfile(profileName, javaExecutable, applicationPath);
Path profilePath = Paths.get(APPARMOR_PROFILES_DIR, profileName);
// Write profile to file
Files.write(profilePath, profileContent.getBytes());
// Load the profile
return loadProfile(profilePath.toString());
}
/**
* Generate AppArmor profile for Java application
*/
private static String generateJavaProfile(String profileName, String javaExecutable,
String applicationPath) {
StringBuilder profile = new StringBuilder();
profile.append("#include <tunables/global>\n\n");
profile.append("profile ").append(profileName).append(" ").append(javaExecutable).append(" {\n");
profile.append(" #include <abstractions/base>\n");
profile.append(" #include <abstractions/openssl>\n\n");
// Java executable access
profile.append(" ").append(javaExecutable).append(" mr,\n\n");
// Application JAR/WAR files
profile.append(" ").append(applicationPath).append("/** r,\n");
profile.append(" ").append(applicationPath).append("/**/*.jar r,\n");
profile.append(" ").append(applicationPath).append("/**/*.class r,\n\n");
// Temporary files
profile.append(" /tmp/** rw,\n");
profile.append(" /var/tmp/** rw,\n\n");
// Log files
profile.append(" /var/log/").append(profileName).append("/** rw,\n");
profile.append(" ").append(applicationPath).append("/logs/** rw,\n\n");
// Network access
profile.append(" network inet stream,\n");
profile.append(" network inet6 stream,\n\n");
// Database access (if needed)
profile.append(" /run/mysqld/mysqld.sock rw,\n\n");
// JVM required paths
profile.append(" /usr/lib/jvm/** mr,\n");
profile.append(" /etc/java/** r,\n");
profile.append(" /proc/** r,\n\n");
profile.append(" # Capabilities\n");
profile.append(" capability setgid,\n");
profile.append(" capability setuid,\n\n");
profile.append(" # Deny everything else\n");
profile.append(" deny /** w,\n");
profile.append("}\n");
return profile.toString();
}
/**
* Load AppArmor profile
*/
public static boolean loadProfile(String profilePath) throws IOException {
CommandLine cmd = CommandLine.parse(APPARMOR_PARSER_CMD);
cmd.addArgument("-r"); // Replace existing profile
cmd.addArgument(profilePath);
return executeCommand(cmd) == 0;
}
/**
* Unload AppArmor profile
*/
public static boolean unloadProfile(String profileName) throws IOException {
CommandLine cmd = CommandLine.parse(APPARMOR_PARSER_CMD);
cmd.addArgument("-R"); // Remove profile
cmd.addArgument(Paths.get(APPARMOR_PROFILES_DIR, profileName).toString());
return executeCommand(cmd) == 0;
}
/**
* Execute shell command and return output
*/
private static String executeCommand(String command) throws IOException {
CommandLine cmd = CommandLine.parse(command);
return executeCommandWithOutput(cmd);
}
private static String executeCommandWithOutput(CommandLine cmd) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
DefaultExecutor executor = new DefaultExecutor();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
executor.setStreamHandler(streamHandler);
int exitValue = executor.execute(cmd);
if (exitValue != 0) {
throw new IOException("Command failed with exit code: " + exitValue);
}
return outputStream.toString();
}
private static int executeCommand(CommandLine cmd) throws IOException {
DefaultExecutor executor = new DefaultExecutor();
return executor.execute(cmd);
}
}
Java Application Security Wrapper
AppArmor Security Manager
package com.apparmor.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;
public class AppArmorSecurityManager {
private static final Logger logger = LoggerFactory.getLogger(AppArmorSecurityManager.class);
private final String profileName;
private final String applicationName;
private final Path applicationPath;
private long hatToken;
public AppArmorSecurityManager(String applicationName, Path applicationPath) {
this.applicationName = applicationName;
this.applicationPath = applicationPath;
this.profileName = applicationName.replaceAll("[^a-zA-Z0-9_-]", "_");
this.hatToken = new SecureRandom().nextLong();
}
/**
* Initialize AppArmor security for the application
*/
public boolean initialize() {
if (!AppArmorProfileManager.isAppArmorEnabled()) {
logger.warn("AppArmor is not enabled on this system");
return false;
}
try {
// Create and load profile
String javaExecutable = System.getProperty("java.home") + "/bin/java";
boolean success = AppArmorProfileManager.createJavaProfile(
profileName, javaExecutable, applicationPath.toString());
if (success) {
logger.info("AppArmor profile '{}' loaded successfully", profileName);
return true;
} else {
logger.error("Failed to load AppArmor profile '{}'", profileName);
return false;
}
} catch (Exception e) {
logger.error("Error initializing AppArmor security: {}", e.getMessage(), e);
return false;
}
}
/**
* Switch to a more restrictive hat (subprofile)
*/
public boolean enterRestrictedMode(String hatName) {
try {
boolean success = AppArmorNativeAccess.changeHat(hatName, hatToken);
if (success) {
logger.info("Entered restricted mode with hat '{}'", hatName);
} else {
logger.warn("Failed to enter restricted mode with hat '{}'", hatName);
}
return success;
} catch (Exception e) {
logger.error("Error entering restricted mode: {}", e.getMessage(), e);
return false;
}
}
/**
* Exit restricted mode
*/
public boolean exitRestrictedMode() {
try {
boolean success = AppArmorNativeAccess.changeHat(null, hatToken);
if (success) {
logger.info("Exited restricted mode");
}
return success;
} catch (Exception e) {
logger.error("Error exiting restricted mode: {}", e.getMessage(), e);
return false;
}
}
/**
* Get current AppArmor context
*/
public String getSecurityContext() {
return AppArmorNativeAccess.getCurrentContext();
}
/**
* Clean up AppArmor profile
*/
public void cleanup() {
try {
AppArmorProfileManager.unloadProfile(profileName);
logger.info("AppArmor profile '{}' unloaded", profileName);
} catch (Exception e) {
logger.error("Error cleaning up AppArmor profile: {}", e.getMessage(), e);
}
}
/**
* Create hat profile for specific operations
*/
public boolean createHatProfile(String hatName, String operations) {
String hatProfile = generateHatProfile(hatName, operations);
Path hatPath = Paths.get("/etc/apparmor.d", profileName + "." + hatName);
try {
Files.write(hatPath, hatProfile.getBytes());
return AppArmorProfileManager.loadProfile(hatPath.toString());
} catch (IOException e) {
logger.error("Error creating hat profile: {}", e.getMessage(), e);
return false;
}
}
private String generateHatProfile(String hatName, String operations) {
return String.format(
"hat %s.%s {\n" +
" #include <abstractions/base>\n" +
" %s\n" +
"}\n",
profileName, hatName, operations
);
}
}
Spring Boot Integration
AppArmor Configuration
package com.apparmor.config;
import com.apparmor.security.AppArmorSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PreDestroy;
import java.nio.file.Paths;
@Configuration
public class AppArmorConfig {
private static final Logger logger = LoggerFactory.getLogger(AppArmorConfig.class);
@Value("${app.name:myapp}")
private String appName;
@Value("${app.path:./}")
private String appPath;
@Value("${apparmor.enabled:false}")
private boolean appArmorEnabled;
private AppArmorSecurityManager appArmorManager;
@Bean
public AppArmorSecurityManager appArmorSecurityManager() {
if (!appArmorEnabled) {
logger.info("AppArmor security is disabled");
return null;
}
appArmorManager = new AppArmorSecurityManager(appName, Paths.get(appPath));
boolean initialized = appArmorManager.initialize();
if (initialized) {
logger.info("AppArmor security initialized successfully");
// Create hat profiles for different operations
createHatProfiles();
} else {
logger.warn("AppArmor security initialization failed");
}
return appArmorManager;
}
private void createHatProfiles() {
// File operations hat
appArmorManager.createHatProfile("file_ops",
"/home/app/data/** rw,\n" +
"deny /** w,");
// Network operations hat
appArmorManager.createHatProfile("network_ops",
"network inet stream,\n" +
"deny /** w,");
// Database operations hat
appArmorManager.createHatProfile("db_ops",
"/var/run/mysqld/mysqld.sock rw,\n" +
"deny /** w,");
}
@PreDestroy
public void cleanup() {
if (appArmorManager != null) {
appArmorManager.cleanup();
}
}
}
Secure Service Implementation
package com.apparmor.service;
import com.apparmor.security.AppArmorSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Service
public class SecureFileService {
private static final Logger logger = LoggerFactory.getLogger(SecureFileService.class);
@Autowired(required = false)
private AppArmorSecurityManager appArmorManager;
/**
* Securely read file with AppArmor hat
*/
public String readFileSecurely(String filePath) throws IOException {
if (appArmorManager != null) {
appArmorManager.enterRestrictedMode("file_ops");
}
try {
Path path = Paths.get(filePath);
if (!Files.exists(path)) {
throw new IOException("File not found: " + filePath);
}
return new String(Files.readAllBytes(path));
} finally {
if (appArmorManager != null) {
appArmorManager.exitRestrictedMode();
}
}
}
/**
* Securely write file with AppArmor hat
*/
public void writeFileSecurely(String filePath, String content) throws IOException {
if (appArmorManager != null) {
appArmorManager.enterRestrictedMode("file_ops");
}
try {
Path path = Paths.get(filePath);
Files.createDirectories(path.getParent());
Files.write(path, content.getBytes());
} finally {
if (appArmorManager != null) {
appArmorManager.exitRestrictedMode();
}
}
}
/**
* Perform secure network operation
*/
public void performSecureNetworkOperation() {
if (appArmorManager != null) {
appArmorManager.enterRestrictedMode("network_ops");
}
try {
// Perform network operations here
logger.info("Performing secure network operation");
} finally {
if (appArmorManager != null) {
appArmorManager.exitRestrictedMode();
}
}
}
/**
* Get current security context
*/
public String getSecurityContext() {
if (appArmorManager != null) {
return appArmorManager.getSecurityContext();
}
return "AppArmor not enabled";
}
}
REST Controller with AppArmor Security
package com.apparmor.controller;
import com.apparmor.service.SecureFileService;
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.Map;
@RestController
@RequestMapping("/api/secure")
public class SecureController {
@Autowired
private SecureFileService secureFileService;
@GetMapping("/file")
public ResponseEntity<Map<String, Object>> readFile(@RequestParam String path) {
Map<String, Object> response = new HashMap<>();
try {
String content = secureFileService.readFileSecurely(path);
response.put("success", true);
response.put("content", content);
response.put("securityContext", secureFileService.getSecurityContext());
return ResponseEntity.ok(response);
} catch (Exception e) {
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@PostMapping("/file")
public ResponseEntity<Map<String, Object>> writeFile(
@RequestParam String path,
@RequestBody String content) {
Map<String, Object> response = new HashMap<>();
try {
secureFileService.writeFileSecurely(path, content);
response.put("success", true);
response.put("message", "File written successfully");
response.put("securityContext", secureFileService.getSecurityContext());
return ResponseEntity.ok(response);
} catch (Exception e) {
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@GetMapping("/network")
public ResponseEntity<Map<String, Object>> performNetworkOperation() {
Map<String, Object> response = new HashMap<>();
try {
secureFileService.performSecureNetworkOperation();
response.put("success", true);
response.put("message", "Network operation completed");
response.put("securityContext", secureFileService.getSecurityContext());
return ResponseEntity.ok(response);
} catch (Exception e) {
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@GetMapping("/status")
public ResponseEntity<Map<String, Object>> getSecurityStatus() {
Map<String, Object> response = new HashMap<>();
response.put("securityContext", secureFileService.getSecurityContext());
response.put("appArmorEnabled", secureFileService.getSecurityContext().contains("AppArmor"));
return ResponseEntity.ok(response);
}
}
Advanced AppArmor Profile Templates
Web Application Profile Template
package com.apparmor.templates;
public class AppArmorTemplates {
public static String generateWebAppProfile(String profileName, String appPath,
String webRoot, int port) {
return String.format(
"#include <tunables/global>\n\n" +
"profile %s /usr/bin/java {\n" +
" #include <abstractions/base>\n" +
" #include <abstractions/openssl>\n" +
" #include <abstractions/nameservice>\n\n" +
" # Java runtime\n" +
" /usr/lib/jvm/** mr,\n" +
" %s/** r,\n\n" +
" # Web application files\n" +
" %s/** r,\n" +
" %s/WEB-INF/** r,\n" +
" %s/WEB-INF/lib/*.jar r,\n\n" +
" # Temporary files\n" +
" /tmp/** rw,\n" +
" /var/tmp/** rw,\n\n" +
" # Log files\n" +
" /var/log/%s/** rw,\n" +
" %s/logs/** rw,\n\n" +
" # Network\n" +
" network inet stream,\n" +
" network inet6 stream,\n\n" +
" # Port binding\n" +
" /proc/sys/net/ipv4/ip_local_port_range r,\n" +
" /proc/%s/root/proc/sys/net/ipv4/ip_local_port_range r,\n\n" +
" # Database access\n" +
" /run/mysqld/mysqld.sock rw,\n" +
" /var/run/mysqld/mysqld.sock rw,\n\n" +
" # System resources\n" +
" /proc/** r,\n" +
" /sys/** r,\n\n" +
" # Capabilities\n" +
" capability setgid,\n" +
" capability setuid,\n\n" +
" # Deny sensitive paths\n" +
" deny /etc/shadow r,\n" +
" deny /root/** rw,\n" +
" deny /home/*/.** rw,\n" +
"}\n",
profileName, appPath, webRoot, webRoot, webRoot,
profileName, appPath, profileName
);
}
public static String generateMicroserviceProfile(String profileName, String appPath,
String[] allowedEndpoints) {
StringBuilder profile = new StringBuilder();
profile.append("#include <tunables/global>\n\n");
profile.append("profile ").append(profileName).append(" /usr/bin/java {\n");
profile.append(" #include <abstractions/base>\n");
profile.append(" #include <abstractions/openssl>\n\n");
// Application files
profile.append(" ").append(appPath).append("/** r,\n");
profile.append(" ").append(appPath).append("/*.jar r,\n\n");
// Network restrictions
profile.append(" network inet stream,\n");
profile.append(" network inet6 stream,\n\n");
// Specific endpoint access
for (String endpoint : allowedEndpoints) {
profile.append(" # Access to ").append(endpoint).append("\n");
}
profile.append(" # Logging\n");
profile.append(" /var/log/").append(profileName).append("/** rw,\n\n");
profile.append(" # Deny unnecessary access\n");
profile.append(" deny /etc/passwd w,\n");
profile.append(" deny /etc/shadow r,\n");
profile.append("}\n");
return profile.toString();
}
}
Monitoring and Auditing
AppArmor Audit Service
package com.apparmor.audit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Service
public class AppArmorAuditService {
private static final Logger logger = LoggerFactory.getLogger(AppArmorAuditService.class);
private static final String AUDIT_LOG_PATH = "/var/log/audit/audit.log";
private static final String KERNEL_LOG_PATH = "/var/log/kern.log";
/**
* Monitor AppArmor denials
*/
@Scheduled(fixedRate = 30000) // Every 30 seconds
public void monitorAppArmorDenials() {
try {
List<String> denials = getRecentAppArmorDenials();
if (!denials.isEmpty()) {
logger.warn("Found {} AppArmor denials:", denials.size());
for (String denial : denials) {
logger.warn("AppArmor Denial: {}", denial);
// Send alert, log to security system, etc.
}
}
} catch (Exception e) {
logger.error("Error monitoring AppArmor denials: {}", e.getMessage());
}
}
/**
* Get recent AppArmor denials from logs
*/
public List<String> getRecentAppArmorDenials() throws IOException {
List<String> denials = new ArrayList<>();
// Check audit log
if (Files.exists(Paths.get(AUDIT_LOG_PATH))) {
denials.addAll(parseAuditLog(AUDIT_LOG_PATH));
}
// Check kernel log
if (Files.exists(Paths.get(KERNEL_LOG_PATH))) {
denials.addAll(parseKernelLog(KERNEL_LOG_PATH));
}
return denials;
}
private List<String> parseAuditLog(String logPath) throws IOException {
List<String> denials = new ArrayList<>();
Path path = Paths.get(logPath);
// Read last 1000 lines (adjust as needed)
List<String> lines = Files.readAllLines(path);
int start = Math.max(0, lines.size() - 1000);
for (int i = start; i < lines.size(); i++) {
String line = lines.get(i);
if (line.contains("apparmor") && line.contains("DENIED")) {
denials.add(line);
}
}
return denials;
}
private List<String> parseKernelLog(String logPath) throws IOException {
List<String> denials = new ArrayList<>();
Path path = Paths.get(logPath);
List<String> lines = Files.readAllLines(path);
int start = Math.max(0, lines.size() - 1000);
for (int i = start; i < lines.size(); i++) {
String line = lines.get(i);
if (line.contains("apparmor") &&
(line.contains("denied") || line.contains("DENIED"))) {
denials.add(line);
}
}
return denials;
}
/**
* Generate security report
*/
public SecurityReport generateSecurityReport() throws IOException {
List<String> recentDenials = getRecentAppArmorDenials();
SecurityReport report = new SecurityReport();
report.setTimestamp(LocalDateTime.now());
report.setTotalDenials(recentDenials.size());
report.setRecentDenials(recentDenials);
report.setAppArmorEnabled(AppArmorProfileManager.isAppArmorEnabled());
return report;
}
public static class SecurityReport {
private LocalDateTime timestamp;
private int totalDenials;
private List<String> recentDenials;
private boolean appArmorEnabled;
// Getters and setters
public LocalDateTime getTimestamp() { return timestamp; }
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
public int getTotalDenials() { return totalDenials; }
public void setTotalDenials(int totalDenials) { this.totalDenials = totalDenials; }
public List<String> getRecentDenials() { return recentDenials; }
public void setRecentDenials(List<String> recentDenials) { this.recentDenials = recentDenials; }
public boolean isAppArmorEnabled() { return appArmorEnabled; }
public void setAppArmorEnabled(boolean appArmorEnabled) { this.appArmorEnabled = appArmorEnabled; }
}
}
Testing AppArmor Integration
Test Configuration
package com.apparmor.test;
import com.apparmor.manager.AppArmorProfileManager;
import com.apparmor.security.AppArmorSecurityManager;
import org.junit.jupiter.api.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class AppArmorIntegrationTest {
private static final Logger logger = LoggerFactory.getLogger(AppArmorIntegrationTest.class);
private AppArmorSecurityManager securityManager;
private static final String TEST_PROFILE = "test_java_app";
@BeforeAll
void setUp() {
// Only run tests if AppArmor is available
assumeTrue(AppArmorProfileManager.isAppArmorEnabled(),
"AppArmor is not enabled on this system");
securityManager = new AppArmorSecurityManager(TEST_PROFILE, Paths.get("."));
}
@Test
void testAppArmorInitialization() {
boolean initialized = securityManager.initialize();
Assertions.assertTrue(initialized, "AppArmor should initialize successfully");
}
@Test
void testProfileLoading() throws IOException {
List<String> profiles = AppArmorProfileManager.getLoadedProfiles();
Assertions.assertTrue(profiles.contains(TEST_PROFILE),
"Test profile should be loaded");
}
@Test
void testHatOperations() {
boolean hatCreated = securityManager.createHatProfile("test_hat",
"/tmp/test.file rw,\ndeny /** w,");
Assertions.assertTrue(hatCreated, "Hat profile should be created");
boolean hatEntered = securityManager.enterRestrictedMode("test_hat");
Assertions.assertTrue(hatEntered, "Should enter hat successfully");
boolean hatExited = securityManager.exitRestrictedMode();
Assertions.assertTrue(hatExited, "Should exit hat successfully");
}
@Test
void testSecurityContext() {
String context = securityManager.getSecurityContext();
Assertions.assertNotNull(context, "Security context should not be null");
logger.info("Current security context: {}", context);
}
@AfterAll
void tearDown() {
if (securityManager != null) {
securityManager.cleanup();
}
}
}
Application Properties
Spring Boot Configuration
# AppArmor Configuration apparmor.enabled=true app.name=my_secure_java_app app.path=/opt/myapp # Logging logging.level.com.apparmor=DEBUG logging.file.path=/var/log/myapp # Security security.apparmor.audit.enabled=true security.apparmor.monitoring.interval=30000
Conclusion
This comprehensive AppArmor integration for Java provides:
- Native AppArmor integration using JNA
- Profile management for Java applications
- Spring Boot integration for easy configuration
- Hat profiles for fine-grained access control
- Security monitoring and auditing
- REST API for security operations
Key benefits:
- Enhanced security through mandatory access control
- Fine-grained permissions for different operations
- Runtime security context switching
- Comprehensive auditing and monitoring
- Production-ready configuration templates
This implementation allows Java applications to leverage Linux AppArmor security while maintaining the flexibility and portability of Java applications.