# Уязвимости и межсервисное взаимодействие ## Навигация - [🏠 Главная страница курса](../../../README.md) - [📚 Модуль 6](../README.md) --- ## Типы уязвимостей в микросервисной архитектуре ### 1. Уязвимости межсервисного взаимодействия **Проблемы:** - Отсутствие аутентификации между сервисами - Передача чувствительных данных в открытом виде - Отсутствие валидации токенов - Man-in-the-middle атаки - Replay атаки **Решения:** - Mutual TLS (mTLS) - Service-to-Service токены - API Gateway с аутентификацией - Token Validation Middleware (TVM) ### 2. TVM (Token Validation Middleware) **TVM** — промежуточное ПО для валидации токенов между микросервисами. **Принципы работы:** 1. Сервис получает токен от клиента 2. TVM валидирует токен 3. TVM извлекает информацию о пользователе/сервисе 4. TVM передает валидные данные в целевой сервис **Технический пример - TVM Implementation:** ```java @Component public class TokenValidationMiddleware { @Autowired private JwtTokenService jwtTokenService; @Autowired private ServiceRegistry serviceRegistry; public ServiceTokenValidationResult validateServiceToken(String token, String targetService) { try { // Валидация токена Claims claims = jwtTokenService.validateToken(token); // Проверка прав доступа к сервису if (!hasAccessToService(claims, targetService)) { return ServiceTokenValidationResult.denied("No access to service: " + targetService); } // Проверка срока действия токена if (isTokenExpired(claims)) { return ServiceTokenValidationResult.denied("Token expired"); } // Извлечение информации о сервисе ServiceInfo serviceInfo = extractServiceInfo(claims); return ServiceTokenValidationResult.success(serviceInfo); } catch (JwtException e) { return ServiceTokenValidationResult.denied("Invalid token: " + e.getMessage()); } } private boolean hasAccessToService(Claims claims, String targetService) { List allowedServices = claims.get("allowed_services", List.class); return allowedServices != null && allowedServices.contains(targetService); } private boolean isTokenExpired(Claims claims) { Date expiration = claims.getExpiration(); return expiration.before(new Date()); } private ServiceInfo extractServiceInfo(Claims claims) { return ServiceInfo.builder() .serviceId(claims.getSubject()) .serviceName(claims.get("service_name", String.class)) .permissions(claims.get("permissions", List.class)) .issuedAt(claims.getIssuedAt()) .expiresAt(claims.getExpiration()) .build(); } } // Service Token Validation Result @Data @Builder public class ServiceTokenValidationResult { private boolean valid; private String errorMessage; private ServiceInfo serviceInfo; public static ServiceTokenValidationResult success(ServiceInfo serviceInfo) { return ServiceTokenValidationResult.builder() .valid(true) .serviceInfo(serviceInfo) .build(); } public static ServiceTokenValidationResult denied(String errorMessage) { return ServiceTokenValidationResult.builder() .valid(false) .errorMessage(errorMessage) .build(); } } // Service Info @Data @Builder public class ServiceInfo { private String serviceId; private String serviceName; private List permissions; private Date issuedAt; private Date expiresAt; } ``` **Rust примеры - TVM с Actix Web:** ```rust // Token Validation Middleware для Actix Web use actix_web::{web, HttpRequest, HttpResponse, Error, middleware::Next}; use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; #[derive(Debug, Serialize, Deserialize)] struct ServiceClaims { sub: String, // service_id service_name: String, allowed_services: Vec, permissions: Vec, iat: u64, // issued_at exp: u64, // expires_at } #[derive(Debug, Clone)] pub struct ServiceInfo { pub service_id: String, pub service_name: String, pub permissions: Vec, } pub struct TokenValidationMiddleware { decoding_key: DecodingKey, validation: Validation, } impl TokenValidationMiddleware { pub fn new(secret: &str) -> Self { let mut validation = Validation::new(Algorithm::HS256); validation.set_issuer(&["microservices-platform"]); Self { decoding_key: DecodingKey::from_secret(secret.as_ref()), validation, } } pub fn validate_service_token( &self, token: &str, target_service: &str, ) -> Result { // Декодирование токена let token_data = decode::(token, &self.decoding_key, &self.validation) .map_err(|e| format!("Token validation failed: {}", e))?; let claims = token_data.claims; // Проверка срока действия let now = chrono::Utc::now().timestamp() as u64; if claims.exp < now { return Err("Token expired".to_string()); } // Проверка прав доступа к сервису if !claims.allowed_services.contains(&target_service.to_string()) { return Err(format!("No access to service: {}", target_service)); } Ok(ServiceInfo { service_id: claims.sub, service_name: claims.service_name, permissions: claims.permissions, }) } } // Middleware для Actix Web pub async fn token_validation_middleware( req: HttpRequest, next: Next, ) -> Result { // Извлечение токена из заголовка let token = req.headers() .get("Authorization") .and_then(|h| h.to_str().ok()) .and_then(|s| s.strip_prefix("Bearer ")); match token { Some(token) => { // Валидация токена let middleware = TokenValidationMiddleware::new("your-secret-key"); let target_service = req.path().split('/').nth(1).unwrap_or("unknown"); match middleware.validate_service_token(token, target_service) { Ok(service_info) => { // Добавление информации о сервисе в контекст запроса req.extensions_mut().insert(service_info); next.run(req).await } Err(e) => Ok(HttpResponse::Unauthorized() .json(serde_json::json!({ "error": "Token validation failed", "message": e }))) } } None => Ok(HttpResponse::Unauthorized() .json(serde_json::json!({ "error": "Missing authorization token" }))) } } ``` **Python примеры - TVM с FastAPI:** ```python # Token Validation Middleware для FastAPI from fastapi import FastAPI, HTTPException, Depends, Request from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from jose import JWTError, jwt from datetime import datetime, timedelta from typing import Optional, List from pydantic import BaseModel class ServiceClaims(BaseModel): sub: str service_name: str allowed_services: List[str] permissions: List[str] iat: int exp: int class ServiceInfo(BaseModel): service_id: str service_name: str permissions: List[str] class TokenValidationMiddleware: def __init__(self, secret_key: str): self.secret_key = secret_key self.algorithm = "HS256" def validate_service_token(self, token: str, target_service: str) -> ServiceInfo: try: # Декодирование токена payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm]) # Проверка срока действия if datetime.utcnow().timestamp() > payload.get("exp", 0): raise HTTPException(status_code=401, detail="Token expired") # Проверка прав доступа к сервису allowed_services = payload.get("allowed_services", []) if target_service not in allowed_services: raise HTTPException( status_code=403, detail=f"No access to service: {target_service}" ) return ServiceInfo( service_id=payload.get("sub"), service_name=payload.get("service_name"), permissions=payload.get("permissions", []) ) except JWTError as e: raise HTTPException(status_code=401, detail=f"Invalid token: {str(e)}") # Dependency для FastAPI security = HTTPBearer() middleware = TokenValidationMiddleware("your-secret-key") async def get_service_info( credentials: HTTPAuthorizationCredentials = Depends(security), request: Request = None ) -> ServiceInfo: token = credentials.credentials target_service = request.url.path.split('/')[1] if request else "unknown" return middleware.validate_service_token(token, target_service) # Использование в FastAPI app = FastAPI() @app.get("/api/orders") async def get_orders(service_info: ServiceInfo = Depends(get_service_info)): # Проверка прав доступа if "read:orders" not in service_info.permissions: raise HTTPException(status_code=403, detail="Insufficient permissions") return {"message": "Orders retrieved", "service": service_info.service_name} ``` **Node.js примеры - TVM с Express:** ```typescript // Token Validation Middleware для Express import express, { Request, Response, NextFunction } from 'express'; import jwt from 'jsonwebtoken'; import { Request as JWTRequest } from 'express-jwt'; interface ServiceClaims { sub: string; service_name: string; allowed_services: string[]; permissions: string[]; iat: number; exp: number; } interface ServiceInfo { service_id: string; service_name: string; permissions: string[]; } class TokenValidationMiddleware { private secretKey: string; private algorithm: string = 'HS256'; constructor(secretKey: string) { this.secretKey = secretKey; } validateServiceToken(token: string, targetService: string): ServiceInfo { try { // Декодирование токена const payload = jwt.verify(token, this.secretKey, { algorithms: [this.algorithm] }) as ServiceClaims; // Проверка срока действия if (Date.now() / 1000 > payload.exp) { throw new Error('Token expired'); } // Проверка прав доступа к сервису if (!payload.allowed_services.includes(targetService)) { throw new Error(`No access to service: ${targetService}`); } return { service_id: payload.sub, service_name: payload.service_name, permissions: payload.permissions }; } catch (error) { throw new Error(`Token validation failed: ${error.message}`); } } } // Middleware для Express const middleware = new TokenValidationMiddleware('your-secret-key'); export const tokenValidationMiddleware = (req: Request, res: Response, next: NextFunction) => { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Missing authorization token' }); } const token = authHeader.substring(7); const targetService = req.path.split('/')[2] || 'unknown'; const serviceInfo = middleware.validateServiceToken(token, targetService); // Добавление информации о сервисе в контекст запроса (req as any).serviceInfo = serviceInfo; next(); } catch (error) { res.status(401).json({ error: 'Token validation failed', message: error.message }); } }; // Использование в Express app.use('/api', tokenValidationMiddleware); app.get('/api/orders', (req: Request, res: Response) => { const serviceInfo = (req as any).serviceInfo; // Проверка прав доступа if (!serviceInfo.permissions.includes('read:orders')) { return res.status(403).json({ error: 'Insufficient permissions' }); } res.json({ message: 'Orders retrieved', service: serviceInfo.service_name }); }); ``` ### 3. Mutual TLS (mTLS) **mTLS** обеспечивает взаимную аутентификацию между сервисами. **Java пример - Spring Boot с mTLS:** ```java @Configuration @EnableWebSecurity public class MTlsSecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(auth -> auth .requestMatchers("/api/public/**").permitAll() .anyRequest().authenticated() ) .x509(x509 -> x509 .subjectPrincipalRegex("CN=(.*?)(?:,|$)") .userDetailsService(userDetailsService()) ) .build(); } @Bean public UserDetailsService userDetailsService() { return username -> { // Валидация сертификата клиента if (isValidServiceCertificate(username)) { return User.builder() .username(username) .password("") .authorities("ROLE_SERVICE") .build(); } throw new UsernameNotFoundException("Invalid service certificate"); }; } private boolean isValidServiceCertificate(String subject) { // Логика валидации сертификата return subject.startsWith("service-"); } } ``` ### 4. Service Mesh Security **Istio Security Configuration:** ```yaml # PeerAuthentication для mTLS apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default namespace: production spec: mtls: mode: STRICT # AuthorizationPolicy для контроля доступа apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: service-access namespace: production spec: rules: - from: - source: principals: ["cluster.local/ns/production/sa/order-service"] to: - operation: methods: ["GET", "POST"] paths: ["/api/orders/*"] ``` ### 5. API Gateway Security **Kong Gateway Configuration:** ```yaml # Kong API Gateway с JWT аутентификацией apiVersion: configuration.konghq.com/v1 kind: KongPlugin metadata: name: jwt-auth config: key_claim_name: iss secret_is_base64: false run_on_preflight: true --- apiVersion: configuration.konghq.com/v1 kind: KongIngress metadata: name: api-ingress upstream: healthchecks: active: healthy: interval: 30 successes: 3 unhealthy: interval: 10 http_failures: 3 ``` ### 6. Security Headers для микросервисов **Java пример - Security Headers:** ```java @Component public class SecurityHeadersFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpResponse = (HttpServletResponse) response; // Security Headers httpResponse.setHeader("X-Content-Type-Options", "nosniff"); httpResponse.setHeader("X-Frame-Options", "DENY"); httpResponse.setHeader("X-XSS-Protection", "1; mode=block"); httpResponse.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); httpResponse.setHeader("Content-Security-Policy", "default-src 'self'"); httpResponse.setHeader("Referrer-Policy", "strict-origin-when-cross-origin"); httpResponse.setHeader("Permissions-Policy", "geolocation=(), microphone=(), camera=()"); chain.doFilter(request, response); } } ``` ### 7. Rate Limiting для межсервисного взаимодействия **Redis-based Rate Limiting:** ```java @Service public class InterServiceRateLimitingService { @Autowired private RedisTemplate redisTemplate; public boolean isAllowed(String serviceId, String targetService, int maxRequests, Duration window) { String key = String.format("rate_limit:%s:%s", serviceId, targetService); // Sliding window rate limiting long now = System.currentTimeMillis(); long windowStart = now - window.toMillis(); // Удаление старых записей redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStart); // Подсчет текущих запросов Long currentCount = redisTemplate.opsForZSet().count(key, windowStart, now); if (currentCount >= maxRequests) { return false; } // Добавление нового запроса redisTemplate.opsForZSet().add(key, UUID.randomUUID().toString(), now); redisTemplate.expire(key, window); return true; } } ``` ### 8. Audit Logging для межсервисного взаимодействия **Structured Audit Logging:** ```java @Component public class InterServiceAuditLogger { private static final Logger auditLogger = LoggerFactory.getLogger("AUDIT"); public void logServiceCall(String sourceService, String targetService, String operation, boolean success, String details) { Map auditData = Map.of( "timestamp", Instant.now().toString(), "source_service", sourceService, "target_service", targetService, "operation", operation, "success", success, "details", details, "correlation_id", MDC.get("correlationId") ); auditLogger.info("Inter-service call: {}", auditData); } } ``` ### 9. Circuit Breaker для безопасности **Resilience4j Circuit Breaker:** ```java @Component public class SecureServiceClient { private final CircuitBreaker circuitBreaker; private final Retry retry; public SecureServiceClient() { this.circuitBreaker = CircuitBreaker.ofDefaults("secure-service"); this.retry = Retry.ofDefaults("secure-service"); } @CircuitBreaker(name = "secure-service") @Retry(name = "secure-service") public ServiceResponse callSecureService(String serviceUrl, String token) { try { // Вызов защищенного сервиса return restTemplate.exchange( serviceUrl, HttpMethod.GET, createSecureRequest(token), ServiceResponse.class ).getBody(); } catch (Exception e) { // Логирование безопасности securityLogger.warn("Secure service call failed: {}", e.getMessage()); throw e; } } private HttpEntity createSecureRequest(String token) { HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(token); headers.set("X-Service-Name", "order-service"); headers.set("X-Request-ID", UUID.randomUUID().toString()); return new HttpEntity<>(headers); } } ``` ### 10. Security Monitoring и Alerting **Security Metrics:** ```java @Component public class SecurityMetrics { private final MeterRegistry meterRegistry; private final Counter authenticationFailures; private final Counter authorizationFailures; private final Timer serviceCallDuration; public SecurityMetrics(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; this.authenticationFailures = Counter.builder("security.auth.failures") .description("Authentication failures") .register(meterRegistry); this.authorizationFailures = Counter.builder("security.authz.failures") .description("Authorization failures") .register(meterRegistry); this.serviceCallDuration = Timer.builder("security.service.call.duration") .description("Secure service call duration") .register(meterRegistry); } public void recordAuthenticationFailure(String reason) { authenticationFailures.increment(Tags.of("reason", reason)); } public void recordAuthorizationFailure(String resource) { authorizationFailures.increment(Tags.of("resource", resource)); } public Timer.Sample startServiceCallTimer() { return Timer.start(meterRegistry); } } ``` ## Заключение Безопасность межсервисного взаимодействия требует комплексного подхода: 1. **Аутентификация** - проверка подлинности сервисов 2. **Авторизация** - контроль доступа к ресурсам 3. **Шифрование** - защита данных в транзите 4. **Мониторинг** - отслеживание подозрительной активности 5. **Аудит** - логирование всех операций 6. **Rate Limiting** - защита от злоупотреблений 7. **Circuit Breaker** - устойчивость к сбоям TVM и mTLS являются ключевыми технологиями для обеспечения безопасности в микросервисной архитектуре.