google-site-verification: google61fe8ba583a51912.html
mTLS Enforcement in Service Mesh with Java: Comprehensive Implementation Guide

mTLS (mutual TLS) provides two-way authentication where both client and server verify each other's certificates. In service mesh architectures, mTLS is enforced automatically between sidecar proxies to secure service-to-service communication.


Core Concepts

What is mTLS in Service Mesh?

  • Automatic certificate issuance and rotation between services
  • Identity-based authentication using X.509 certificates
  • Encryption of all service-to-service traffic
  • Zero-trust security model

Key Benefits:

  • Automatic Certificate Management: No manual certificate configuration needed
  • Service Identity: Each service gets a unique cryptographic identity
  • Traffic Encryption: All inter-service communication is encrypted
  • Policy Enforcement: Fine-grained access control based on service identity

Architecture Overview

Java App → Envoy Sidecar → mTLS → Envoy Sidecar → Java App ↓ (client) (server) ↓ Service A Service B ↓ ↓ Certificate Certificate issued by issued by Mesh CA Mesh CA

Dependencies and Setup

Maven Dependencies
<properties> <spring-boot.version>3.1.0</spring-boot.version> <istio-client.version>1.16.0</istio-client.version> <spring-cloud.version>2022.0.3</spring-cloud.version> </properties> <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>${spring-boot.version}</version> </dependency> <!-- Spring Cloud Kubernetes --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-kubernetes-fabric8</artifactId> <version>${spring-cloud.version}</version> </dependency> <!-- Istio Java API --> <dependency> <groupId>io.istio</groupId> <artifactId>istio-client</artifactId> <version>${istio-client.version}</version> </dependency> <!-- Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>${spring-boot.version}</version> </dependency> <!-- Resilience --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId> <version>${spring-cloud.version}</version> </dependency> <!-- Monitoring --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> </dependencies>
Docker Compose for Local mTLS Testing
# docker-compose.mtls.yml version: '3.8' services: # Consul with mTLS enabled consul-server: image: consul:1.15 container_name: consul-server ports: - "8500:8500" - "8600:8600/udp" command: > agent -server -ui -node=consul-server-1 -bootstrap-expect=1 -client=0.0.0.0 -config-file=/consul/config/mtls.hcl volumes: - ./consul-config/mtls.hcl:/consul/config/mtls.hcl environment: - CONSUL_BIND_INTERFACE=eth0 # Istio for advanced mTLS features istio-pilot: image: istio/pilot:1.18.0 container_name: istio-pilot ports: - "15010:15010" - "15017:15017" command: [ "discovery", "--monitoringAddr=:15014", "--log_output_level=default:info", "--domain", "cluster.local", "--keepaliveMaxServerConnectionAge", "30m" ] # mTLS-enabled services frontend-service: build: ./frontend-service container_name: frontend-service ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=mtls,kubernetes - MTLS_ENABLED=true labels: - "service-mesh=enabled" - "mtls-strict=true" backend-service: build: ./backend-service container_name: backend-service ports: - "8081:8081" environment: - SPRING_PROFILES_ACTIVE=mtls,kubernetes - MTLS_ENABLED=true labels: - "service-mesh=enabled" - "mtls-strict=true" database-service: build: ./database-service container_name: database-service ports: - "8082:8082" environment: - SPRING_PROFILES_ACTIVE=mtls,kubernetes - MTLS_ENABLED=true labels: - "service-mesh=enabled" - "mtls-strict=true"

Spring Boot Configuration for mTLS

1. Application Properties
# application-mtls.yml spring: application: name: frontend-service cloud: kubernetes: enabled: true reload: enabled: true config: sources: - name: ${spring.application.name} namespace: default discovery: all-namespaces: false # mTLS Configuration app: mtls: enabled: true mode: STRICT # STRICT, PERMISSIVE, DISABLED ca-cert-path: /var/run/secrets/istio/root-cert.pem cert-chain-path: /var/run/secrets/istio/cert-chain.pem key-path: /var/run/secrets/istio/key.pem verify-hostname: true allowed-services: - backend-service - database-service # Security server: ssl: enabled: true client-auth: need key-store: /var/run/secrets/istio/keystore.p12 key-store-password: changeit key-store-type: PKCS12 trust-store: /var/run/secrets/istio/truststore.p12 trust-store-password: changeit trust-store-type: PKCS12 port: 8443 management: endpoints: web: exposure: include: health,info,metrics,mtls endpoint: mtls: enabled: true server: port: 8444 ssl: enabled: true
2. mTLS Configuration Class
@Configuration @ConfigurationProperties(prefix = "app.mtls") @Validated public class MtlsConfig { private boolean enabled = false; private MtlsMode mode = MtlsMode.PERMISSIVE; private String caCertPath; private String certChainPath; private String keyPath; private boolean verifyHostname = true; private Set<String> allowedServices = new HashSet<>(); public enum MtlsMode { STRICT, PERMISSIVE, DISABLED } // Getters and setters public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public MtlsMode getMode() { return mode; } public void setMode(MtlsMode mode) { this.mode = mode; } public String getCaCertPath() { return caCertPath; } public void setCaCertPath(String caCertPath) { this.caCertPath = caCertPath; } public String getCertChainPath() { return certChainPath; } public void setCertChainPath(String certChainPath) { this.certChainPath = certChainPath; } public String getKeyPath() { return keyPath; } public void setKeyPath(String keyPath) { this.keyPath = keyPath; } public boolean isVerifyHostname() { return verifyHostname; } public void setVerifyHostname(boolean verifyHostname) { this.verifyHostname = verifyHostname; } public Set<String> getAllowedServices() { return allowedServices; } public void setAllowedServices(Set<String> allowedServices) { this.allowedServices = allowedServices; } }
3. mTLS Certificate Manager
@Component @Slf4j public class MtlsCertificateManager { private final MtlsConfig mtlsConfig; private SSLContext sslContext; private X509Certificate clientCertificate; public MtlsCertificateManager(MtlsConfig mtlsConfig) { this.mtlsConfig = mtlsConfig; if (mtlsConfig.isEnabled()) { initializeCertificates(); } } @PostConstruct public void initializeCertificates() { if (!mtlsConfig.isEnabled()) { log.info("mTLS is disabled"); return; } try { // Load certificates from mesh-injected paths CertificateFactory cf = CertificateFactory.getInstance("X.509"); // Load CA certificate X509Certificate caCert = loadCertificate(mtlsConfig.getCaCertPath()); // Load client certificate chain X509Certificate clientCert = loadCertificate(mtlsConfig.getCertChainPath()); // Load private key PrivateKey privateKey = loadPrivateKey(mtlsConfig.getKeyPath()); // Create KeyStore KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(null, null); keyStore.setKeyEntry("client", privateKey, "changeit".toCharArray(), new Certificate[]{clientCert}); // Create TrustStore KeyStore trustStore = KeyStore.getInstance("PKCS12"); trustStore.load(null, null); trustStore.setCertificateEntry("ca", caCert); // Initialize SSLContext SSLContextBuilder sslContextBuilder = SSLContexts.custom() .loadKeyMaterial(keyStore, "changeit".toCharArray()) .loadTrustMaterial(trustStore, null); if (mtlsConfig.isVerifyHostname()) { sslContextBuilder = sslContextBuilder.setKeyStoreType("PKCS12"); } this.sslContext = sslContextBuilder.build(); this.clientCertificate = clientCert; log.info("mTLS certificates initialized successfully for mode: {}", mtlsConfig.getMode()); logCertificateInfo(clientCert); } catch (Exception e) { log.error("Failed to initialize mTLS certificates", e); throw new IllegalStateException("mTLS initialization failed", e); } } private X509Certificate loadCertificate(String path) throws Exception { try (InputStream is = Files.newInputStream(Paths.get(path))) { CertificateFactory cf = CertificateFactory.getInstance("X.509"); return (X509Certificate) cf.generateCertificate(is); } } private PrivateKey loadPrivateKey(String path) throws Exception { // This is simplified - in practice, keys might be in different formats byte[] keyBytes = Files.readAllBytes(Paths.get(path)); String key = new String(keyBytes); if (key.contains("PRIVATE KEY")) { // PEM format return loadPemPrivateKey(key); } else { // Assume DER format PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePrivate(spec); } } private PrivateKey loadPemPrivateKey(String pem) throws Exception { String privateKeyPEM = pem .replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "") .replaceAll("\\s", ""); byte[] encoded = Base64.getDecoder().decode(privateKeyPEM); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); return keyFactory.generatePrivate(keySpec); } private void logCertificateInfo(X509Certificate cert) { log.info("Client Certificate Details:"); log.info(" Subject: {}", cert.getSubjectX500Principal()); log.info(" Issuer: {}", cert.getIssuerX500Principal()); log.info(" Valid From: {}", cert.getNotBefore()); log.info(" Valid Until: {}", cert.getNotAfter()); log.info(" Serial Number: {}", cert.getSerialNumber()); } public SSLContext getSslContext() { return sslContext; } public X509Certificate getClientCertificate() { return clientCertificate; } public String getServiceIdentity() { if (clientCertificate == null) { return "unknown"; } String subject = clientCertificate.getSubjectX500Principal().getName(); // Extract service name from certificate subject // Format: CN=frontend-service.default.svc.cluster.local Pattern pattern = Pattern.compile("CN=([^.]+)"); Matcher matcher = pattern.matcher(subject); if (matcher.find()) { return matcher.group(1); } return subject; } public boolean isCertificateValid() { if (clientCertificate == null) { return false; } try { clientCertificate.checkValidity(); return true; } catch (CertificateExpiredException | CertificateNotYetValidException e) { log.warn("Certificate is not valid: {}", e.getMessage()); return false; } } }
4. mTLS-Aware RestTemplate
@Configuration public class MtlsRestTemplateConfig { @Bean @ConditionalOnProperty(name = "app.mtls.enabled", havingValue = "true") public RestTemplate mtlsRestTemplate(MtlsCertificateManager certificateManager, MtlsConfig mtlsConfig) { try { SSLContext sslContext = certificateManager.getSslContext(); SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( sslContext, new String[]{"TLSv1.2", "TLSv1.3"}, null, mtlsConfig.isVerifyHostname() ? SSLConnectionSocketFactory.getDefaultHostnameVerifier() : NoopHostnameVerifier.INSTANCE ); HttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create() .setSSLSocketFactory(socketFactory) .build(); HttpClient httpClient = HttpClients.custom() .setConnectionManager(connectionManager) .setSSLContext(sslContext) .addInterceptorFirst(new HttpComponentsMessageSender.RemoveSoapHeadersInterceptor()) .build(); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); return new RestTemplate(requestFactory); } catch (Exception e) { throw new IllegalStateException("Failed to create mTLS RestTemplate", e); } } @Bean @ConditionalOnProperty(name = "app.mtls.enabled", havingValue = "false") public RestTemplate standardRestTemplate() { return new RestTemplate(); } }

Service Implementation with mTLS

1. mTLS Service Client
@Service @Slf4j public class MtlsAwareServiceClient { private final RestTemplate restTemplate; private final MtlsCertificateManager certificateManager; private final MtlsConfig mtlsConfig; private final CircuitBreaker circuitBreaker; public MtlsAwareServiceClient(@Qualifier("mtlsRestTemplate") RestTemplate restTemplate, MtlsCertificateManager certificateManager, MtlsConfig mtlsConfig, CircuitBreakerRegistry circuitBreakerRegistry) { this.restTemplate = restTemplate; this.certificateManager = certificateManager; this.mtlsConfig = mtlsConfig; this.circuitBreaker = circuitBreakerRegistry.circuitBreaker("mtlsServiceClient"); } public <T> T callServiceWithMtls(String serviceUrl, String path, Class<T> responseType) { validateMtlsStatus(); validateServiceAccess(serviceUrl); return circuitBreaker.executeSupplier(() -> { try { String fullUrl = buildServiceUrl(serviceUrl, path); log.debug("Making mTLS request to: {}", fullUrl); ResponseEntity<T> response = restTemplate.getForEntity(fullUrl, responseType); if (response.getStatusCode().is2xxSuccessful()) { log.debug("mTLS request successful to: {}", serviceUrl); return response.getBody(); } else { throw new MtlsServiceException( "Service returned non-2xx status: " + response.getStatusCode()); } } catch (ResourceAccessException e) { log.error("mTLS connection failed to: {}", serviceUrl, e); throw new MtlsConnectionException("mTLS connection failed", e); } }); } public <T> T postWithMtls(String serviceUrl, String path, Object request, Class<T> responseType) { validateMtlsStatus(); validateServiceAccess(serviceUrl); return circuitBreaker.executeSupplier(() -> { try { String fullUrl = buildServiceUrl(serviceUrl, path); log.debug("Making mTLS POST request to: {}", fullUrl); ResponseEntity<T> response = restTemplate.postForEntity(fullUrl, request, responseType); if (response.getStatusCode().is2xxSuccessful()) { log.debug("mTLS POST request successful to: {}", serviceUrl); return response.getBody(); } else { throw new MtlsServiceException( "Service returned non-2xx status: " + response.getStatusCode()); } } catch (ResourceAccessException e) { log.error("mTLS POST connection failed to: {}", serviceUrl, e); throw new MtlsConnectionException("mTLS connection failed", e); } }); } private void validateMtlsStatus() { if (!mtlsConfig.isEnabled()) { throw new IllegalStateException("mTLS is not enabled"); } if (!certificateManager.isCertificateValid()) { throw new MtlsSecurityException("Client certificate is not valid"); } if (mtlsConfig.getMode() == MtlsConfig.MtlsMode.STRICT && !certificateManager.isCertificateValid()) { throw new MtlsSecurityException("STRICT mode requires valid certificate"); } } private void validateServiceAccess(String serviceUrl) { String serviceName = extractServiceName(serviceUrl); if (!mtlsConfig.getAllowedServices().contains(serviceName)) { throw new MtlsSecurityException( "Service " + serviceName + " is not in allowed services list"); } } private String buildServiceUrl(String serviceUrl, String path) { // For service mesh, use the service name with mTLS port if (serviceUrl.contains(".")) { // Full domain - use HTTPS return "https://" + serviceUrl + ":8443" + path; } else { // Service name only - use mesh internal communication return "https://" + serviceUrl + ".default.svc.cluster.local:8443" + path; } } private String extractServiceName(String serviceUrl) { // Extract service name from URL // Format: service-name.namespace.svc.cluster.local return serviceUrl.split("\\.")[0]; } public MtlsConnectionInfo getConnectionInfo() { return new MtlsConnectionInfo( certificateManager.getServiceIdentity(), certificateManager.isCertificateValid(), mtlsConfig.getMode(), mtlsConfig.getAllowedServices() ); } }
2. mTLS Security Service
@Service @Slf4j public class MtlsSecurityService { private final MtlsCertificateManager certificateManager; private final MtlsConfig mtlsConfig; public MtlsSecurityService(MtlsCertificateManager certificateManager, MtlsConfig mtlsConfig) { this.certificateManager = certificateManager; this.mtlsConfig = mtlsConfig; } public boolean authenticateService(HttpServletRequest request) { if (!mtlsConfig.isEnabled()) { return true; // mTLS disabled, allow all } try { X509Certificate[] certificates = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); if (certificates == null || certificates.length == 0) { log.warn("No client certificate presented in request"); return mtlsConfig.getMode() != MtlsConfig.MtlsMode.STRICT; } X509Certificate clientCert = certificates[0]; return validateCertificate(clientCert); } catch (Exception e) { log.error("Certificate authentication failed", e); return false; } } private boolean validateCertificate(X509Certificate certificate) { try { // Check certificate validity certificate.checkValidity(); // Verify certificate chain (simplified) String subject = certificate.getSubjectX500Principal().getName(); String issuer = certificate.getIssuerX500Principal().getName(); log.debug("Validating certificate - Subject: {}, Issuer: {}", subject, issuer); // Extract service name from subject String serviceName = extractServiceName(subject); // Check if service is allowed to communicate with this service return isServiceAllowed(serviceName); } catch (CertificateException e) { log.warn("Certificate validation failed: {}", e.getMessage()); return false; } } private String extractServiceName(String subject) { // Certificate subject format: CN=service-name.namespace.svc.cluster.local Pattern pattern = Pattern.compile("CN=([^.]+)"); Matcher matcher = pattern.matcher(subject); if (matcher.find()) { return matcher.group(1); } return "unknown"; } private boolean isServiceAllowed(String serviceName) { return mtlsConfig.getAllowedServices().contains(serviceName); } public ServiceIdentity getCallerIdentity(HttpServletRequest request) { try { X509Certificate[] certificates = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); if (certificates != null && certificates.length > 0) { X509Certificate cert = certificates[0]; String subject = cert.getSubjectX500Principal().getName(); String serviceName = extractServiceName(subject); return new ServiceIdentity( serviceName, subject, cert.getIssuerX500Principal().getName(), cert.getNotBefore(), cert.getNotAfter() ); } } catch (Exception e) { log.error("Failed to extract caller identity", e); } return ServiceIdentity.UNKNOWN; } }
3. mTLS Security Filter
@Component @Slf4j public class MtlsSecurityFilter implements Filter { private final MtlsSecurityService mtlsSecurityService; private final MtlsConfig mtlsConfig; public MtlsSecurityFilter(MtlsSecurityService mtlsSecurityService, MtlsConfig mtlsConfig) { this.mtlsSecurityService = mtlsSecurityService; this.mtlsConfig = mtlsConfig; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (!mtlsConfig.isEnabled()) { chain.doFilter(request, response); return; } HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // Skip mTLS check for health endpoints if (shouldSkipMtlsCheck(httpRequest)) { chain.doFilter(request, response); return; } // Authenticate service using mTLS if (mtlsSecurityService.authenticateService(httpRequest)) { ServiceIdentity caller = mtlsSecurityService.getCallerIdentity(httpRequest); log.debug("mTLS authentication successful for service: {}", caller.getServiceName()); // Add caller identity to request for business logic request.setAttribute("callerServiceIdentity", caller); chain.doFilter(request, response); } else { log.warn("mTLS authentication failed for request: {}", httpRequest.getRequestURI()); httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); httpResponse.getWriter().write("mTLS authentication failed"); } } private boolean shouldSkipMtlsCheck(HttpServletRequest request) { String path = request.getRequestURI(); return path.startsWith("/actuator/health") || path.startsWith("/actuator/info") || path.startsWith("/actuator/mtls"); } @Override public void init(FilterConfig filterConfig) throws ServletException { log.info("mTLS Security Filter initialized with mode: {}", mtlsConfig.getMode()); } @Override public void destroy() { log.info("mTLS Security Filter destroyed"); } }
4. Frontend Service with mTLS
@RestController @RequestMapping("/api/frontend") @Slf4j public class FrontendController { private final MtlsAwareServiceClient mtlsClient; private final MtlsSecurityService mtlsSecurityService; public FrontendController(MtlsAwareServiceClient mtlsClient, MtlsSecurityService mtlsSecurityService) { this.mtlsClient = mtlsClient; this.mtlsSecurityService = mtlsSecurityService; } @GetMapping("/user/{userId}/orders") public ResponseEntity<UserOrdersResponse> getUserOrders(@PathVariable String userId, HttpServletRequest request) { try { // Get caller identity for audit ServiceIdentity caller = mtlsSecurityService.getCallerIdentity(request); log.info("Processing request from service: {} for user: {}", caller.getServiceName(), userId); // Call backend service with mTLS String backendUrl = "backend-service.default.svc.cluster.local"; List<Order> orders = mtlsClient.callServiceWithMtls( backendUrl, "/api/orders/user/" + userId, List.class); // Call user service with mTLS String userServiceUrl = "user-service.default.svc.cluster.local"; User user = mtlsClient.callServiceWithMtls( userServiceUrl, "/api/users/" + userId, User.class); UserOrdersResponse response = new UserOrdersResponse(user, orders); return ResponseEntity.ok(response); } catch (MtlsSecurityException e) { log.error("mTLS security violation", e); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } catch (MtlsConnectionException e) { log.error("mTLS connection failed", e); return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build(); } } @PostMapping("/orders") public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request, HttpServletRequest httpRequest) { try { // Validate caller has permission to create orders ServiceIdentity caller = (ServiceIdentity) httpRequest.getAttribute("callerServiceIdentity"); if (!isAllowedToCreateOrders(caller)) { return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); } // Call backend service with mTLS String backendUrl = "backend-service.default.svc.cluster.local"; Order order = mtlsClient.postWithMtls( backendUrl, "/api/orders", request, Order.class); return ResponseEntity.status(HttpStatus.CREATED).body(order); } catch (MtlsSecurityException e) { log.error("mTLS security violation", e); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } private boolean isAllowedToCreateOrders(ServiceIdentity caller) { // Business logic for authorization return "web-gateway".equals(caller.getServiceName()) || "mobile-gateway".equals(caller.getServiceName()); } @GetMapping("/mtls-info") public ResponseEntity<MtlsConnectionInfo> getMtlsInfo() { MtlsConnectionInfo info = mtlsClient.getConnectionInfo(); return ResponseEntity.ok(info); } }
5. mTLS Actuator Endpoint
@Component @Endpoint(id = "mtls") @Slf4j public class MtlsActuatorEndpoint { private final MtlsCertificateManager certificateManager; private final MtlsConfig mtlsConfig; private final MtlsSecurityService mtlsSecurityService; public MtlsActuatorEndpoint(MtlsCertificateManager certificateManager, MtlsConfig mtlsConfig, MtlsSecurityService mtlsSecurityService) { this.certificateManager = certificateManager; this.mtlsConfig = mtlsConfig; this.mtlsSecurityService = mtlsSecurityService; } @ReadOperation public MtlsStatus getMtlsStatus() { return new MtlsStatus( mtlsConfig.isEnabled(), mtlsConfig.getMode().toString(), certificateManager.isCertificateValid(), certificateManager.getServiceIdentity(), mtlsConfig.getAllowedServices() ); } @ReadOperation public CertificateInfo getCertificateInfo() { X509Certificate cert = certificateManager.getClientCertificate(); if (cert == null) { return new CertificateInfo("No certificate available", null, null, null, null); } return new CertificateInfo( cert.getSubjectX500Principal().getName(), cert.getIssuerX500Principal().getName(), cert.getNotBefore(), cert.getNotAfter(), cert.getSerialNumber().toString() ); } public static class MtlsStatus { private final boolean enabled; private final String mode; private final boolean certificateValid; private final String serviceIdentity; private final Set<String> allowedServices; // Constructor, getters... } public static class CertificateInfo { private final String subject; private final String issuer; private final Date validFrom; private final Date validUntil; private final String serialNumber; // Constructor, getters... } }

Kubernetes Deployment with mTLS

1. Istio mTLS Configuration
# k8s/istio-mtls.yaml apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default namespace: default spec: mtls: mode: STRICT --- apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: frontend-service namespace: default spec: selector: matchLabels: app: frontend-service mtls: mode: STRICT --- apiVersion: security.istio.io/v1beta1 kind: DestinationRule metadata: name: enable-mtls namespace: default spec: host: "*.svc.cluster.local" trafficPolicy: tls: mode: ISTIO_MUTUAL
2. Frontend Service Deployment
# k8s/frontend-service.yaml apiVersion: apps/v1 kind: Deployment metadata: name: frontend-service labels: app: frontend-service version: v1 spec: replicas: 3 selector: matchLabels: app: frontend-service version: v1 template: metadata: labels: app: frontend-service version: v1 annotations: sidecar.istio.io/inject: "true" proxy.istio.io/config: | tracing: zipkin: address: zipkin:9411 spec: serviceAccountName: frontend-service-account containers: - name: frontend-service image: frontend-service:latest ports: - containerPort: 8443 name: https - containerPort: 8444 name: management env: - name: SPRING_PROFILES_ACTIVE value: "kubernetes,mtls" - name: APP_MTLS_ENABLED value: "true" - name: APP_MTLS_MODE value: "STRICT" resources: requests: memory: "512Mi" cpu: "200m" limits: memory: "1Gi" cpu: "500m" readinessProbe: httpGet: path: /actuator/health/readiness port: 8444 scheme: HTTPS initialDelaySeconds: 30 periodSeconds: 10 livenessProbe: httpGet: path: /actuator/health port: 8444 scheme: HTTPS initialDelaySeconds: 60 periodSeconds: 30 --- apiVersion: v1 kind: Service metadata: name: frontend-service labels: app: frontend-service spec: selector: app: frontend-service ports: - name: https port: 8443 targetPort: 8443 - name: management port: 8444 targetPort: 8444 --- apiVersion: v1 kind: ServiceAccount metadata: name: frontend-service-account
3. Service Role for mTLS
# k8s/service-roles.yaml apiVersion: rbac.istio.io/v1alpha1 kind: ServiceRole metadata: name: frontend-service-role namespace: default spec: rules: - services: ["frontend-service.default.svc.cluster.local"] methods: ["*"] paths: ["*"] --- apiVersion: rbac.istio.io/v1alpha1 kind: ServiceRoleBinding metadata: name: frontend-service-binding namespace: default spec: subjects: - properties: source.namespace: "default" roleRef: kind: ServiceRole name: frontend-service-role

Testing and Monitoring

1. mTLS Integration Tests
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Testcontainers @ActiveProfiles("test") class MtlsIntegrationTest { @Container static GenericContainer<?> consulContainer = new GenericContainer<>("consul:1.15") .withExposedPorts(8500) .withCommand("agent", "-dev", "-client", "0.0.0.0"); @Autowired private TestRestTemplate testRestTemplate; @Autowired private MtlsCertificateManager certificateManager; @Test void testMtlsEnabled() { MtlsConfig mtlsConfig = certificateManager.getMtlsConfig(); assertThat(mtlsConfig.isEnabled()).isTrue(); } @Test void testCertificateValidation() { assertThat(certificateManager.isCertificateValid()).isTrue(); } @Test void testMtlsActuatorEndpoint() { ResponseEntity<MtlsActuatorEndpoint.MtlsStatus> response = testRestTemplate.getForEntity( "/actuator/mtls", MtlsActuatorEndpoint.MtlsStatus.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody().isEnabled()).isTrue(); } }
2. mTLS Health Indicator
@Component public class MtlsHealthIndicator implements HealthIndicator { private final MtlsCertificateManager certificateManager; private final MtlsConfig mtlsConfig; public MtlsHealthIndicator(MtlsCertificateManager certificateManager, MtlsConfig mtlsConfig) { this.certificateManager = certificateManager; this.mtlsConfig = mtlsConfig; } @Override public Health health() { if (!mtlsConfig.isEnabled()) { return Health.unknown() .withDetail("mTLS", "disabled") .build(); } try { boolean certificateValid = certificateManager.isCertificateValid(); String serviceIdentity = certificateManager.getServiceIdentity(); if (certificateValid) { return Health.up() .withDetail("mTLS", "enabled") .withDetail("mode", mtlsConfig.getMode()) .withDetail("serviceIdentity", serviceIdentity) .withDetail("certificateValid", true) .build(); } else { return Health.down() .withDetail("mTLS", "enabled") .withDetail("mode", mtlsConfig.getMode()) .withDetail("serviceIdentity", serviceIdentity) .withDetail("certificateValid", false) .withDetail("error", "Certificate is not valid") .build(); } } catch (Exception e) { return Health.down() .withDetail("mTLS", "error") .withDetail("error", e.getMessage()) .build(); } } }

Best Practices

  1. Start with PERMISSIVE mode: Gradually move to STRICT mode
  2. Monitor certificate rotation: Ensure automatic rotation works correctly
  3. Implement proper error handling: Handle mTLS failures gracefully
  4. Use circuit breakers: Prevent cascading failures
  5. Monitor mTLS metrics: Track success/failure rates
  6. Test across service boundaries: Ensure end-to-end mTLS works
// Example of gradual mTLS enforcement public class GradualMtlsEnforcement { public void enforceMtls() { // Phase 1: Monitor without enforcement // Phase 2: PERMISSIVE mode (allow non-mTLS) // Phase 3: STRICT mode (enforce mTLS) // Phase 4: Monitor and optimize } }

Conclusion

mTLS enforcement in service mesh provides:

  • Automatic certificate management with zero manual intervention
  • Strong service identity for authentication and authorization
  • End-to-end encryption for all service communication
  • Zero-trust security model implementation

By implementing the patterns shown above, Java applications can seamlessly integrate with service mesh mTLS capabilities, providing robust security while maintaining development velocity. The combination of automatic certificate management, service identity, and fine-grained access control creates a secure foundation for microservices communication.

Leave a Reply

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


Macro Nepal Helper