This is an automated email from the ASF dual-hosted git repository.

nytai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 0ccb586540a fix(docker): use the system CA store for build-time 
downloads (#45678)
0ccb586540a is described below

commit 0ccb586540a50b780936789510f6503d37eeafc9
Author: ʈᵃᵢ <[email protected]>
AuthorDate: Sun Oct 25 17:51:23 2026 -0700

    fix(docker): use the system CA store for build-time downloads (#45678)
---
 .github/renovate.json                             | 40 ++++++++++++++++++
 .github/workflows/docker.yml                      | 16 ++++++--
 Dockerfile                                        | 49 +++++++++++++++++++----
 scripts/docker-build-extra-flags.sh               |  4 +-
 tests/unit_tests/scripts/docker_cache_ref_test.py |  3 +-
 5 files changed, 99 insertions(+), 13 deletions(-)

diff --git a/.github/renovate.json b/.github/renovate.json
new file mode 100644
index 00000000000..494b16e058c
--- /dev/null
+++ b/.github/renovate.json
@@ -0,0 +1,40 @@
+{
+  "$schema": "https://docs.renovatebot.com/renovate-schema.json";,
+  "description": "Renovate only refreshes the pinned base-image digests in the 
root Dockerfile; Dependabot (.github/dependabot.yml) owns npm, pip and GitHub 
Actions updates.",
+  "extends": ["config:recommended", "docker:pinDigests"],
+  "enabledManagers": ["dockerfile", "custom.regex"],
+  "includePaths": ["Dockerfile"],
+  "labels": ["dependencies", "docker"],
+  "schedule": ["* 0-5 * * 1"],
+  "packageRules": [
+    {
+      "description": "The python image is referenced as `FROM 
python:${PY_VER}`, so its tag and digest live in the PY_VER ARG; leave it to 
the custom manager below so the dockerfile manager can't propose a competing 
update.",
+      "matchManagers": ["dockerfile"],
+      "matchDepNames": ["python"],
+      "enabled": false
+    },
+    {
+      "description": "Base image tags (Node major, PY_VER's Python line, 
Debian release) are deliberate choices, not routine bumps; see 
python-nightly-canary.yml for how PY_VER moves. Only refresh the digest behind 
the current tag.",
+      "matchManagers": ["dockerfile", "custom.regex"],
+      "matchUpdateTypes": ["major", "minor", "patch"],
+      "enabled": false
+    },
+    {
+      "description": "Refresh all base-image digests in one weekly PR so the 
Node and Python stages are rebuilt together.",
+      "matchManagers": ["dockerfile", "custom.regex"],
+      "matchUpdateTypes": ["digest", "pinDigest"],
+      "groupName": "Docker base image digests"
+    }
+  ],
+  "customManagers": [
+    {
+      "customType": "regex",
+      "description": "Track the digest pinned in the Dockerfile's PY_VER ARG 
(tag@sha256:...), which FROM python:${PY_VER} consumes.",
+      "managerFilePatterns": ["/^Dockerfile$/"],
+      "matchStrings": ["ARG 
PY_VER=(?<currentValue>[^@\\s]+)@(?<currentDigest>sha256:[a-f0-9]{64})"],
+      "depNameTemplate": "python",
+      "datasourceTemplate": "docker",
+      "versioningTemplate": "docker"
+    }
+  ]
+}
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 052dfbe1e6b..2ee6cc90351 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -142,7 +142,9 @@ jobs:
             local preset="$1" expected="$2" extra_flags command
             extra_flags="$(scripts/docker-build-extra-flags.sh "$preset" 
dummy-tag)"
             command="$(docker_command "$preset" "$extra_flags")"
-            if ! grep -Fq -- 
"--cache-from=type=registry,ref=apache/superset-cache:$expected" <<<"$command"; 
then
+            # Match the whole flag, so a ref with extra trailing text (e.g. a
+            # digest) doesn't pass on its prefix.
+            if ! grep -oE -- '--cache-from=[^[:space:]]+' <<<"$command" | grep 
-Fxq -- "--cache-from=type=registry,ref=apache/superset-cache:$expected"; then
               echo "::error::preset '$preset' expected cache-from 
apache/superset-cache:$expected (full command: $command)"
               exit 1
             fi
@@ -196,7 +198,7 @@ jobs:
             load_build_plan push refs/heads/master master
             extra_flags="$(PUBLISH_DOCKER_CACHE=true DOCKERHUB_TOKEN=dummy 
scripts/docker-build-extra-flags.sh "$preset" dummy-tag)"
             command="$(docker_command "$preset" "$extra_flags" --context 
"$BUILD_CONTEXT" --context-ref "$BUILD_CONTEXT_REF")"
-            if ! grep -Fq -- 
"--cache-to=type=registry,mode=max,ref=apache/superset-cache:$expected" 
<<<"$command"; then
+            if ! grep -oE -- '--cache-to=[^[:space:]]+' <<<"$command" | grep 
-Fxq -- 
"--cache-to=type=registry,mode=max,ref=apache/superset-cache:$expected"; then
               echo "::error::preset '$preset' expected cache-to 
apache/superset-cache:$expected for master publish (full command: $command)"
               exit 1
             fi
@@ -255,8 +257,16 @@ jobs:
           assert_publish_guard push refs/heads/7.0 aaaa111 bbbb222 true
           assert_publish_guard pull_request refs/pull/44250/merge aaaa111 
bbbb222 true
           assert_supersetbot_exports_cache_with_token superset
+          # The default presets must pass the Dockerfile's digest-pinned PY_VER
+          # (tag@sha256:...) through unchanged; read it the way
+          # docker-build-extra-flags.sh does so digest bumps need no edit here.
+          default_py_ver="$(sed -n 's/^ARG PY_VER=//p' Dockerfile | head -n 1)"
+          if [[ "$default_py_ver" != *@sha256:* ]]; then
+            echo "::error::Dockerfile ARG PY_VER must pin a digest 
(tag@sha256:...), got: ${default_py_ver:-<none>}"
+            exit 1
+          fi
           for preset in superset dev lean; do
-            assert_effective_py_ver "$preset" "3.11-slim-trixie"
+            assert_effective_py_ver "$preset" "$default_py_ver"
             expected_cache="3.11-slim-trixie"
             if [ "$preset" != "superset" ]; then
               expected_cache="${expected_cache}-${preset}"
diff --git a/Dockerfile b/Dockerfile
index 6d62c0d01fe..a3b02332eb0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -18,7 +18,12 @@
 ######################################################################
 # Node stage to deal with static asset construction
 ######################################################################
-ARG PY_VER=3.11-slim-trixie
+# Base images are pinned by digest so builds are reproducible and cached
+# stages don't silently outlive base image updates. Renovate
+# (.github/renovate.json) refreshes the digests weekly; when changing a tag
+# by hand, bump its digest too. PY_VER carries its digest, so overriding it
+# (e.g. the py311/py312 presets) replaces both.
+ARG 
PY_VER=3.11-slim-trixie@sha256:b7a9e5f6a93d6d4a31f129bbd4bdee69efccb6ded0fb230d78aafaf37af98377
 
 # If BUILDPLATFORM is null, set it to 'amd64' (or leave as is otherwise).
 ARG BUILDPLATFORM=${BUILDPLATFORM:-amd64}
@@ -26,6 +31,14 @@ ARG BUILDPLATFORM=${BUILDPLATFORM:-amd64}
 # Include translations in the final build
 ARG BUILD_TRANSLATIONS="false"
 
+######################################################################
+# system-ca exposes the system CA bundle of the pinned python image. The
+# node base images ship no CA store, so the Node stages copy this one in
+# and verify TLS against the same store as the Python stages, rather than
+# Node's built-in CA list.
+######################################################################
+FROM python:${PY_VER} AS system-ca
+
 ######################################################################
 # superset-node-manifests holds only the npm manifests of the frontend
 # tree (the root package.json + lockfile, .npmrc, and every workspace 
package.json),
@@ -34,7 +47,7 @@ ARG BUILD_TRANSLATIONS="false"
 # Docker cannot COPY a glob while keeping the directory structure, hence
 # the copy-then-prune stage.
 ######################################################################
-FROM --platform=${BUILDPLATFORM} node:24-trixie-slim AS superset-node-manifests
+FROM --platform=${BUILDPLATFORM} 
node:24-trixie-slim@sha256:43b9ba1142b8dcf33fa8f922ca1be85714adc444e0586c7cf7726daaeabc1bea
 AS superset-node-manifests
 WORKDIR /app/superset-frontend
 COPY superset-frontend /app/superset-frontend
 RUN find . -type f ! -name package.json ! -name package-lock.json ! -name 
.npmrc -delete \
@@ -43,7 +56,7 @@ RUN find . -type f ! -name package.json ! -name 
package-lock.json ! -name .npmrc
 ######################################################################
 # superset-node-ci used as a base for building frontend assets and CI
 ######################################################################
-FROM --platform=${BUILDPLATFORM} node:24-trixie-slim AS superset-node-ci
+FROM --platform=${BUILDPLATFORM} 
node:24-trixie-slim@sha256:43b9ba1142b8dcf33fa8f922ca1be85714adc444e0586c7cf7726daaeabc1bea
 AS superset-node-ci
 ARG BUILD_TRANSLATIONS
 ENV BUILD_TRANSLATIONS=${BUILD_TRANSLATIONS}
 ARG DEV_MODE="false"           # Skip frontend build in dev mode
@@ -76,6 +89,11 @@ ENV npm_config_fetch_retries=5 \
     npm_config_fetch_retry_maxtimeout=120000 \
     npm_config_fetch_timeout=600000
 
+# Verify TLS against the system CA store (see system-ca)
+COPY --from=system-ca /etc/ssl/certs/ca-certificates.crt 
/etc/ssl/certs/ca-certificates.crt
+ENV NODE_OPTIONS=--use-openssl-ca \
+    SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
+
 # Install dependencies from the manifests alone (root + workspaces) if not in
 # dev mode. With the workspace package.json files present, npm ci links the
 # workspaces and installs their dependencies too, so the CI jobs that run
@@ -120,7 +138,7 @@ RUN if [ "${BUILD_TRANSLATIONS}" = "true" ]; then \
 # superset-websocket builds the realtime WebSocket (Node) server that
 # ships in the official image, launched via docker/entrypoints/run-websocket.sh
 ######################################################################
-FROM node:24-trixie-slim AS superset-websocket
+FROM 
node:24-trixie-slim@sha256:43b9ba1142b8dcf33fa8f922ca1be85714adc444e0586c7cf7726daaeabc1bea
 AS superset-websocket
 
 # Harden `npm ci` against transient npm-registry network blips (e.g. 
ECONNRESET).
 ENV npm_config_fetch_retries=5 \
@@ -128,6 +146,11 @@ ENV npm_config_fetch_retries=5 \
     npm_config_fetch_retry_maxtimeout=120000 \
     npm_config_fetch_timeout=600000
 
+# Verify TLS against the system CA store (see system-ca)
+COPY --from=system-ca /etc/ssl/certs/ca-certificates.crt 
/etc/ssl/certs/ca-certificates.crt
+ENV NODE_OPTIONS=--use-openssl-ca \
+    SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
+
 WORKDIR /app/superset-websocket
 
 # Install against the lockfile first (cached until it changes), then bundle the
@@ -156,7 +179,12 @@ RUN useradd --user-group -d ${SUPERSET_HOME} -m 
--no-log-init --shell /bin/bash
 # Some bash scripts needed throughout the layers
 COPY --chmod=755 docker/*.sh /app/docker/
 
-RUN pip install --no-cache-dir --upgrade uv
+# Use the system CA store for every download in the Python stages, rather than
+# the CA lists pip and uv bundle. UV_SYSTEM_CERTS needs uv>=0.11.0.
+ENV PIP_CERT=/etc/ssl/certs/ca-certificates.crt \
+    UV_SYSTEM_CERTS=true
+
+RUN pip install --no-cache-dir --upgrade "uv>=0.11.0"
 
 # Using uv as it's faster/simpler than pip
 RUN uv venv /app/.venv
@@ -207,7 +235,9 @@ RUN mkdir -p \
       requirements \
     && touch superset/static/version_info.json
 
-# Optionally install Playwright and the headless Chromium used for screenshots
+# Optionally install Playwright and the headless Chromium used for screenshots.
+# `playwright install` runs on Playwright's bundled Node; --use-openssl-ca 
makes
+# it use the system CA store, consistent with the rest of the build.
 ENV PLAYWRIGHT_BROWSERS_PATH=/usr/local/share/playwright-browsers
 
 ARG INCLUDE_CHROMIUM="false"
@@ -215,7 +245,8 @@ RUN --mount=type=cache,target=${SUPERSET_HOME}/.cache/uv \
     if [ "${INCLUDE_CHROMIUM}" = "true" ]; then \
         uv pip install playwright && \
         playwright install-deps && \
-        playwright install chromium; \
+        NODE_OPTIONS=--use-openssl-ca 
SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \
+            playwright install chromium; \
     else \
         echo "Skipping browser installation"; \
     fi
@@ -328,10 +359,12 @@ RUN --mount=type=cache,target=${SUPERSET_HOME}/.cache/uv \
 # INCLUDE_CHROMIUM build arg on python-common) because this stage builds
 # FROM lean, which is already past that arg. Chromium ships for both
 # linux/amd64 and linux/arm64, so this works on multi-platform builds.
+# See the INCLUDE_CHROMIUM step in python-common for --use-openssl-ca.
 RUN --mount=type=cache,target=${SUPERSET_HOME}/.cache/uv \
     uv pip install playwright \
     && playwright install-deps \
-    && playwright install chromium
+    && NODE_OPTIONS=--use-openssl-ca 
SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \
+        playwright install chromium
 
 USER superset
 
diff --git a/scripts/docker-build-extra-flags.sh 
b/scripts/docker-build-extra-flags.sh
index 6b5bd68f46f..cccfc9f7ceb 100755
--- a/scripts/docker-build-extra-flags.sh
+++ b/scripts/docker-build-extra-flags.sh
@@ -42,6 +42,8 @@ BUILD_PRESET="${1:?usage: docker-build-extra-flags.sh 
<build_preset> [image_tag]
 IMAGE_TAG="${2:-}"
 BUILD_MODE="${3:-ci}"
 DEFAULT_PY_VER="$(sed -n 's/^ARG PY_VER=//p' Dockerfile | head -n 1)"
+# PY_VER pins a digest (tag@sha256:...); registry cache refs take the tag only
+DEFAULT_PY_TAG="${DEFAULT_PY_VER%%@*}"
 if [ -z "$DEFAULT_PY_VER" ]; then
   echo "Could not determine the default PY_VER from Dockerfile" >&2
   exit 1
@@ -65,7 +67,7 @@ case "$BUILD_PRESET" in
     CACHE_REF="apache/superset-cache:3.12-slim-bookworm"
     ;;
   *)
-    CACHE_REF="apache/superset-cache:${DEFAULT_PY_VER}"
+    CACHE_REF="apache/superset-cache:${DEFAULT_PY_TAG}"
     # Keep the superset cache ref for Compose consumers; isolate other
     # matrix targets so concurrent exports cannot overwrite its layers.
     if [ "$BUILD_PRESET" != "superset" ]; then
diff --git a/tests/unit_tests/scripts/docker_cache_ref_test.py 
b/tests/unit_tests/scripts/docker_cache_ref_test.py
index 573321d1f52..742ae7508de 100644
--- a/tests/unit_tests/scripts/docker_cache_ref_test.py
+++ b/tests/unit_tests/scripts/docker_cache_ref_test.py
@@ -59,7 +59,8 @@ def _dockerfile_py_ver() -> str:
     """
     for line in (REPO_ROOT / "Dockerfile").read_text().splitlines():
         if line.startswith("ARG PY_VER="):
-            return line.removeprefix("ARG PY_VER=").strip()
+            # PY_VER pins a digest (tag@sha256:...); cache refs use the tag
+            return line.removeprefix("ARG PY_VER=").strip().split("@", 1)[0]
     raise AssertionError("Dockerfile no longer declares a default ARG PY_VER")
 
 

Reply via email to