Как обеспечить безопасность 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), и его несоблюдение влечёт серьёзные финансовые и репутационные последствия. Каждый пункт данного чек-листа должен быть реализован и регулярно проверяться в рамках аудитов безопасности.