Overview
Istio mTLS (mutual TLS) provides service-to-service authentication and encryption. Java applications can leverage Istio's mTLS capabilities while implementing additional security checks and policy enforcement.
1. Basic mTLS Configuration Detection
import javax.net.ssl.*;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
public class IstioMTLSDetector {
public static void checkMTLSConnection() {
try {
SSLSession session = ((SSLSocket) SSLSocketFactory.getDefault().createSocket()).getSession();
System.out.println("=== mTLS Connection Details ===");
System.out.println("Protocol: " + session.getProtocol());
System.out.println("Cipher Suite: " + session.getCipherSuite());
// Check if client certificate is present (mTLS)
Certificate[] clientCerts = session.getPeerCertificates();
if (clientCerts != null && clientCerts.length > 0) {
System.out.println("✓ mTLS Enabled: Client certificate present");
analyzeClientCertificate((X509Certificate) clientCerts[0]);
} else {
System.out.println("✗ mTLS Not Active: No client certificate");
}
} catch (Exception e) {
System.err.println("Error checking mTLS: " + e.getMessage());
}
}
private static void analyzeClientCertificate(X509Certificate cert) {
try {
System.out.println("Client Certificate Details:");
System.out.println(" Subject: " + cert.getSubjectX500Principal());
System.out.println(" Issuer: " + cert.getIssuerX500Principal());
System.out.println(" Serial: " + cert.getSerialNumber());
System.out.println(" Valid Until: " + cert.getNotAfter());
// Check SPIFFE ID for service identity
String spiffeId = extractSpiffeId(cert);
if (spiffeId != null) {
System.out.println(" SPIFFE ID: " + spiffeId);
}
} catch (Exception e) {
System.err.println("Error analyzing certificate: " + e.getMessage());
}
}
private static String extractSpiffeId(X509Certificate cert) {
// SPIFFE ID is typically in the URI SAN extension
try {
Collection<List<?>> sans = cert.getSubjectAlternativeNames();
if (sans != null) {
for (List<?> san : sans) {
if (san.size() >= 2) {
Integer type = (Integer) san.get(0);
if (type == 6) { // URI type
String uri = (String) san.get(1);
if (uri.startsWith("spiffe://")) {
return uri;
}
}
}
}
}
} catch (Exception e) {
// Ignore extraction errors
}
return null;
}
}
2. Spring Boot mTLS Configuration
Application Properties
# application.yml server: port: 8443 ssl: key-store: classpath:keystore.p12 key-store-password: changeit key-store-type: PKCS12 key-alias: server client-auth: need trust-store: classpath:truststore.p12 trust-store-password: changeit trust-store-type: PKCS12 spring: application: name: secure-service security: require-ssl: true # Custom mTLS properties mtls: enforcement: enabled: true require-spiffe: true allowed-spiffe-patterns: - "spiffe://cluster.local/ns/.*/sa/.*" require-specific-issuer: "spiffe://root.example.com"
Spring Security mTLS Configuration
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
import javax.servlet.http.HttpServletRequest;
import java.security.cert.X509Certificate;
import java.util.regex.Pattern;
@Configuration
@EnableWebSecurity
public class MTlsSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.x509(x509 -> x509
.x509AuthenticationFilter(x509AuthenticationFilter())
.subjectPrincipalRegex("CN=(.*?)(?:,|$)")
)
.ssl(ssl -> ssl.requireSecure(true));
return http.build();
}
@Bean
public X509AuthenticationFilter x509AuthenticationFilter() {
X509AuthenticationFilter filter = new X509AuthenticationFilter();
filter.setAuthenticationManager(authentication -> {
if (authentication.getCredentials() instanceof X509Certificate) {
X509Certificate cert = (X509Certificate) authentication.getCredentials();
return authenticateWithCertificate(cert);
}
throw new RuntimeException("Invalid authentication");
});
return filter;
}
private Authentication authenticateWithCertificate(X509Certificate cert) {
try {
// Extract SPIFFE ID
String spiffeId = extractSpiffeId(cert);
// Validate SPIFFE ID
if (!isValidSpiffeId(spiffeId)) {
throw new RuntimeException("Invalid SPIFFE ID: " + spiffeId);
}
// Extract service account from SPIFFE ID
String serviceAccount = extractServiceAccount(spiffeId);
String role = determineRole(serviceAccount);
User user = new User(serviceAccount, "",
AuthorityUtils.createAuthorityList("ROLE_" + role));
return new PreAuthenticatedAuthenticationToken(user, cert,
AuthorityUtils.createAuthorityList("ROLE_" + role));
} catch (Exception e) {
throw new RuntimeException("Certificate authentication failed", e);
}
}
private boolean isValidSpiffeId(String spiffeId) {
if (spiffeId == null) return false;
Pattern pattern = Pattern.compile("^spiffe://cluster\\.local/ns/[^/]+/sa/[^/]+$");
return pattern.matcher(spiffeId).matches();
}
private String extractServiceAccount(String spiffeId) {
// spiffe://cluster.local/ns/default/sa/my-service
String[] parts = spiffeId.split("/");
return parts[parts.length - 1]; // Returns "my-service"
}
private String determineRole(String serviceAccount) {
// Map service accounts to roles
if (serviceAccount.endsWith("-admin")) {
return "ADMIN";
} else if (serviceAccount.endsWith("-reader")) {
return "READER";
}
return "USER";
}
}
3. Advanced mTLS Policy Enforcement
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.*;
@Component
public class MTlsPolicyEnforcementFilter extends OncePerRequestFilter {
private final PolicyEngine policyEngine;
private final CertificateValidator certificateValidator;
public MTlsPolicyEnforcementFilter(PolicyEngine policyEngine,
CertificateValidator certificateValidator) {
this.policyEngine = policyEngine;
this.certificateValidator = certificateValidator;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// Extract client certificate from request
X509Certificate[] certificates = (X509Certificate[])
request.getAttribute("javax.servlet.request.X509Certificate");
if (certificates == null || certificates.length == 0) {
sendError(response, "No client certificate provided", HttpServletResponse.SC_UNAUTHORIZED);
return;
}
X509Certificate clientCert = certificates[0];
try {
// Validate certificate
CertificateValidationResult validationResult =
certificateValidator.validateCertificate(clientCert);
if (!validationResult.isValid()) {
sendError(response, "Invalid client certificate: " +
validationResult.getErrorMessage(), HttpServletResponse.SC_UNAUTHORIZED);
return;
}
// Extract identity information
ServiceIdentity identity = extractServiceIdentity(clientCert);
// Apply policies
PolicyDecision decision = policyEngine.evaluate(request, identity);
if (!decision.isAllowed()) {
sendError(response, "Access denied: " + decision.getReason(),
HttpServletResponse.SC_FORBIDDEN);
return;
}
// Add identity to request for downstream use
request.setAttribute("serviceIdentity", identity);
filterChain.doFilter(request, response);
} catch (Exception e) {
sendError(response, "Internal server error",
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
private ServiceIdentity extractServiceIdentity(X509Certificate cert) {
String spiffeId = extractSpiffeId(cert);
String namespace = extractNamespace(spiffeId);
String serviceAccount = extractServiceAccount(spiffeId);
return new ServiceIdentity(spiffeId, namespace, serviceAccount,
cert.getSubjectX500Principal().getName());
}
private void sendError(HttpServletResponse response, String message, int status)
throws IOException {
response.setStatus(status);
response.setContentType("application/json");
response.getWriter().write(String.format(
"{\"error\": \"%s\", \"status\": %d}", message, status));
}
}
// Supporting classes
class ServiceIdentity {
private final String spiffeId;
private final String namespace;
private final String serviceAccount;
private final String subjectDN;
public ServiceIdentity(String spiffeId, String namespace,
String serviceAccount, String subjectDN) {
this.spiffeId = spiffeId;
this.namespace = namespace;
this.serviceAccount = serviceAccount;
this.subjectDN = subjectDN;
}
// Getters
public String getSpiffeId() { return spiffeId; }
public String getNamespace() { return namespace; }
public String getServiceAccount() { return serviceAccount; }
public String getSubjectDN() { return subjectDN; }
}
class CertificateValidationResult {
private final boolean valid;
private final String errorMessage;
public CertificateValidationResult(boolean valid, String errorMessage) {
this.valid = valid;
this.errorMessage = errorMessage;
}
public boolean isValid() { return valid; }
public String getErrorMessage() { return errorMessage; }
}
class PolicyDecision {
private final boolean allowed;
private final String reason;
public PolicyDecision(boolean allowed, String reason) {
this.allowed = allowed;
this.reason = reason;
}
public boolean isAllowed() { return allowed; }
public String getReason() { return reason; }
}
4. Policy Engine Implementation
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.regex.Pattern;
@Component
public class PolicyEngine {
private final List<AccessPolicy> policies;
public PolicyEngine() {
this.policies = Arrays.asList(
new NamespaceIsolationPolicy(),
new ServiceAccountPolicy(),
new TimeBasedPolicy(),
new ResourceAccessPolicy()
);
}
public PolicyDecision evaluate(HttpServletRequest request, ServiceIdentity identity) {
List<String> denialReasons = new ArrayList<>();
for (AccessPolicy policy : policies) {
PolicyDecision decision = policy.evaluate(request, identity);
if (!decision.isAllowed()) {
denialReasons.add(decision.getReason());
}
}
if (denialReasons.isEmpty()) {
return new PolicyDecision(true, "Access granted");
} else {
return new PolicyDecision(false,
String.join("; ", denialReasons));
}
}
}
interface AccessPolicy {
PolicyDecision evaluate(HttpServletRequest request, ServiceIdentity identity);
}
class NamespaceIsolationPolicy implements AccessPolicy {
@Override
public PolicyDecision evaluate(HttpServletRequest request, ServiceIdentity identity) {
String requestPath = request.getRequestURI();
String callerNamespace = identity.getNamespace();
// Extract target namespace from request path or headers
String targetNamespace = extractTargetNamespace(request);
if (targetNamespace != null && !callerNamespace.equals(targetNamespace)) {
// Allow cross-namespace access only for specific endpoints
if (!isCrossNamespaceAllowed(requestPath)) {
return new PolicyDecision(false,
"Cross-namespace access not allowed to " + requestPath);
}
}
return new PolicyDecision(true, "Namespace check passed");
}
private String extractTargetNamespace(HttpServletRequest request) {
// Extract from headers (Istio typically adds these)
String namespace = request.getHeader("x-forwarded-namespace");
if (namespace != null) return namespace;
// Extract from path pattern: /api/v1/namespaces/{namespace}/...
String path = request.getRequestURI();
Pattern pattern = Pattern.compile("/api/v1/namespaces/([^/]+)/");
java.util.regex.Matcher matcher = pattern.matcher(path);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
private boolean isCrossNamespaceAllowed(String path) {
// Define paths that allow cross-namespace access
return path.startsWith("/api/public") ||
path.startsWith("/health") ||
path.startsWith("/metrics");
}
}
class ServiceAccountPolicy implements AccessPolicy {
private final Set<String> privilegedAccounts = Set.of(
"istio-system", "kube-system", "cluster-admin"
);
@Override
public PolicyDecision evaluate(HttpServletRequest request, ServiceIdentity identity) {
String serviceAccount = identity.getServiceAccount();
String path = request.getRequestURI();
// Check if privileged service account is accessing sensitive endpoints
if (isSensitiveEndpoint(path) && isPrivilegedAccount(serviceAccount)) {
return new PolicyDecision(false,
"Privileged service account cannot access sensitive endpoints");
}
// Check service account specific restrictions
if (hasServiceAccountRestriction(serviceAccount, path)) {
return new PolicyDecision(false,
"Service account restricted from accessing " + path);
}
return new PolicyDecision(true, "Service account check passed");
}
private boolean isSensitiveEndpoint(String path) {
return path.contains("/admin/") ||
path.contains("/config/") ||
path.contains("/secret/");
}
private boolean isPrivilegedAccount(String serviceAccount) {
return privilegedAccounts.stream()
.anyMatch(serviceAccount::contains);
}
private boolean hasServiceAccountRestriction(String serviceAccount, String path) {
// Implement service account specific restrictions
Map<String, Set<String>> restrictions = Map.of(
"readonly-sa", Set.of("/api/write", "/api/delete"),
"external-sa", Set.of("/api/internal", "/admin")
);
Set<String> restrictedPaths = restrictions.get(serviceAccount);
return restrictedPaths != null && restrictedPaths.stream()
.anyMatch(path::startsWith);
}
}
class TimeBasedPolicy implements AccessPolicy {
@Override
public PolicyDecision evaluate(HttpServletRequest request, ServiceIdentity identity) {
Calendar cal = Calendar.getInstance();
int hour = cal.get(Calendar.HOUR_OF_DAY);
// Restrict access during maintenance window (2 AM - 4 AM)
if (hour >= 2 && hour < 4) {
String path = request.getRequestURI();
if (!isMaintenanceAllowed(path)) {
return new PolicyDecision(false,
"Access restricted during maintenance window (2AM-4AM)");
}
}
return new PolicyDecision(true, "Time-based check passed");
}
private boolean isMaintenanceAllowed(String path) {
return path.startsWith("/health") ||
path.startsWith("/maintenance") ||
path.startsWith("/metrics");
}
}
class ResourceAccessPolicy implements AccessPolicy {
@Override
public PolicyDecision evaluate(HttpServletRequest request, ServiceIdentity identity) {
String method = request.getMethod();
String path = request.getRequestURI();
// RBAC based on service identity and resource
if (method.equals("DELETE") && !canDelete(identity, path)) {
return new PolicyDecision(false, "Delete operation not permitted");
}
if (method.equals("POST") && path.contains("/admin") &&
!isAdmin(identity)) {
return new PolicyDecision(false, "Admin access required");
}
return new PolicyDecision(true, "Resource access check passed");
}
private boolean canDelete(ServiceIdentity identity, String path) {
return identity.getServiceAccount().endsWith("-admin") ||
path.startsWith("/api/user/") && path.endsWith(identity.getServiceAccount());
}
private boolean isAdmin(ServiceIdentity identity) {
return identity.getServiceAccount().endsWith("-admin") ||
identity.getNamespace().equals("admin");
}
}
5. Certificate Validator
import org.springframework.stereotype.Component;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Set;
@Component
public class CertificateValidator {
private final Set<String> trustedIssuers = Set.of(
"CN=istio-ca, O=Istio",
"CN=cluster.local",
"OU=spiffe://cluster.local"
);
private final CertificateRevocationChecker revocationChecker;
private final CertificateCache certificateCache;
public CertificateValidator(CertificateRevocationChecker revocationChecker,
CertificateCache certificateCache) {
this.revocationChecker = revocationChecker;
this.certificateCache = certificateCache;
}
public CertificateValidationResult validateCertificate(X509Certificate cert) {
try {
// Check cache first
String certFingerprint = generateFingerprint(cert);
if (certificateCache.isBlacklisted(certFingerprint)) {
return new CertificateValidationResult(false, "Certificate is blacklisted");
}
// Basic validity check
cert.checkValidity(new Date());
// Check issuer
if (!isTrustedIssuer(cert)) {
return new CertificateValidationResult(false, "Untrusted certificate issuer");
}
// Check SPIFFE ID format
String spiffeId = extractSpiffeId(cert);
if (spiffeId == null || !isValidSpiffeFormat(spiffeId)) {
return new CertificateValidationResult(false, "Invalid SPIFFE ID format");
}
// Check revocation
if (revocationChecker.isRevoked(cert)) {
certificateCache.blacklist(certFingerprint);
return new CertificateValidationResult(false, "Certificate is revoked");
}
// Check certificate chain (if available)
if (!isValidCertificateChain(cert)) {
return new CertificateValidationResult(false, "Invalid certificate chain");
}
return new CertificateValidationResult(true, null);
} catch (Exception e) {
return new CertificateValidationResult(false, "Certificate validation failed: " + e.getMessage());
}
}
private boolean isTrustedIssuer(X509Certificate cert) {
String issuerDN = cert.getIssuerX500Principal().getName();
return trustedIssuers.stream()
.anyMatch(issuerDN::contains);
}
private boolean isValidSpiffeFormat(String spiffeId) {
return spiffeId.matches("^spiffe://[^/]+/ns/[^/]+/sa/[^/]+$");
}
private boolean isValidCertificateChain(X509Certificate cert) {
// In production, this would validate the entire certificate chain
// against the Istio root CA
return true; // Simplified for example
}
private String generateFingerprint(X509Certificate cert) throws Exception {
// Generate SHA-256 fingerprint of certificate
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(cert.getEncoded());
return bytesToHex(digest);
}
private String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
}
@Component
class CertificateRevocationChecker {
public boolean isRevoked(X509Certificate cert) {
// Implement CRL or OCSP checking
// For Istio, typically certificates are short-lived so revocation is less critical
return false;
}
}
@Component
class CertificateCache {
private final Set<String> blacklistedCertificates = Collections.synchronizedSet(new HashSet<>());
public void blacklist(String certificateFingerprint) {
blacklistedCertificates.add(certificateFingerprint);
}
public boolean isBlacklisted(String certificateFingerprint) {
return blacklistedCertificates.contains(certificateFingerprint);
}
}
6. mTLS-aware REST Client
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.*;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
@Component
public class MTlsRestClient {
private final RestTemplate restTemplate;
private final ServiceIdentityProvider identityProvider;
public MTlsRestClient(ServiceIdentityProvider identityProvider) throws Exception {
this.identityProvider = identityProvider;
this.restTemplate = createMTlsRestTemplate();
}
private RestTemplate createMTlsRestTemplate() throws Exception {
// Load client certificate and key
KeyStore keyStore = loadKeyStore();
KeyStore trustStore = loadTrustStore();
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(keyStore, "changeit".toCharArray())
.loadTrustMaterial(trustStore, null)
.build();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
sslContext, new String[]{"TLSv1.2", "TLSv1.3"}, null,
new DefaultHostnameVerifier());
HttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
public <T> ResponseEntity<T> executeWithMTls(String url, HttpMethod method,
HttpEntity<?> requestEntity,
Class<T> responseType) {
// Add mTLS specific headers
HttpHeaders headers = new HttpHeaders();
if (requestEntity.getHeaders() != null) {
headers.putAll(requestEntity.getHeaders());
}
// Add service identity headers
ServiceIdentity identity = identityProvider.getCurrentIdentity();
headers.add("X-Service-Identity", identity.getSpiffeId());
headers.add("X-Service-Namespace", identity.getNamespace());
headers.add("X-Service-Account", identity.getServiceAccount());
HttpEntity<?> enhancedEntity = new HttpEntity<>(
requestEntity.getBody(), headers);
return restTemplate.exchange(url, method, enhancedEntity, responseType);
}
private KeyStore loadKeyStore() throws Exception {
// Load client certificate and private key
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(getClass().getResourceAsStream("/client-keystore.p12"),
"changeit".toCharArray());
return keyStore;
}
private KeyStore loadTrustStore() throws Exception {
// Load CA certificates
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(getClass().getResourceAsStream("/truststore.jks"),
"changeit".toCharArray());
return trustStore;
}
}
@Component
class ServiceIdentityProvider {
public ServiceIdentity getCurrentIdentity() {
// In a real implementation, this would extract identity from security context
// or from the current mTLS certificate
return new ServiceIdentity(
"spiffe://cluster.local/ns/default/sa/my-service",
"default",
"my-service",
"CN=my-service.default.cluster.local"
);
}
}
7. Monitoring and Logging
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.cert.X509Certificate;
@Component
public class MTlsAuditInterceptor implements HandlerInterceptor {
private final AuditLogger auditLogger;
public MTlsAuditInterceptor(AuditLogger auditLogger) {
this.auditLogger = auditLogger;
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) {
try {
X509Certificate[] certificates = (X509Certificate[])
request.getAttribute("javax.servlet.request.X509Certificate");
ServiceIdentity identity = (ServiceIdentity)
request.getAttribute("serviceIdentity");
if (identity != null) {
auditLogger.logAccessAttempt(
identity,
request.getMethod(),
request.getRequestURI(),
request.getRemoteAddr(),
certificates != null && certificates.length > 0
);
}
} catch (Exception e) {
// Don't break the request for logging errors
System.err.println("Audit logging failed: " + e.getMessage());
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
try {
ServiceIdentity identity = (ServiceIdentity)
request.getAttribute("serviceIdentity");
if (identity != null) {
auditLogger.logAccessCompletion(
identity,
request.getMethod(),
request.getRequestURI(),
response.getStatus(),
ex != null ? ex.getMessage() : null
);
}
} catch (Exception e) {
System.err.println("Audit logging failed: " + e.getMessage());
}
}
}
@Component
class AuditLogger {
public void logAccessAttempt(ServiceIdentity identity, String method,
String path, String remoteAddr, boolean mTlsEnabled) {
String logMessage = String.format(
"ACCESS_ATTEMPT|spiffe_id=%s|method=%s|path=%s|remote_addr=%s|mtls=%s",
identity.getSpiffeId(), method, path, remoteAddr, mTlsEnabled
);
System.out.println(logMessage);
}
public void logAccessCompletion(ServiceIdentity identity, String method,
String path, int status, String error) {
String logMessage = String.format(
"ACCESS_COMPLETE|spiffe_id=%s|method=%s|path=%s|status=%d|error=%s",
identity.getSpiffeId(), method, path, status,
error != null ? error : "none"
);
System.out.println(logMessage);
}
}
Key Features
- mTLS Detection: Automatically detect and validate mTLS connections
- SPIFFE Identity Extraction: Parse SPIFFE IDs from client certificates
- Policy Enforcement: Implement fine-grained access control based on service identity
- Certificate Validation: Comprehensive certificate validation including revocation checking
- Security Integration: Seamless integration with Spring Security
- Audit Logging: Comprehensive logging of mTLS authentication and authorization events
- REST Client: mTLS-aware HTTP client for service-to-service communication
This implementation provides a robust foundation for enforcing mTLS policies in Java applications within an Istio service mesh, ensuring secure service-to-service communication with proper identity-based access control.