Skip to content

feat(validation): add workflow image vetting#739

Merged
tiborsimko merged 1 commit into
reanahub:masterfrom
CameronMcClymont:vetted-containers
Jun 7, 2026
Merged

feat(validation): add workflow image vetting#739
tiborsimko merged 1 commit into
reanahub:masterfrom
CameronMcClymont:vetted-containers

Conversation

@CameronMcClymont

@CameronMcClymont CameronMcClymont commented Jul 16, 2025

Copy link
Copy Markdown
Member

Extend validate_workflow to check that all images used in workflows are authorized. Such authorized images are specified via the vetted_container_images Helm value. Also add details of container vetting to the info API endpoint.

Closes #728

@CameronMcClymont CameronMcClymont self-assigned this Jul 16, 2025
CameronMcClymont added a commit to CameronMcClymont/reana-server that referenced this pull request Jul 16, 2025
Comment thread reana_server/validation.py Outdated
Comment on lines +132 to +135
steps = reana_yaml["workflow"].get("specification", {}).get("steps", [])

for step in steps:
step_image = step.get("environment", None)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was a bit unsure about this - is this the correct place to get the user workflow images from?

CameronMcClymont added a commit to CameronMcClymont/reana-server that referenced this pull request Jul 16, 2025
CameronMcClymont added a commit to CameronMcClymont/reana-server that referenced this pull request Jul 16, 2025
CameronMcClymont added a commit to CameronMcClymont/reana-server that referenced this pull request Jul 16, 2025
@codecov

codecov Bot commented Jul 16, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.47368% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.19%. Comparing base (db21167) to head (32d7491).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
reana_server/rest/workflows.py 50.00% 1 Missing ⚠️
reana_server/validation.py 90.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #739      +/-   ##
==========================================
+ Coverage   60.06%   60.19%   +0.12%     
==========================================
  Files          33       33              
  Lines        4074     4092      +18     
==========================================
+ Hits         2447     2463      +16     
- Misses       1627     1629       +2     
Files with missing lines Coverage Δ
reana_server/config.py 87.26% <100.00%> (+0.09%) ⬆️
reana_server/rest/info.py 96.34% <100.00%> (+0.23%) ⬆️
reana_server/rest/workflows.py 49.36% <50.00%> (+<0.01%) ⬆️
reana_server/validation.py 68.75% <90.00%> (+2.08%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

CameronMcClymont added a commit to CameronMcClymont/reana-server that referenced this pull request Jul 17, 2025
Extend `validate_workflow` to check that all
images used in workflows are authorized.
Such authorized images are specified via the
`vetted_container_images` Helm value. Also
add details of container vetting to the
`info` API endpoint.
CameronMcClymont added a commit to CameronMcClymont/reana-server that referenced this pull request May 20, 2026
Extend `validate_workflow` to check that all
images used in workflows are authorized.
Such authorized images are specified via the
`vetted_container_images` Helm value. Also
add details of container vetting to the
`info` API endpoint.
CameronMcClymont added a commit to CameronMcClymont/reana-server that referenced this pull request May 20, 2026
Extend `validate_workflow` to check that all
images used in workflows are authorized.
Such authorized images are specified via the
`vetted_container_images` Helm value. Also
add details of container vetting to the
`info` API endpoint.
)


def validate_images(reana_yaml: Dict) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this only scans workflow.specification.steps. Yadage uses nested stages and CWL uses requirements[].dockerPull. The client already contains extraction logic for Yadage and CWL. Maybe let's move the shared extraction into reana-commons or implement equivalent server extractors?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes good idea, moved image extraction logic to reana-commons

Comment thread reana_server/validation.py Outdated
Comment on lines +134 to +137
for step in steps:
image = step.get("environment", None)
if image and image not in allowed_images:
raise REANAValidationError(f"Image not allowed: {image}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server ignores falsey environments here. Snakemake jobs without an explicit image use REANA_DEFAULT_SNAKEMAKE_ENV_IMAGE client fallback. Maybe let's also vet the resolved default image?

@CameronMcClymont CameronMcClymont Jun 3, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to vet the default image because both the list of vetted images and the default snakemake env image are set by cluster admins (i.e. REANA_DEFAULT_SNAKEMAKE_ENV_IMAGE is trustworthy so doesn't need to be vetted).

Comment thread reana_server/config.py
Comment on lines +567 to +570
REANA_VETTED_CONTAINER_IMAGES = json.loads(
os.getenv("REANA_VETTED_CONTAINER_IMAGES", "{}")
)
"""Container images that users are allowed to use in their workflows."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The environment variable defaults to {} in config.py, but validation and info index both keys. This probably breaks standalone deployments and version skewed rollouts. Maybe let's default to {"enabled": false, "allowlist": []}?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh ye nice catch, updated the default now.

Comment thread reana_server/rest/info.py Outdated
},
"vetted_container_images_enabled": {
"title": "Whether container image vetting is enabled",
"value": "False"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema declares a boolean, but the example uses "False" as a string in reana-server and the generated reana-commons snapshot. Use JSON false and regenerate the specification.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fixed!

@michaelbuchar

Copy link
Copy Markdown

Maybe we can add more engine specific tests as well?

CameronMcClymont added a commit to CameronMcClymont/reana-server that referenced this pull request Jun 3, 2026
Extend `validate_workflow` to check that all
images used in workflows are authorized.
Such authorized images are specified via the
`vetted_container_images` Helm value. Also
add details of container vetting to the
`info` API endpoint.
CameronMcClymont added a commit to CameronMcClymont/reana-server that referenced this pull request Jun 3, 2026
Extend `validate_workflow` to check that all
images used in workflows are authorized.
Such authorized images are specified via the
`vetted_container_images` Helm value. Also
add details of container vetting to the
`info` API endpoint.
@CameronMcClymont

CameronMcClymont commented Jun 3, 2026

Copy link
Copy Markdown
Member Author

Some tests are failing due to dependency on new reana-commons version: reanahub/reana-commons#497

All tests pass locally:

image

CameronMcClymont added a commit to CameronMcClymont/reana-server that referenced this pull request Jun 4, 2026
Extend `validate_workflow` to check that all
images used in workflows are authorized.
Such authorized images are specified via the
`vetted_container_images` Helm value. Also
add details of container vetting to the
`info` API endpoint.
CameronMcClymont added a commit to CameronMcClymont/reana-server that referenced this pull request Jun 4, 2026
Extend `validate_workflow` to check that all
images used in workflows are authorized.
Such authorized images are specified via the
`vetted_container_images` Helm value. Also
add details of container vetting to the
`info` API endpoint.
Comment thread reana_server/rest/info.py
]
],
),
vetted_container_images_enabled=dict(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 reana-client-go info drops vetted image settings (MEDIUM)

The Python client seems fine here, but reana-client-go info will not show these new settings yet. Its generated InfoOKBody model does not include vetted_container_images_enabled or vetted_container_images_allowlist, and the human-readable output in cmd/info.go is hardcoded field by field. As a result, both plain output and likely --json output discard these fields even though the server OpenAPI now exposes them.

Could we please update reana-client-go as part of this change as well? The fix should be to regenerate the Go client from the updated reana-server/docs/openapi.json, add the two fields to cmd/info.go, and update the info command fixtures/tests.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a new reana-client-go PR: reanahub/reana-client-go#182

CameronMcClymont added a commit to CameronMcClymont/reana-server that referenced this pull request Jun 5, 2026
CameronMcClymont added a commit to CameronMcClymont/reana-server that referenced this pull request Jun 5, 2026
Extend `validate_workflow` to check that all
images used in workflows are authorized.
Such authorized images are specified via the
`vetted_container_images` Helm value. Also
add details of container vetting to the
`info` API endpoint.
CameronMcClymont added a commit to CameronMcClymont/reana-server that referenced this pull request Jun 5, 2026
Extend `validate_workflow` to check that all
images used in workflows are authorized.
Such authorized images are specified via the
`vetted_container_images` Helm value. Also
add details of container vetting to the
`info` API endpoint.

Co-authored-by: Tibor Šimko <tibor.simko@cern.ch>

@tiborsimko tiborsimko left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 GitLab webhook path publishes workflows without image vetting (HIGH)

reana_server/rest/workflows.py:540-613 short-circuits to publish_workflow_submission when git_data is present, bypassing the start endpoint where validate_workflow()validate_images() normally runs. With vetting enabled, an unvetted image referenced in a GitLab-tracked reana.yaml will still be queued and executed.

Adding validate_images() only at create-time isn't enough on its own: GitLab reana.yaml files commonly use workflow.file:, so the initial yaml.safe_load() carries no workflow.specification and the early check no-ops. The specification is materialised later by _load_and_save_yadage_spec / load_reana_spec at lines 600-610. The vetting check therefore has to run after that load.

Suggested fix — add the import and two validate_images() calls:

from reana_server.validation import (
    validate_images,
    validate_inputs,
    validate_workflow,
    validate_workspace_path,
    validate_dask_limits,
)
        validate_inputs(reana_spec_file)

        validate_images(reana_spec_file)   # catches inline-spec workflows

        validate_dask_limits(reana_spec_file)
            elif workflow.type_ in ["cwl", "snakemake"]:
                reana_yaml_path = os.path.join(workflow.workspace_path, "reana.yaml")
                workflow.reana_specification = load_reana_spec(
                    reana_yaml_path, workflow.workspace_path
                )
                Session.commit()

            validate_images(workflow.reana_specification)   # closes the GitLab bypass

            parameters = request.json
            publish_workflow_submission(workflow, user.id_, parameters)

Worth adding a tests/test_views.py case that posts a git_data create with a disallowed image and asserts the webhook returns 400 before publish_workflow_submission is reached.

@tiborsimko

Copy link
Copy Markdown
Member

🤖 GitLab webhook path publishes workflows without image vetting (HIGH)

[...]

Suggested fix — add the import and two validate_images() calls:

I've added a quick fix to the PR and squashed.

@tiborsimko tiborsimko force-pushed the vetted-containers branch from 1ad65b8 to 32d7491 Compare June 7, 2026 14:22
@tiborsimko tiborsimko merged commit 32d7491 into reanahub:master Jun 7, 2026
19 checks passed
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.

validate: check workflow specification for unauthorised container images

3 participants