From 7abff8f73ff9a042a5d03b81654827eb427fa981 Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Wed, 27 May 2026 19:02:34 +0300 Subject: [PATCH] =?UTF-8?q?fix(security):=20CSO=20findings=20=E2=80=94=20n?= =?UTF-8?q?on-root=20containers=20+=20.env=20gitignore=20+=20actuator=20ha?= =?UTF-8?q?rdening?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Три прямых фикса по /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. --- .gitignore | 10 +++++++ ordinis-app/Dockerfile | 9 +++++- .../src/main/resources/application.yml | 23 +++++++++----- .../ordinis/auth/SecurityConfig.java | 30 ++++++++++++++++++- ordinis-projection-writer/Dockerfile | 6 +++- .../src/main/resources/application.yml | 3 +- ordinis-read-api/Dockerfile | 7 ++++- .../src/main/resources/application.yml | 4 ++- 8 files changed, 78 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 93f9a1d..ec14ecc 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,13 @@ HELP.md *.key .gstack/ .claude/ +# Local env files — CSO finding #2. dotenv pattern предполагает не-committed +# .env с реальными creds для local dev. Без этого правила `git add .` засосёт +# secrets. Whitelist .env.example / .env.template / .env.sample — шаблоны. +.env +.env.local +.env.*.local +*.env +!.env.example +!.env.template +!.env.sample diff --git a/ordinis-app/Dockerfile b/ordinis-app/Dockerfile index 9e06718..165c353 100644 --- a/ordinis-app/Dockerfile +++ b/ordinis-app/Dockerfile @@ -30,7 +30,14 @@ RUN mvn -B -pl ordinis-app -am package -DskipTests \ -Dgit.commit.time="$GIT_TIME" FROM repo.nstart.cloud/library/eclipse-temurin:21-jre +# Non-root user — security hardening (CSO report 2026-05-27 finding #1). +# Eclipse-temurin runtime image не задаёт USER by default → JVM от root. +# Compromise через Spring Boot CVE / Jackson deserialization без USER +# становится root-в-контейнере → escalation surface на ноду. UID 10001 +# не конфликтует с system users (0-999) и distribution-reserved (1000-9999). +RUN groupadd -r -g 10001 app && useradd -r -u 10001 -g app -s /sbin/nologin app WORKDIR /app -COPY --from=build /build/ordinis-app/target/*.jar /app/app.jar +COPY --from=build --chown=app:app /build/ordinis-app/target/*.jar /app/app.jar +USER app EXPOSE 8080 ENTRYPOINT ["java", "-XX:+UseZGC", "-XX:MaxRAMPercentage=75", "-jar", "/app/app.jar"] diff --git a/ordinis-app/src/main/resources/application.yml b/ordinis-app/src/main/resources/application.yml index 330b395..2e68acd 100644 --- a/ordinis-app/src/main/resources/application.yml +++ b/ordinis-app/src/main/resources/application.yml @@ -12,14 +12,21 @@ management: endpoints: web: exposure: - # `conditions`, `beans`, `configprops`, `loggers` — diagnostic четвёрка - # для autoconfig debug (например: почему MailSenderAutoConfiguration - # не fired при наличии SPRING_MAIL_HOST env var и starter-mail на - # classpath). `loggers` runtime-toggle позволяет крутить - # `org.springframework.boot.autoconfigure` в DEBUG без redeploy. - # Endpoints sensitive — exposed только на staging behind cluster-internal - # Ingress (auth.required=true в prod). - include: health,info,prometheus,refresh,metrics,env,mappings,conditions,beans,configprops,loggers + # 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: diff --git a/ordinis-auth/src/main/java/cloud/nstart/terravault/ordinis/auth/SecurityConfig.java b/ordinis-auth/src/main/java/cloud/nstart/terravault/ordinis/auth/SecurityConfig.java index 3e036b3..f2b7bfb 100644 --- a/ordinis-auth/src/main/java/cloud/nstart/terravault/ordinis/auth/SecurityConfig.java +++ b/ordinis-auth/src/main/java/cloud/nstart/terravault/ordinis/auth/SecurityConfig.java @@ -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 → "новая версия" diff --git a/ordinis-projection-writer/Dockerfile b/ordinis-projection-writer/Dockerfile index fc15810..d8f50cc 100644 --- a/ordinis-projection-writer/Dockerfile +++ b/ordinis-projection-writer/Dockerfile @@ -7,7 +7,11 @@ COPY . ./ RUN mvn -B -pl ordinis-projection-writer -am package -DskipTests FROM repo.nstart.cloud/library/eclipse-temurin:21-jre +# Non-root user — security hardening (CSO report 2026-05-27 finding #1). +# См. ordinis-app/Dockerfile для rationale. +RUN groupadd -r -g 10001 app && useradd -r -u 10001 -g app -s /sbin/nologin app WORKDIR /app -COPY --from=build /build/ordinis-projection-writer/target/*.jar /app/app.jar +COPY --from=build --chown=app:app /build/ordinis-projection-writer/target/*.jar /app/app.jar +USER app EXPOSE 8082 ENTRYPOINT ["java", "-XX:+UseZGC", "-XX:MaxRAMPercentage=75", "-jar", "/app/app.jar"] diff --git a/ordinis-projection-writer/src/main/resources/application.yml b/ordinis-projection-writer/src/main/resources/application.yml index 39eac18..9c44ab3 100644 --- a/ordinis-projection-writer/src/main/resources/application.yml +++ b/ordinis-projection-writer/src/main/resources/application.yml @@ -14,7 +14,8 @@ management: endpoints: web: exposure: - include: health,info,prometheus,refresh,metrics + # CSO finding #3 — defense-in-depth. См. ordinis-app/application.yml. + include: ${MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE:health,info,prometheus} endpoint: health: probes: diff --git a/ordinis-read-api/Dockerfile b/ordinis-read-api/Dockerfile index 8ae5002..5cfaddd 100644 --- a/ordinis-read-api/Dockerfile +++ b/ordinis-read-api/Dockerfile @@ -7,7 +7,12 @@ COPY . ./ RUN mvn -B -pl ordinis-read-api -am package -DskipTests FROM repo.nstart.cloud/library/eclipse-temurin:21-jre +# Non-root user — security hardening (CSO report 2026-05-27 finding #1). +# См. ordinis-app/Dockerfile для rationale (compromise → root в контейнере +# = escalation surface на ноду). UID 10001 outside system+distro reserved. +RUN groupadd -r -g 10001 app && useradd -r -u 10001 -g app -s /sbin/nologin app WORKDIR /app -COPY --from=build /build/ordinis-read-api/target/*.jar /app/app.jar +COPY --from=build --chown=app:app /build/ordinis-read-api/target/*.jar /app/app.jar +USER app EXPOSE 8081 ENTRYPOINT ["java", "-XX:+UseZGC", "-XX:MaxRAMPercentage=75", "-jar", "/app/app.jar"] diff --git a/ordinis-read-api/src/main/resources/application.yml b/ordinis-read-api/src/main/resources/application.yml index d398a90..4616bfd 100644 --- a/ordinis-read-api/src/main/resources/application.yml +++ b/ordinis-read-api/src/main/resources/application.yml @@ -12,7 +12,9 @@ management: endpoints: web: exposure: - include: health,info,prometheus,refresh,metrics,env,mappings + # CSO finding #3 — defense-in-depth. См. ordinis-app/application.yml + # для rationale (narrow default, env-override для staging diagnostic). + include: ${MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE:health,info,prometheus} endpoint: health: probes: