Gymterview
senior

Как обеспечить безопасность Java-приложения в контейнере в банковской среде?

Это комплексный вопрос, объединяющий все аспекты контейнерной безопасности применительно к реальному банковскому Java-приложению. Ответ демонстрирует целостную картину: от Dockerfile до Kubernetes-манифестов, сетевых политик и чек-листа аудита.

1. Безопасный Dockerfile для банковского Java-приложения:

Пример
# ============ Этап 1: Сборка ============
FROM eclipse-temurin:21-jdk-alpine@sha256:abc123... AS builder

WORKDIR /build

# Отдельные слои для зависимостей (лучшее кэширование Docker-слоёв)
COPY pom.xml mvnw ./
COPY .mvn .mvn
RUN ./mvnw dependency:go-offline -B

COPY src ./src
RUN ./mvnw clean package -DskipTests -B \
    && java -Djarmode=layertools -jar target/*.jar extract --destination /layers

# ============ Этап 2: Финальный образ ============
FROM eclipse-temurin:21-jre-alpine@sha256:def456...

# Метаданные OCI (для аудита и трассировки)
LABEL org.opencontainers.image.title="banking-payment-service" \
      org.opencontainers.image.vendor="Bank JSC" \
      org.opencontainers.image.authors="platform-team@bank.local"

# Создание непривилегированного пользователя с фиксированным UID/GID
RUN addgroup -g 1000 -S banking && \
    adduser -u 1000 -S banking -G banking && \
    mkdir -p /app /tmp && \
    chown -R banking:banking /app /tmp

WORKDIR /app

# Копирование слоёв Spring Boot (послойное копирование для лучшего кэширования)
COPY --from=builder --chown=banking:banking /layers/dependencies/ ./
COPY --from=builder --chown=banking:banking /layers/spring-boot-loader/ ./
COPY --from=builder --chown=banking:banking /layers/snapshot-dependencies/ ./
COPY --from=builder --chown=banking:banking /layers/application/ ./

# Переключение на непривилегированного пользователя
USER banking:banking

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1

ENTRYPOINT ["java", \
    "-XX:+UseContainerSupport", \
    "-XX:MaxRAMPercentage=75.0", \
    "-XX:+ExitOnOutOfMemoryError", \
    "-Djava.io.tmpdir=/tmp", \
    "-Djava.security.egd=file:/dev/./urandom", \
    "-Dspring.profiles.active=production", \
    "org.springframework.boot.loader.launch.JarLauncher"]

Ключевые аспекты данного Dockerfile: multistage build (исходный код и build-инструменты не попадают в финальный образ), pin по digest (воспроизводимость), фиксированный UID/GID 1000 (согласованность с Kubernetes Security Context), послойное копирование Spring Boot (оптимизация кэша), HEALTHCHECK (мониторинг здоровья). Параметр -Djava.security.egd=file:/dev/./urandom ускоряет генерацию случайных чисел, что критично для TLS и криптографических операций.

2. Kubernetes Deployment:

Пример
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
  namespace: banking-production
  labels:
    app: payment-service
    tier: backend
    compliance: pci-dss
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0  # Нулевое время простоя при обновлении
  selector:
    matchLabels:
      app: payment-service
  template:
    metadata:
      labels:
        app: payment-service
        tier: backend
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "payment-service"
        vault.hashicorp.com/agent-inject-secret-db: "secret/data/banking/payment-db"
    spec:
      serviceAccountName: payment-service-sa
      automountServiceAccountToken: false

      # Pod-level Security Context
      securityContext:
        runAsNonRoot: true
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault

      # Распределение по разным нодам для отказоустойчивости
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: payment-service

      containers:
      - name: payment-service
        image: registry.bank.local/payment-service@sha256:789xyz...
        imagePullPolicy: Always

        # Container-level Security Context
        securityContext:
          runAsUser: 1000
          runAsGroup: 1000
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          privileged: false
          capabilities:
            drop: ["ALL"]

        ports:
        - containerPort: 8080
          name: http
          protocol: TCP

        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
            ephemeral-storage: "100Mi"
          limits:
            memory: "1Gi"
            cpu: "2"
            ephemeral-storage: "500Mi"

        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: http
          initialDelaySeconds: 60
          periodSeconds: 10
          failureThreshold: 3

        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: http
          initialDelaySeconds: 30
          periodSeconds: 5
          failureThreshold: 3

        volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: logs
          mountPath: /app/logs

        env:
        - name: JAVA_OPTS
          value: "-XX:MaxRAMPercentage=75.0"
        - name: SPRING_PROFILES_ACTIVE
          value: "production"

      volumes:
      - name: tmp
        emptyDir:
          medium: Memory     # tmpfs — хранение в RAM, не на диске
          sizeLimit: 100Mi
      - name: logs
        emptyDir:
          sizeLimit: 500Mi

      imagePullSecrets:
      - name: registry-credentials

maxUnavailable: 0 в стратегии RollingUpdate гарантирует, что во время обновления всегда доступно минимальное количество реплик — нулевое время простоя. topologySpreadConstraints распределяет реплики по разным физическим нодам, обеспечивая отказоустойчивость при выходе ноды из строя. Аннотации vault.hashicorp.com/* настраивают Vault Agent Injector для автоматической подстановки секретов. ephemeral-storage ограничивает использование временного дискового пространства контейнером.

3. Network Policy для payment-service:

Пример
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: payment-service-policy
  namespace: banking-production
spec:
  podSelector:
    matchLabels:
      app: payment-service
  policyTypes:
  - Ingress
  - Egress

  ingress:
  # Принимать трафик только от API Gateway
  - from:
    - podSelector:
        matchLabels:
          app: api-gateway
    ports:
    - protocol: TCP
      port: 8080

  egress:
  # К базе данных PostgreSQL
  - to:
    - podSelector:
        matchLabels:
          app: payment-db
    ports:
    - protocol: TCP
      port: 5432
  # К Kafka
  - to:
    - podSelector:
        matchLabels:
          app: kafka
    ports:
    - protocol: TCP
      port: 9092
  # DNS (обязателен для резолвинга имён сервисов)
  - to:
    - namespaceSelector: {}
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53
  # К Vault (для получения секретов)
  - to:
    - namespaceSelector:
        matchLabels:
          name: vault
    ports:
    - protocol: TCP
      port: 8200

Эта Network Policy реализует принцип наименьших привилегий на сетевом уровне: payment-service может принимать входящий трафик только от api-gateway и инициировать исходящие подключения только к базе данных, Kafka, DNS и Vault. Любые другие сетевые подключения (например, попытка обратиться к другому микросервису или к внешнему IP) будут заблокированы.

4. Настройка namespace с принудительной безопасностью:

Пример
apiVersion: v1
kind: Namespace
metadata:
  name: banking-production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted
    compliance: pci-dss

Уровень restricted в Pod Security Standards запрещает создание подов, не соответствующих строгим требованиям безопасности: запуск от root, privileged, без seccomp-профиля и т.д. Это гарантирует, что даже при ошибке в манифесте небезопасный под не будет запущен.

5. Spring Boot Security конфигурация (application-production.yml):

Пример
server:
  port: 8080
  ssl:
    enabled: false  # TLS терминируется на Ingress Controller или Service Mesh

management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus  # Минимальный набор эндпоинтов
  endpoint:
    health:
      show-details: never  # Не раскрывать детали подключений к БД и т.д. в production
      probes:
        enabled: true      # Включить /health/liveness и /health/readiness

spring:
  jackson:
    default-property-inclusion: non_null
    serialization:
      fail-on-empty-beans: false

# Логирование: не логировать чувствительные данные
logging:
  level:
    org.springframework.security: WARN
    org.hibernate.SQL: WARN
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

Ключевые решения: TLS не на уровне приложения (терминируется на Ingress или Service Mesh — централизованное управление сертификатами), минимальный набор Actuator-эндпоинтов (не раскрывать /env, /beans, /configprops), show-details: never (не показывать информацию о внутренней инфраструктуре), уровень логирования для security-модулей — WARN (не логировать попытки аутентификации на уровне DEBUG, чтобы избежать утечки токенов и паролей).

6. Итоговый чек-лист безопасности для банковского Java-приложения в контейнерах:

Пример
ОБРАЗ:
  [  ] Multistage build (исходники, build-инструменты не в финальном образе)
  [  ] Минимальный базовый образ (Alpine или Distroless)
  [  ] Pin по digest (@sha256:...), не по тегу
  [  ] USER — непривилегированный с фиксированным UID/GID
  [  ] HEALTHCHECK указан в Dockerfile
  [  ] Нет секретов в слоях образа (проверено через docker history и secret scan)
  [  ] COPY вместо ADD (ADD может распаковывать архивы и скачивать URL)
  [  ] .dockerignore настроен (исключены .git, .env, .idea, target)

KUBERNETES:
  [  ] runAsNonRoot: true
  [  ] readOnlyRootFilesystem: true
  [  ] allowPrivilegeEscalation: false
  [  ] capabilities: drop ALL
  [  ] seccompProfile: RuntimeDefault
  [  ] Resource limits (memory, cpu, ephemeral-storage) установлены
  [  ] Network Policies применены (deny-by-default + явные разрешения)
  [  ] ServiceAccount с минимальными правами (RBAC)
  [  ] automountServiceAccountToken: false
  [  ] Pod Security Standards: restricted на namespace

СЕКРЕТЫ:
  [  ] Vault / Sealed Secrets / External Secrets Operator
  [  ] Encryption at rest включён для Kubernetes Secrets
  [  ] Нет секретов в Git, переменных окружения, Dockerfile

CI/CD:
  [  ] SAST (SonarQube, SpotBugs + FindSecBugs)
  [  ] SCA (OWASP Dependency Check или Snyk)
  [  ] Image scanning (Trivy) с блокировкой при CRITICAL
  [  ] Secret detection (Trivy/GitLeaks)
  [  ] Dockerfile linting (hadolint)
  [  ] K8s manifest scanning (trivy config / kubesec)
  [  ] Подпись образов (Cosign)
  [  ] SBOM генерация и прикрепление к образу

RUNTIME:
  [  ] Falco для мониторинга аномалий
  [  ] Централизованное логирование (ELK/EFK)
  [  ] Мониторинг метрик (Prometheus + Grafana)
  [  ] Регулярное обновление базовых образов с SLA по CVSS

Безопасность контейнеров — это не разовое действие, а непрерывный процесс. В банковской среде это требование регуляторов (ЦБ РФ, PCI DSS), и его несоблюдение влечёт серьёзные финансовые и репутационные последствия. Каждый пункт данного чек-листа должен быть реализован и регулярно проверяться в рамках аудитов безопасности.