Skip to content

Add selectable Dagster image variant (base/hardened) with restructured image layout - #376

Open
RonaldHensbergen wants to merge 6 commits into
mainfrom
feat/dagster-image-variant
Open

Add selectable Dagster image variant (base/hardened) with restructured image layout#376
RonaldHensbergen wants to merge 6 commits into
mainfrom
feat/dagster-image-variant

Conversation

@RonaldHensbergen

Copy link
Copy Markdown
Owner

Summary

Introduces a selectable Dagster image variant and restructures both
Dagster and Superset image directories into per-variant subfolders,
addressing #372, #373, and #374.

images/dagster/
├── base/Dockerfile        # Debian/python:3.14-slim (default)
├── hardened/Dockerfile    # Alpine-based, minimal attack surface
├── entrypoint.sh, generate_config.py, healthcheck.py,
│   requirements*.txt, workspace.yaml, dagster.yaml.j2, README.md  (shared)

images/superset/
├── base/Dockerfile
├── entrypoint-web.sh, init.sh, requirements.txt,
│   superset_config.py, README.md  (shared)

Changes

  • modules/orchestration/dagster/module.yaml: image.variant enum
    base/hardened (default base); dockerfile: path →
    images/dagster/${config.image.variant}/Dockerfile.
  • modules/bi/superset/module.yaml: dockerfile:
    images/superset/base/Dockerfile.
  • Makefile, .github/workflows/docker-smoke-test.yml:
    build-context detection matches any images/dagster/* or
    images/superset/* Dockerfile instead of an exact-path list.
  • .github/workflows/image-security-scan.yml,
    .github/workflows/publish-images.yml
    : image discovery supports both
    the flat (images/<name>/Dockerfile) and nested
    (images/<name>/<variant>/Dockerfile) layouts.
  • publish-images.yml: base and hardened variants publish to the
    same image name/repository (both GHCR and Docker Hub), distinguished by
    tag — latest / <ver>-<date> for base, hardened-latest /
    hardened-<ver>-<date> for hardened.
  • Tests: test_dagster_hardening.py, test_python_version_alignment.py,
    test_render_example_profile.py, test_superset_hardening.py,
    test_publish_images_workflow.py updated for the new paths/discovery
    logic.
  • Docs: docs/modules.md, docs/image-signing.md,
    images/dagster/README.md updated to document the new layout and tags.

Testing

  • python -m unittest discover -s tests -p "test_*.py" — 420 tests, all
    passing.
  • ruff check ., yamllint ., npx markdownlint-cli@0.49.0 on edited
    docs — clean.

Why draft

This branch currently diverges from main by several unrelated commits
(the broader Dagster image-variant work predates recent main history).
Opening as a draft to track the work and enable review/CI feedback while
the branch is rebased/reconciled with main before it's ready to merge.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

RonaldHensbergen and others added 5 commits August 6, 2026 10:43
…ging fields

CDS-SEC-070 ("Secret appears in command args or log configuration") had
scope: ["none"], so it was never dispatched despite enabled: true. Its
valueRegex was also malformed ("(?i)(--******"), an invalid/unbalanced
regex that nothing ever compiled or exercised -- exactly the kind of
silent dead-rule regression #297 warned about, and the rule that should
have caught the Vault dev-root-token-as-command-arg issue fixed in #290.

- Add a "rendered-compose" scope to cli/security.py: run_security_validation
  now does a best-effort plan+render of the profile and flattens each
  service's command/entrypoint/logging fields, so rules can inspect where a
  module's implementation template actually places a secret-bearing value
  (only visible after ${config.*} template expressions resolve into their
  final Compose "${CDS_*}" placeholders). Plan/render failures are handled
  silently here since the separate plan/render stages already report them.
- Re-scope CDS-SEC-070 to "rendered-compose", fix its pathPatterns to match
  list-shaped command/entrypoint/logging fields, and fix the broken
  valueRegex to actually catch secret-like command-line flags.
- Add "rendered-compose" to the rule-schema.json scope enum.
- Add regression fixtures mirroring the real PR #290 bug/fix (a fixture
  module passing a secret via "command:", and a safe variant passing it via
  "environment:"), and tests proving CDS-SEC-070 now fires for the
  vulnerable shape and stays silent for the safe one.
- Update the #297 deferred-rule documentation test: CDS-SEC-070 is no
  longer scope: ["none"].

CDS-SEC-006, 030, 032, 050-054, and 071 remain scope: ["none"] as
documented by #303 -- they need rendered-artifact/CLI-output scanning or
file-permission checks that are out of scope here, and 050-052/054 are
already enforced separately by cli/image_verification.py.

Addresses #297
- Add a third CDS-SEC-070 match branch to catch bare positional
  ${CDS_*}/${config.*}/${secrets.*} placeholders, not just "--flag="
  style command arguments.
- Extend pathPatterns/coverage to include healthcheck.* alongside
  command/entrypoint/logging, since healthcheck probes leak secrets
  the same way.
- Map rendered-compose findings back to the owning CDS module id
  (via a new _map_service_to_module helper reusing the renderer's
  service-naming rule) instead of the raw Compose service name, which
  only coincidentally matched module ids in the fixture.
- Replace the silent `except Exception: return None` in
  _try_render_compose_for_scan with a warning diagnostic (W096) so
  unexpected internal errors are surfaced instead of vanishing as zero
  findings.
- Let callers pass an already-computed plan/rendered Compose YAML into
  run_security_validation(), and wire cli/main.py's `cds test` to do so,
  so the profile isn't planned and rendered twice per run.
- Expand the regression fixture to exercise all three leak surfaces
  (flag-style, bare positional, healthcheck) and assert exact
  path/value/module attribution instead of loose substring checks.
- Add coverage for the plan/render reuse behavior and for the new
  warning-diagnostic path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- CDS-SEC-070: remove the two match.any branches that could never fire
  against rendered output (keyRegex only tests the final path segment,
  which is a list index or ordinary leaf key on rendered command/
  entrypoint/healthcheck/logging paths; and ${secrets.*}/${config.*}
  resolve or fail before this rule ever runs), leaving only the single
  working "${CDS_" branch.
- cds test: pass skip_self_plan_render=True to run_security_validation
  when its own plan/render stages already failed, so
  _try_render_compose_for_scan doesn't retry (and re-fail) the same
  build_plan()/render_compose() calls a second time.
- _try_render_compose_for_scan now emits a W096 warning diagnostic when
  planning or rendering fails (not just on unexpected exceptions), so
  `cds security` (which has no separate plan/render stage) can no
  longer silently report "No security findings" for a profile that
  never actually got scanned by rendered-compose-scoped rules.
- run_security_validation now only plans+renders the profile when some
  enabled rule in the active rule set actually declares the
  "rendered-compose" scope, avoiding unconditional plan+render overhead
  on every security scan when no such rule applies.
- Add regression tests for each of the above.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduce spec.configSchema.image.variant on the Dagster module
(enum: dagster [default], alpine-dagster) so profiles can opt into a
hardened Alpine-based build instead of the default Debian/python:3.14-slim
image. The user-code, dagster-webserver, and dagster-daemon build blocks
now select their Dockerfile via
images/${config.image.variant}/Dockerfile.

Also teach cli/image_updates.py's collect_module_images/
find_images_in_compose to resolve ${config.*} expressions in
build.dockerfile against configSchema defaults, so static tooling
(base-image extraction, dependency-update checks) can still locate the
default variant's Dockerfile now that the path is templated.

Adds the (currently stub/in-progress) images/alpine-dagster/Dockerfile
as the alpine-dagster variant's build directory. Follow-up work (image
hardening, publish workflow wiring, signed-images fixture, docs) is
tracked in #372.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move images/dagster/Dockerfile and images/alpine-dagster/Dockerfile into
images/dagster/base/Dockerfile and images/dagster/hardened/Dockerfile
respectively, and images/superset/Dockerfile into
images/superset/base/Dockerfile, so shared build support files
(entrypoint, config generation, healthcheck, workspace, requirements,
README) live once per module while each Dockerfile variant gets its own
directory. Remove the now-empty images/alpine-dagster/ placeholder.

- modules/orchestration/dagster/module.yaml: image.variant enum
  dagster/alpine-dagster -> base/hardened (default base); dockerfile
  path -> images/dagster/${config.image.variant}/Dockerfile.
- modules/bi/superset/module.yaml: dockerfile -> images/superset/base/Dockerfile.
- Makefile and CI workflows (docker-smoke-test, image-security-scan,
  publish-images): build-context detection and image discovery updated
  for the nested variant layout instead of exact old paths.
- publish-images.yml: base and hardened variants publish to the same
  image name/repository, distinguished by tag (latest/<ver>-<date> for
  base, hardened-latest/hardened-<ver>-<date> for hardened) on both
  GHCR and Docker Hub.
- Updated tests (test_dagster_hardening, test_python_version_alignment,
  test_render_example_profile, test_superset_hardening,
  test_publish_images_workflow) and docs (docs/modules.md,
  docs/image-signing.md, images/dagster/README.md) for the new paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@RonaldHensbergen
RonaldHensbergen force-pushed the feat/dagster-image-variant branch from 4fe6e42 to 5671d8b Compare August 6, 2026 08:53
…text

The hardened variant's Dockerfile was a stale placeholder that never
matched the repository's build conventions: it expected a flat build
context (COPY requirements.txt ., COPY your_definitions.py .) instead
of the root-relative context used by every other image in this repo
(build.context: ../../../, COPY images/dagster/requirements*.txt ./),
and referenced a your_definitions.py file that doesn't exist anywhere
in the repository. This broke CI on PR #376 in both the
'Image Security Scan' (scan (dagster, hardened, ...)) and
'Docker Runtime Smoke Test' (build-images) jobs with
'failed to calculate checksum ... "/your_definitions.py": not found'.

Rebuild the hardened Dockerfile as an Alpine-based (python:3.14-alpine)
multi-stage build mirroring images/dagster/base/Dockerfile's structure
and shared build-support files (entrypoint.sh, generate_config.py,
healthcheck.py, workspace.yaml, dagster.yaml.j2), so both variants
build from the same repo-root context and only differ in base image.
Also fixed the dagster group/user GID/UID (999 collides with Alpine's
built-in 'ping' group; changed to 1001).

Verified: built the image locally, dagster imports and the healthcheck
script runs correctly as the dagster user, and Trivy reports zero
HIGH/CRITICAL vulnerabilities. Full unittest suite (426 tests) passes
and ruff is clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@RonaldHensbergen
RonaldHensbergen marked this pull request as ready for review August 6, 2026 09:38
@RonaldHensbergen
RonaldHensbergen marked this pull request as draft August 6, 2026 09:42
@RonaldHensbergen
RonaldHensbergen marked this pull request as ready for review August 6, 2026 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant