Files
sa-lessons/modules/module6/sections/02-vulnerabilities-inter-service.md
Alexander Abakumov 15e744da49 Initial commit: Complete Software Architecture course with 11 modules
- Added comprehensive course structure with 11 modules
- Included theoretical content, practical examples, and tasks
- Multi-language code examples (Java, Rust, Node.js, Python)
- Detailed analysis of architectural approaches and trade-offs
- Complete caching strategies documentation
- Architecture examples from real-world systems (Twitter, Netflix, Uber)
- Structured navigation and learning path
- Ready for educational use
2025-09-09 22:08:07 +03:00

23 KiB
Raw Permalink Blame History

Уязвимости и межсервисное взаимодействие

Навигация


Типы уязвимостей в микросервисной архитектуре

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:

@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<String> 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<String> permissions;
    private Date issuedAt;
    private Date expiresAt;
}

Rust примеры - TVM с Actix Web:

// 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<String>,
    permissions: Vec<String>,
    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<String>,
}

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<ServiceInfo, String> {
        // Декодирование токена
        let token_data = decode::<ServiceClaims>(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<HttpResponse, Error> {
    // Извлечение токена из заголовка
    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:

# 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:

// 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:

@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:

# 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:

# 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:

@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:

@Service
public class InterServiceRateLimitingService {
    
    @Autowired
    private RedisTemplate<String, String> 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:

@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<String, Object> 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:

@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:

@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 являются ключевыми технологиями для обеспечения безопасности в микросервисной архитектуре.