From 87c45a7109a369f6f3afd3ec1e8dfb14072f7f3a Mon Sep 17 00:00:00 2001 From: Dipesh Mittal Date: Mon, 27 Apr 2026 20:01:03 +0530 Subject: [PATCH 1/2] experiment: switch runtime base to python:3.12-alpine (musl) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eliminates CVE-2026-5450 (glibc scanf %mc heap overflow, CVSS 9.8) by moving away from glibc entirely. musl libc is unaffected by the CVE since it has its own scanf implementation. Empirical comparison vs trixie v3 (jf docker scan against trialwyikk8): | Metric | Trixie v3 | Alpine | Δ | | --- | --- | --- | --- | | CVE-2026-5450 | Critical, Applicable | gone | fixed | | Critical total | 1 | 0 | -1 | | High total | 12 | 4 | -8 | | Medium total | 18 | 13 | -5 | | Low total | 50 | 0 (2 Unknown) | -48 | | Secrets (the 59) | 59 | 59 (identical) | same | | Image size | 365 MB | 272 MB | -25% | The 4 remaining Highs: - CVE-2018-20225 (pip, disputed) — already in our triage - CVE-2026-3805 (curl/libcurl 8.17.0-r1) — Alpine 3.23 awaiting backport (fix exists in 8.19.0-r0 on edge) - CVE-2026-27135 (nghttp2-libs) — Xray CSA marks Not Applicable Build deltas vs the trixie Dockerfile: - Builder: +git, +mariadb-dev, +pkgconfig (apk equivalents) - Runtime: +bash, +tzdata, +musl-locales, +libstdc++, +libgcc, +libxml2, +libxslt, +lz4-libs (clickhouse-connect dep), +mariadb-connector-c, +procps-ng, +file, +ca-certificates - nginx config path moved from /etc/nginx/sites-available/default to /etc/nginx/http.d/default.conf (Alpine convention) - chown changed from www-data to nginx (nginx package's own user on Alpine) - PYTHONTHREADSTACKSIZE=8388608 set explicitly to match glibc default; musl's 80KB default would segfault Celery threadpool workers under load Smoke-tested 27 critical Python imports (django, celery, cryptography, psycopg2, mysqlclient, pymysql, google.auth, google.cloud, kubernetes, msal, azure.identity, boto3, awscli, oauthlib, requests_oauthlib, sqlalchemy, clickhouse_connect, slack_sdk, prometheus_client, grpc, jwt, yaml, drdroid_debug_toolkit, etc.) — all pass. Smoke-tested nginx config (`nginx -t` passes), kubectl install (v1.36.0), bash and otterize. NOT YET TESTED: - End-to-end agent boot (gunicorn + Celery worker + Celery beat + nginx orchestration) under realistic load - Connector calls against live targets (Grafana, Datadog, K8s, AWS, Azure, GCP, ArgoCD) - Helm chart deployment in a Kind cluster - musl thread stack sizing under Celery load (mitigated by PYTHONTHREADSTACKSIZE but unverified) - DNS resolver edge cases vs glibc Treat this branch as an experiment; do not merge until the above runtime tests are complete. Co-Authored-By: Claude Opus 4.7 (1M context) --- Dockerfile | 96 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 33 deletions(-) diff --git a/Dockerfile b/Dockerfile index 701d085..3d2d906 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,22 @@ -FROM python:3.12-bookworm AS builder - -RUN apt-get update \ - && apt-get upgrade -y \ - && apt-get install -y build-essential libpq-dev gettext curl \ - && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ - && rm -rf /var/lib/apt/lists/* - -# Install Rust for building dependencies -ENV PATH="/root/.cargo/bin:${PATH}" -RUN curl https://sh.rustup.rs -sSf | sh -s -- -y +FROM python:3.12-alpine AS builder + +# Build deps for native Python wheels (cryptography → libffi+openssl+rust; +# psycopg2 → postgresql-dev; lxml → libxml2-dev+libxslt-dev; etc.). Cargo+rust +# are needed by `cryptography` when no musllinux wheel is available. +RUN apk add --no-cache \ + build-base \ + postgresql-dev \ + mariadb-dev \ + libffi-dev \ + openssl-dev \ + libxml2-dev \ + libxslt-dev \ + pkgconfig \ + gettext \ + curl \ + git \ + cargo \ + rust WORKDIR /build @@ -87,32 +95,51 @@ RUN rm -rf /build/deps/allauth /build/deps/allauth-*.dist-info \ /build/deps/sqlalchemy/dialects/mssql/pyodbc.py # ---- Runtime stage ---- -FROM python:3.12-slim-trixie - -RUN apt-get update \ - && apt-get upgrade -y \ - && apt-get install -y --no-install-recommends \ - libpq5 \ - nginx \ - procps \ - curl \ +FROM python:3.12-alpine + +# Runtime deps. Notes: +# - libpq: PostgreSQL client lib (psycopg2 needs at runtime) +# - nginx: reverse proxy in front of gunicorn +# - bash: scripts/start-*.sh use /bin/bash +# - tzdata: Django timezone-aware datetime + APScheduler need /usr/share/zoneinfo +# - musl-locales: Django i18n / gettext locale lookups +# - libstdc++ + libgcc: required for some compiled wheels at runtime +# - file: used during otterize install (mime-detection of the tarball) +RUN apk add --no-cache \ + libpq \ + nginx \ + bash \ + tzdata \ + musl-locales \ + libstdc++ \ + libgcc \ + libxml2 \ + libxslt \ + lz4-libs \ + mariadb-connector-c \ + procps-ng \ + curl \ + file \ + ca-certificates \ + && update-ca-certificates \ # Install kubectl - && ARCH=$(dpkg --print-architecture) \ + && ARCH=$(uname -m) \ + && case "$ARCH" in x86_64) ARCH=amd64 ;; aarch64) ARCH=arm64 ;; esac \ && curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/${ARCH}/kubectl" \ && install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl \ && rm kubectl \ # Install Otterize CLI (optional - skip if download fails) && (curl -LJO https://get.otterize.com/otterize-cli/v2.0.3/otterize_linux_x86_64.tar.gz && \ - file otterize_linux_x86_64.tar.gz | grep -q "gzip compressed" && \ - tar xf otterize_linux_x86_64.tar.gz && \ - install -o root -g root -m 0755 otterize /usr/local/bin/otterize && \ - rm otterize_linux_x86_64.tar.gz) || echo "Otterize CLI installation skipped" \ - # Clean up - && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ - && rm -rf /var/lib/apt/lists/* - -COPY nginx.default /etc/nginx/sites-available/default -RUN ln -sf /dev/stdout /var/log/nginx/access.log \ + file otterize_linux_x86_64.tar.gz | grep -q "gzip compressed" && \ + tar xf otterize_linux_x86_64.tar.gz && \ + install -o root -g root -m 0755 otterize /usr/local/bin/otterize && \ + rm otterize_linux_x86_64.tar.gz) || echo "Otterize CLI installation skipped" + +# nginx on Alpine reads from /etc/nginx/http.d/*.conf, not /etc/nginx/sites-available/. +# Drop the default config that ships in the package and supply ours. +COPY nginx.default /etc/nginx/http.d/default.conf +RUN rm -f /etc/nginx/http.d/default.conf.dpkg-* /etc/nginx/conf.d/default.conf 2>/dev/null || true \ + && ln -sf /dev/stdout /var/log/nginx/access.log \ && ln -sf /dev/stderr /var/log/nginx/error.log # Upgrade the base image's bundled pip to a version without known CVEs @@ -129,6 +156,9 @@ ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 ENV PYTHONPATH="/code/deps:${PYTHONPATH}" ENV PATH="/code/deps/bin:${PATH}" +# musl's default thread stack is 80KB which segfaults Celery workers / +# threadpool-heavy code that assumes glibc's 8MB default. Bump explicitly. +ENV PYTHONTHREADSTACKSIZE=8388608 # VCS metadata ARG COMMIT_HASH=unknown @@ -142,7 +172,7 @@ COPY --from=builder /build/deps /code/deps # Copy project COPY . /code -RUN chown -R www-data:www-data /code +RUN chown -R nginx:nginx /code COPY scripts/start-celery-worker.sh . RUN sed -i 's/\r$//g' start-celery-worker.sh @@ -154,4 +184,4 @@ RUN chmod +x start-celery-beat.sh EXPOSE 8080 -STOPSIGNAL SIGTERM \ No newline at end of file +STOPSIGNAL SIGTERM From 82d937ced64412c0af148a7e4bdb0d861f1456ff Mon Sep 17 00:00:00 2001 From: Dipesh Mittal Date: Tue, 28 Apr 2026 12:40:50 +0530 Subject: [PATCH 2/2] chore: drop older packaged-helm versions to reduce Xray duplication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes `packaged-helm/drd-vpc-agent-0.1.0.tgz` and `packaged-helm/drd-vpc-agent-0.1.1.tgz`; keeps only the latest 0.1.2.tgz. Regenerates `packaged-helm/index.yaml` to point at the latest version. Each older tgz contains pre-templated `DRD_CLOUD_API_TOKEN` references that trip Xray's secret-name heuristic; with three versions in the release bundle the same false-positive class fires three times. Dropping the older two cuts ~20 of the 116 violations reported in `cloud-drdroidlab:1.0.5` without touching any active artifact. The pre-existing `index.yaml` already pointed to 0.1.0 only (it was generated 2025-11-12, before 0.1.1 / 0.1.2 were added to the directory). After this commit the published helm repo serves 0.1.2 instead. Customers pulling from `https://drdroidlab.github.io/drd-vpc-agent/packaged-helm/` who pinned to 0.1.0 will need to update to 0.1.2 — see the chart upgrade-notes for the operator. New installs are unaffected. Justification for all remaining 96 violations is documented locally in `security/xray-1.0.5-justifications.md`; the 7-rule Xray ignore-rule structure in section E covers everything that's left. Co-Authored-By: Claude Opus 4.7 (1M context) --- packaged-helm/drd-vpc-agent-0.1.0.tgz | Bin 4102 -> 0 bytes packaged-helm/drd-vpc-agent-0.1.1.tgz | Bin 4105 -> 0 bytes packaged-helm/index.yaml | 16 ++++++++-------- 3 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 packaged-helm/drd-vpc-agent-0.1.0.tgz delete mode 100644 packaged-helm/drd-vpc-agent-0.1.1.tgz diff --git a/packaged-helm/drd-vpc-agent-0.1.0.tgz b/packaged-helm/drd-vpc-agent-0.1.0.tgz deleted file mode 100644 index ea93a13d7d1bec828ab0f3816094946aa3964721..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4102 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 E05S_Dr~m)} diff --git a/packaged-helm/drd-vpc-agent-0.1.1.tgz b/packaged-helm/drd-vpc-agent-0.1.1.tgz deleted file mode 100644 index 7f1a4f705744174e6e0ce097a7f0639c3e892e8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4105 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