fix(security): CSO findings — non-root containers + .env gitignore + actuator hardening
Три прямых фикса по /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.
This commit is contained in:
+29
-1
@@ -40,7 +40,35 @@ public class SecurityConfig {
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> {
|
||||
auth.requestMatchers("/actuator/**").permitAll();
|
||||
// Actuator split — CSO finding #3.
|
||||
// Раньше /actuator/** = permitAll → /actuator/env публично отдавал
|
||||
// env vars (включая ORDINIS_KEYCLOAK_ADMIN_CLIENT_SECRET) любому
|
||||
// pod'у в кластере. Сейчас защищены только три безопасных:
|
||||
//
|
||||
// - /actuator/health/** — k8s liveness/readiness probes (бессрочно
|
||||
// open, иначе pod не Ready).
|
||||
// - /actuator/info — build info (commit, tag) для UI banner.
|
||||
// - /actuator/prometheus — Prometheus scrape (cluster-internal
|
||||
// ServiceMonitor; в принципе можно гейтить по network policy,
|
||||
// но scrape token convention'а не было).
|
||||
//
|
||||
// Остальные (env, configprops, beans, mappings, conditions,
|
||||
// loggers, refresh, metrics) — denyAll. Если оператор хочет
|
||||
// diagnose в проде — port-forward к pod и `curl localhost:8080`
|
||||
// (внутри pod нет network policy), либо temporarily exposed
|
||||
// через kubectl exec.
|
||||
//
|
||||
// Defense-in-depth: дополнительно values-prod.yaml ограничивает
|
||||
// management.endpoints.web.exposure.include = health,info,prometheus
|
||||
// на уровне Spring → даже если SecurityFilterChain поменяется,
|
||||
// endpoint'ов просто не существует.
|
||||
auth.requestMatchers(
|
||||
"/actuator/health",
|
||||
"/actuator/health/**",
|
||||
"/actuator/info",
|
||||
"/actuator/prometheus"
|
||||
).permitAll();
|
||||
auth.requestMatchers("/actuator/**").denyAll();
|
||||
auth.requestMatchers("/error").permitAll();
|
||||
// Build-info endpoint — открыт без auth: UI polling с update-banner
|
||||
// должен работать ДО login (anonymous landing → "новая версия"
|
||||
|
||||
Reference in New Issue
Block a user