7abff8f73f
Три прямых фикса по /cso security audit на v2.45.2: ## Finding #1 (HIGH): Backend containers run as root ordinis-app / ordinis-read-api / ordinis-projection-writer Dockerfile'ы запускали JVM от root (eclipse-temurin:21-jre default'но не задаёт USER). Compromise через Spring CVE / Jackson deserialization → root-в-контейнере = escalation surface на ноду через любой pod misconfig. Добавлено: `RUN groupadd -r -g 10001 app && useradd -r -u 10001 -g app -s /sbin/nologin app` + `USER app`. UID 10001 — outside system (0-999) и distribution reserved (1000-9999). `--chown=app:app` на COPY чтобы JVM могла прочитать jar. ordinis-migrations (USER liquibase) и docs (nginx default) уже non-root. ## Finding #2 (HIGH): .env / .env.local not in .gitignore `.gitignore` блокировал *.pem, *.key, .gstack/, .claude/ — но не .env. Развработчик с realный dotenv для local dev мог случайно `git add .` и закоммитить creds. Сейчас нет committed .env (verified) но защита нулевая. Добавлено: `.env`, `.env.local`, `.env.*.local`, `*.env` + whitelist `!.env.example`, `!.env.template`, `!.env.sample` для шаблонов. Существующий tracked `ordinis-admin-ui/.env.example` остался tracked (verified `git ls-files | grep .env.example`). ## Finding #3 (MEDIUM): Spring Actuator permitAll() — defense-in-depth gap SecurityConfig had `requestMatchers("/actuator/**").permitAll()` → любой pod в кластере мог `curl ordinis-app:8080/actuator/env` и получить ORDINIS_KEYCLOAK_ADMIN_CLIENT_SECRET, DB creds и пр. VERIFIED: nginx ingress на проде НЕ проксирует /actuator/* (SPA fallback отдаёт index.html), так что не exposed на интернет. Но pod-to-pod в кластере = открыто. Defense in two layers: 1. SecurityConfig: split actuator routes — /actuator/health/**, /actuator/info, /actuator/prometheus → permitAll /actuator/** → denyAll Probes/Prometheus scrape работают, остальное блок. 2. application.yml для всех трёх Spring apps: narrowed default exposure to "health,info,prometheus". Поддерживается env override MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE для staging diagnostic (включить env/configprops/loggers только когда нужно дебажить autoconfig). ## Тесты - mvn package — все 15 модулей SUCCESS - ordinis-rest-api + ordinis-auth tests — SUCCESS ## Manual verify post-deploy После пророллится v2.45.3: - staging+prod liveness/readiness probes должны остаться зелёными (/actuator/health/{liveness,readiness} в permitAll list). - Prometheus scrape /actuator/prometheus → 200. - ssh pod && curl localhost:8080/actuator/env → должен вернуть 401/403 (или 404 если endpoint вообще выключен через exposure). ## Откат Если probes сломаются (маловероятно — health endpoints whitelisted) — revert этого MR. Image rollback к v2.45.2 через helm.
279 lines
11 KiB
YAML
279 lines
11 KiB
YAML
spring:
|
||
application:
|
||
name: ordinis-app
|
||
profiles:
|
||
active: ${SPRING_PROFILES_ACTIVE:dev}
|
||
|
||
server:
|
||
port: 8080
|
||
shutdown: graceful
|
||
|
||
management:
|
||
endpoints:
|
||
web:
|
||
exposure:
|
||
# CSO finding #3 — defense-in-depth. Default exposure narrowed к трём
|
||
# безопасным: health (k8s probes), info (UI banner), prometheus
|
||
# (ServiceMonitor scrape). Sensitive endpoints (env, configprops,
|
||
# beans, mappings, conditions, loggers) выключены by default —
|
||
# утечка ORDINIS_KEYCLOAK_ADMIN_CLIENT_SECRET через /actuator/env
|
||
# потенциальна на любой pod-to-pod атаке.
|
||
#
|
||
# Diagnostic четвёрка (conditions/beans/configprops/loggers) +
|
||
# debug-set (env/mappings/refresh/metrics) включаются через env
|
||
# override `MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE` на staging
|
||
# — для autoconfig debug (например: почему
|
||
# MailSenderAutoConfiguration не fired при наличии SPRING_MAIL_HOST
|
||
# env var). На prod env override не ставим → defense вместе с
|
||
# SecurityConfig denyAll на /actuator/{env,beans,...}.
|
||
include: ${MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE:health,info,prometheus}
|
||
base-path: /actuator
|
||
endpoint:
|
||
health:
|
||
probes:
|
||
enabled: true
|
||
show-details: when-authorized
|
||
group:
|
||
liveness:
|
||
include: livenessState
|
||
readiness:
|
||
include: readinessState,db
|
||
health:
|
||
# Redis health indicator отключён в writer pod. Writer не использует Redis
|
||
# напрямую (нет @Cacheable, нет RedisTemplate), но depends на
|
||
# ordinis-notifications module который включает spring-boot-starter-data-redis
|
||
# transitive'но (для будущего @EnableCaching). Spring autoconfig пытается
|
||
# connect к localhost:6379 → fail → /actuator/health = DOWN, забивает
|
||
# readiness alerts.
|
||
# Если writer когда-либо понадобится реальный Redis — wire env vars
|
||
# (см. projection-writer.yaml образец) и убрать этот override.
|
||
redis:
|
||
enabled: false
|
||
prometheus:
|
||
metrics:
|
||
export:
|
||
enabled: true
|
||
metrics:
|
||
tags:
|
||
application: ${spring.application.name}
|
||
environment: ${ENVIRONMENT:dev}
|
||
distribution:
|
||
# Server-side histogram buckets для http.server.requests — нужны
|
||
# histogram_quantile() в Grafana p50/p95/p99 dashboards.
|
||
percentiles-histogram:
|
||
http.server.requests: true
|
||
slo:
|
||
http.server.requests: 5ms,10ms,25ms,50ms,100ms,250ms,500ms,1s,2s,5s
|
||
|
||
otel:
|
||
exporter:
|
||
otlp:
|
||
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:http://tempo.monitoring:4318}
|
||
protocol: http/protobuf
|
||
# Tempo (single-process на staging) принимает только traces. Если включить
|
||
# экспорт metrics/logs — каждую секунду 404 в логах ("Failed to export logs").
|
||
# Когда поднимем полный observability stack (Tempo + Mimir + Loki) — снимем
|
||
# эти overrides либо переопределим через ENV.
|
||
metrics:
|
||
exporter: ${OTEL_METRICS_EXPORTER:none}
|
||
logs:
|
||
exporter: ${OTEL_LOGS_EXPORTER:none}
|
||
resource:
|
||
attributes:
|
||
service.name: ${spring.application.name}
|
||
service.namespace: ordinis
|
||
deployment.environment: ${ENVIRONMENT:dev}
|
||
traces:
|
||
sampler: parentbased_traceidratio
|
||
sampler.arg: ${ORDINIS_TRACING_SAMPLING:0.1}
|
||
|
||
logging:
|
||
level:
|
||
root: INFO
|
||
cloud.nstart.terravault.ordinis: DEBUG
|
||
|
||
ordinis:
|
||
outbox:
|
||
enabled: ${ORDINIS_OUTBOX_ENABLED:true}
|
||
poll-interval-ms: ${ORDINIS_OUTBOX_POLL_INTERVAL_MS:1000}
|
||
batch-size: ${ORDINIS_OUTBOX_BATCH_SIZE:100}
|
||
# Cap на ретраи: после N попыток событие → DLQ (dlq_at), poller его пропускает.
|
||
# 1000 при poll=1s ≈ ~17 минут активных ошибок (или быстрее на fail-fast топиках).
|
||
attempt-max: ${ORDINIS_OUTBOX_ATTEMPT_MAX:1000}
|
||
|
||
springdoc:
|
||
# Per env policy:
|
||
# prod → swagger UI disabled (real-user environment, не для exploration);
|
||
# api-docs JSON остаётся для SDK generation / machine consumers
|
||
# staging/dev → оба enabled (interactive playground для интеграторов)
|
||
# Controlled via ORDINIS_SWAGGER_UI_ENABLED env var (helm values-prod.yaml:
|
||
# springdoc.swaggerUi=false). Default true для backward compat.
|
||
api-docs:
|
||
path: /v3/api-docs
|
||
enabled: ${ORDINIS_SWAGGER_API_DOCS_ENABLED:true}
|
||
swagger-ui:
|
||
path: /swagger-ui.html
|
||
enabled: ${ORDINIS_SWAGGER_UI_ENABLED:true}
|
||
operations-sorter: method
|
||
tags-sorter: alpha
|
||
display-request-duration: true
|
||
# OAuth2 / Keycloak interactive login (PKCE flow). См. OpenApiConfig.
|
||
# Public client `ordinis-swagger` создаётся в Keycloak realm `nstart` с
|
||
# redirect URI = https://<host>/swagger-ui/oauth2-redirect.html
|
||
oauth:
|
||
client-id: ${ORDINIS_SWAGGER_CLIENT_ID:ordinis-swagger}
|
||
use-pkce-with-authorization-code-grant: true
|
||
use-basic-authentication-with-access-code-grant: false
|
||
scope-separator: " "
|
||
show-actuator: false
|
||
packages-to-scan: cloud.nstart.terravault.ordinis.restapi.web
|
||
|
||
---
|
||
# DEV profile: подключается к cluster PG+Kafka через kubectl port-forward.
|
||
# Перед запуском: kubectl port-forward -n ordinis-staging svc/ordinis-postgres-rw 5432:5432 &
|
||
# kubectl port-forward -n ordinis-staging svc/ordinis-kafka-kafka-bootstrap 9092:9092 &
|
||
spring:
|
||
config:
|
||
activate:
|
||
on-profile: dev
|
||
cloud:
|
||
config:
|
||
enabled: false
|
||
vault:
|
||
enabled: false
|
||
autoconfigure:
|
||
exclude:
|
||
- org.springframework.cloud.vault.config.VaultAutoConfiguration
|
||
- org.springframework.cloud.vault.config.VaultObservationAutoConfiguration
|
||
- org.springframework.cloud.vault.config.VaultReactiveAutoConfiguration
|
||
datasource:
|
||
url: jdbc:postgresql://${ORDINIS_PG_HOST:localhost}:${ORDINIS_PG_PORT:5432}/${ORDINIS_PG_DB:ordinis}
|
||
username: ${ORDINIS_PG_USER:ordinis_app}
|
||
password: ${ORDINIS_PG_PASSWORD:}
|
||
driver-class-name: org.postgresql.Driver
|
||
jpa:
|
||
hibernate:
|
||
ddl-auto: update
|
||
properties:
|
||
hibernate.dialect: org.hibernate.dialect.PostgreSQLDialect
|
||
hibernate.format_sql: true
|
||
hibernate.jdbc.lob.non_contextual_creation: true
|
||
open-in-view: false
|
||
kafka:
|
||
bootstrap-servers: ${ORDINIS_KAFKA_BOOTSTRAP:localhost:9092}
|
||
properties:
|
||
security.protocol: ${ORDINIS_KAFKA_SECURITY:SASL_PLAINTEXT}
|
||
sasl.mechanism: ${ORDINIS_KAFKA_SASL_MECHANISM:SCRAM-SHA-512}
|
||
sasl.jaas.config: >-
|
||
org.apache.kafka.common.security.scram.ScramLoginModule required
|
||
username="${ORDINIS_KAFKA_USER:ordinis-app}"
|
||
password="${ORDINIS_KAFKA_PASSWORD:}";
|
||
producer:
|
||
acks: all
|
||
key-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||
value-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||
properties:
|
||
enable.idempotence: true
|
||
compression.type: lz4
|
||
|
||
otel:
|
||
sdk:
|
||
disabled: true
|
||
|
||
---
|
||
# K8s profile: реальная инфра в кластере. Cloud Config + Vault Kubernetes auth.
|
||
spring:
|
||
config:
|
||
activate:
|
||
on-profile: k8s
|
||
import:
|
||
- "configserver:${SPRING_CLOUD_CONFIG_URI:http://config-server.ordinis-staging:8888}"
|
||
- "vault://"
|
||
cloud:
|
||
config:
|
||
enabled: true
|
||
vault:
|
||
enabled: true
|
||
uri: ${VAULT_URI:http://vault.config:8200}
|
||
authentication: KUBERNETES
|
||
kubernetes:
|
||
role: ordinis-app
|
||
service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||
kv:
|
||
enabled: true
|
||
backend: secret
|
||
application-name: ordinis/cuod
|
||
datasource:
|
||
url: jdbc:postgresql://${ORDINIS_PG_HOST}:${ORDINIS_PG_PORT:5432}/${ORDINIS_PG_DB:ordinis}
|
||
username: ${ORDINIS_PG_USER}
|
||
password: ${ORDINIS_PG_PASSWORD}
|
||
driver-class-name: org.postgresql.Driver
|
||
kafka:
|
||
bootstrap-servers: ${ORDINIS_KAFKA_BOOTSTRAP}
|
||
properties:
|
||
security.protocol: ${ORDINIS_KAFKA_SECURITY:SASL_PLAINTEXT}
|
||
sasl.mechanism: ${ORDINIS_KAFKA_SASL_MECHANISM:SCRAM-SHA-512}
|
||
sasl.jaas.config: >-
|
||
org.apache.kafka.common.security.scram.ScramLoginModule required
|
||
username="${ORDINIS_KAFKA_USER}"
|
||
password="${ORDINIS_KAFKA_PASSWORD}";
|
||
producer:
|
||
acks: all
|
||
key-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||
value-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||
properties:
|
||
enable.idempotence: true
|
||
compression.type: lz4
|
||
jpa:
|
||
hibernate:
|
||
ddl-auto: none
|
||
open-in-view: false
|
||
|
||
# Auth (см. ordinis-auth: SecurityConfig). public-key из Vault.
|
||
ordinis:
|
||
auth:
|
||
public-key: ${key:}
|
||
issuer: ${jwt-issuer:}
|
||
require-authentication: ${ORDINIS_AUTH_REQUIRED:false}
|
||
allow-query-scope: ${ORDINIS_AUTH_ALLOW_QUERY_SCOPE:true}
|
||
|
||
# AI Schema Assist (Phase 1 step 3-6). Off by default — controllers/services
|
||
# bean-conditionally registered, frontend hides AI button when 404.
|
||
# Enable per-env через helm values (см. charts/ordinis/values-staging.yaml).
|
||
# endpoint: OpenAI-compatible /v1/chat/completions root (без /v1 suffix —
|
||
# adapter добавит сам). bearer-token optional (Ollama не требует, OpenAI да).
|
||
ai:
|
||
enabled: ${ORDINIS_AI_ENABLED:false}
|
||
endpoint: ${ORDINIS_AI_ENDPOINT:}
|
||
model: ${ORDINIS_AI_MODEL:qwen2.5:7b}
|
||
bearer-token: ${ORDINIS_AI_BEARER_TOKEN:}
|
||
timeout-seconds: ${ORDINIS_AI_TIMEOUT_SECONDS:15}
|
||
|
||
# Keycloak Admin REST integration for UserDisplayService Phase 2 — on-demand
|
||
# sub→user lookup when JWT capture cache + DB cache both miss. See
|
||
# KeycloakAdminUserResolver.java + 0023-user-display-cache.xml migration.
|
||
# Disabled by default; flip on per-environment when the service-account
|
||
# client + vault secret are provisioned.
|
||
keycloak:
|
||
admin:
|
||
enabled: ${ORDINIS_KEYCLOAK_ADMIN_ENABLED:false}
|
||
url: ${ORDINIS_KEYCLOAK_ADMIN_URL:}
|
||
realm: ${ORDINIS_KEYCLOAK_ADMIN_REALM:nstart}
|
||
client-id: ${ORDINIS_KEYCLOAK_ADMIN_CLIENT_ID:}
|
||
client-secret: ${ORDINIS_KEYCLOAK_ADMIN_CLIENT_SECRET:}
|
||
|
||
# Notifications module — push email for draft lifecycle events.
|
||
# Activated via ordinis.notifications.enabled=true (writer pod only).
|
||
# SMTP creds: spring.mail.* (standard Spring Boot mail properties).
|
||
# See ordinis-notifications module + migration 0021/0024.
|
||
notifications:
|
||
enabled: ${ORDINIS_NOTIFICATIONS_ENABLED:true}
|
||
poll-interval-ms: ${ORDINIS_NOTIFICATIONS_POLL_INTERVAL_MS:30000}
|
||
batch-size: ${ORDINIS_NOTIFICATIONS_BATCH_SIZE:50}
|
||
# Reviewer pool email — receives RecordDraftSubmitted / RecordDraftWithdrawn pings.
|
||
# Leave blank to disable reviewer-pool notifications (maker-only notifications still work).
|
||
reviewer-pool-email: ${ORDINIS_NOTIFICATIONS_REVIEWER_POOL_EMAIL:}
|
||
email:
|
||
from-address: ${ORDINIS_NOTIFICATIONS_FROM:}
|
||
base-url: ${ORDINIS_NOTIFICATIONS_BASE_URL:}
|