Skip to content

Wv 228 add nf plugin security scan#912

Merged
pditommaso merged 45 commits intomasterfrom
WV-228-Add-nf-plugin-security-scan
Oct 16, 2025
Merged

Wv 228 add nf plugin security scan#912
pditommaso merged 45 commits intomasterfrom
WV-228-Add-nf-plugin-security-scan

Conversation

@munishchouhan
Copy link
Copy Markdown
Member

@munishchouhan munishchouhan commented Sep 24, 2025

Security Scanning for Nextflow Plugins in OCI Registries

Summary

This PR adds support for security scanning of Nextflow plugins hosted in OCI registries. The unified scanner can now detect and scan both traditional container images and Nextflow plugin artifacts, providing comprehensive vulnerability analysis across the entire Wave ecosystem.

Feature Overview

What it does

  • Unified Scanning Infrastructure: Single scanner implementation handles both container images and Nextflow plugins
  • OCI Registry Support: Pulls plugin artifacts from any OCI-compliant registry using ORAS
  • Vulnerability Detection: Scans plugin dependencies and JAR libraries for known CVEs using Trivy
  • Multiple Output Formats: Generates reports in JSON, SPDX, and CycloneDX formats
  • Multi-Architecture Support: Scanner image works on both AMD64 and ARM64 platforms

How it works

Detection Flow:

  1. Scan request comes in with target image name
  2. System detects scan type based on image name pattern (nextflow/plugin → plugin scan)
  3. Scanner executes appropriate workflow:

For Plugins:

  • Downloads artifact using ORAS from OCI registry
  • Extracts plugin ZIP to temporary filesystem
  • Performs rootfs scan with Trivy (trivy rootfs --scanners vuln)
  • Generates vulnerability reports

For Containers:

  • Performs standard container image scan with Trivy (trivy image)
  • Analyzes all layers for vulnerabilities
  • Generates vulnerability reports

Key Technical Choices

1. Unified Scanner Script

Implementation: Refactored scanner/scan.sh into a unified script with --type parameter

  • --type container → Container image scan
  • --type plugin → Nextflow plugin scan

Rationale:

  • Single script reduces maintenance overhead
  • Ensures consistent scanning behavior across types
  • Simplifies testing and deployment
  • Easier to add new scan types in the future

2. ORAS for Plugin Downloads

Implementation: Integrated ORAS (OCI Registry As Storage) tool into scanner image

Rationale:

  • Industry-standard tool for OCI artifact management
  • Native support for non-container artifacts (plugins, charts, etc.)
  • Simple CLI interface integrates easily with bash scripts
  • Maintained by CNCF, ensuring long-term support

3. Filesystem Scanning for Plugins

Implementation: Plugins scanned using Trivy's rootfs scanner mode with --scanners vuln flag

Rationale:

  • Plugins are JAR/ZIP artifacts, not container images
  • Filesystem scanning detects vulnerabilities in Java dependencies
  • Uses same Trivy vulnerability database as container scans
  • Provides consistent security posture across all artifacts

4. Explicit Cache Directory Configuration

Implementation: Added --cache-dir option to configure Trivy's cache location explicitly

Rationale:

  • Improves reproducibility across environments
  • Allows mounting external cache volumes in production for persistence
  • Uses constant Trivy.CACHE_MOUNT_PATH for consistency across codebase
  • Better control over disk usage and cache lifecycle

5. Multi-Architecture Scanner Image

Implementation: Fixed Dockerfile to properly support both amd64 and arm64 architectures using Docker's TARGETARCH build arg

Rationale:

  • Critical for ARM-based infrastructure (AWS Graviton, Apple Silicon)
  • Single image tag works across all architectures
  • Reduces image management complexity
  • Improves deployment flexibility

6. Pattern-Based Scan Type Detection

Implementation: Detects plugin scans by checking if image name contains nextflow/plugin

Rationale:

  • Simple and effective for current use case
  • No additional registry roundtrips required
  • Future: Will migrate to manifest media type detection (tracked in Improve plugin scan detection using mediatype #919)
  • Provides clear migration path without breaking existing functionality

Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Wave Application                          │
│                                                              │
│  ┌────────────────────────────────────────────────────┐    │
│  │ ContainerScanServiceImpl                            │    │
│  │  • fromBuild()                                      │    │
│  │  • fromMirror()                                     │    │
│  │  • fromContainer()                                  │    │
│  └──────────────────┬──────────────────────────────────┘    │
│                     │                                        │
│                     ▼                                        │
│  ┌────────────────────────────────────────────────────┐    │
│  │ ScanStrategy                                        │    │
│  │  • buildScanCommand()                               │    │
│  │  • Detects: container vs plugin                     │    │
│  └──────────────────┬──────────────────────────────────┘    │
│                     │                                        │
│                     ▼                                        │
│  ┌────────────────────────────────────────────────────┐    │
│  │ KubeScanStrategy / DockerScanStrategy               │    │
│  │  • Launches scanner container                       │    │
│  └──────────────────┬──────────────────────────────────┘    │
└───────────────────────┼────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────┐
│              Scanner Container Image                         │
│              (public.cr.seqera.io/wave/scanner:v1)          │
│                                                              │
│  ┌────────────────────────────────────────────────────┐    │
│  │ /usr/local/bin/scan.sh                              │    │
│  │                                                      │    │
│  │  if [type == container]:                            │    │
│  │    trivy image --platform $PLATFORM $TARGET       │    │
│  │                                                      │    │
│  │  if [type == plugin]:                               │    │
│  │    oras pull $TARGET                                │    │
│  │    unzip plugin.zip                                 │    │
│  │    trivy rootfs --scanners vuln /extracted          │    │
│  └────────────────────────────────────────────────────┘    │
│                                                              │
│  Components:                                                 │
│  • Trivy 0.65.0 (vulnerability scanner)                     │
│  • ORAS 1.3.0 (OCI artifact client)                         │
│  • bash, unzip (utilities)                                  │
└─────────────────────────────────────────────────────────────┘

Files Changed

Scanner Implementation

  • scanner/scan.sh - Unified bash script with plugin support and --cache-dir option
  • scanner/Dockerfile - Multi-arch build with ORAS integration and TARGETARCH support
  • scanner/Makefile - Build configuration for multi-platform images

Application Code

  • ScanStrategy.groovy - Added buildScanCommand() with scan type detection and --cache-dir parameter
  • KubeScanStrategy.groovy - Simplified to use unified command builder
  • DockerScanStrategy.groovy - Simplified to use unified command builder
  • Trivy.groovy - Added CACHE_MOUNT_PATH constant

Tests

  • ScanStrategyTest.groovy - Updated command expectations with --cache-dir
  • BuildScanCommandTest.groovy - Updated command expectations with --cache-dir

Testing

All tests passing:

./gradlew test --tests 'io.seqera.wave.service.scan.*Test'

Test coverage includes:

  • Container scans with/without platform and severity
  • Plugin scans with/without severity
  • Scan type auto-detection logic
  • Timeout conversions and parameter handling
  • Cache directory configuration

Deployment

Building the Scanner Image

cd scanner
make build  # Builds for linux/amd64 and linux/arm64

This publishes:

  • public.cr.seqera.io/wave/scanner:v1-0.65.0-oras-1.3.0
  • public.cr.seqera.io/wave/scanner:v1 (latest)

Configuration

The application automatically uses the scanner via existing configuration:

wave:
  scan:
    image:
      name: public.cr.seqera.io/wave/scanner:v1

Example Usage

Container Scan:

/usr/local/bin/scan.sh \
  --type container \
  --target ubuntu:latest \
  --work-dir /tmp/scan \
  --platform linux/amd64 \
  --timeout 15 \
  --cache-dir /root/.cache/

Plugin Scan:

/usr/local/bin/scan.sh \
  --type plugin \
  --target ghcr.io/nextflow-io/nextflow/plugins/nf-amazon:1.0.0 \
  --work-dir /tmp/scan \
  --timeout 15 \
  --cache-dir /root/.cache/

Benefits

  1. Unified Infrastructure - Single image handles all security scanning
  2. Simplified Maintenance - One scanner to build, test, and deploy
  3. Consistent Security - Same Trivy version and vulnerability database
  4. Better Performance - Explicit cache configuration and reuse
  5. Multi-Architecture - Works on AMD64 and ARM64 platforms
  6. Future-Ready - Easy to extend for new artifact types

🤖 Generated with Claude Code

Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
@munishchouhan munishchouhan self-assigned this Sep 24, 2025
@munishchouhan munishchouhan marked this pull request as draft September 24, 2025 11:50
@pditommaso
Copy link
Copy Markdown
Collaborator

A bit more context how this PR is expected to work? (claude is great for this)

@pditommaso
Copy link
Copy Markdown
Collaborator

How this differ from a regular container scan?

@munishchouhan
Copy link
Copy Markdown
Member Author

How this differ from a regular container scan?

When creating scan request for plugin i am setting containerplatform as null and accordingly command for scanning plugin is used

@munishchouhan
Copy link
Copy Markdown
Member Author

A bit more context how this PR is expected to work? (claude is great for this)

Added summary

@pditommaso
Copy link
Copy Markdown
Collaborator

One thing i'd like to avoid is to have a different endpoint for plugins, because it not scale with other content we may want handle in the future (modules, maven jar, etc).

Ideally is could be used the same endpoint and dispatched internally depending the content type detected. However for sake of proving fast the impact of this approach, i'd say just to check the nextflow/plugin path in the container name and dispatch the scan accordingly. For example

public.cr.seqera.io/nextflow/plugin/nf-aspera:0.1.0-edge1

Make sense?

@munishchouhan
Copy link
Copy Markdown
Member Author

One thing i'd like to avoid is to have a different endpoint for plugins, because it not scale with other content we may want handle in the future (modules, maven jar, etc).

Ideally is could be used the same endpoint and dispatched internally depending the content type detected. However for sake of proving fast the impact of this approach, i'd say just to check the nextflow/plugin path in the container name and dispatch the scan accordingly. For example


public.cr.seqera.io/nextflow/plugin/nf-aspera:0.1.0-edge1

Make sense?

Ok understood
I will make the changes

munishchouhan and others added 6 commits September 25, 2025 11:12
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
@munishchouhan munishchouhan marked this pull request as ready for review September 25, 2025 12:54
Signed-off-by: munishchouhan <hrma017@gmail.com>
pditommaso and others added 4 commits September 25, 2025 17:51
@munishchouhan munishchouhan requested review from fntlnz and jordeu October 8, 2025 08:41
Copy link
Copy Markdown
Collaborator

@pditommaso pditommaso left a comment

Choose a reason for hiding this comment

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

Made a few comments to address and we are ready to go

munishchouhan and others added 5 commits October 15, 2025 17:27
Co-authored-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
@munishchouhan
Copy link
Copy Markdown
Member Author

Tested:
Screenshot 2025-10-16 at 12 18 19
Screenshot 2025-10-16 at 12 16 43

Screenshot 2025-10-16 at 12 37 22

Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
@pditommaso
Copy link
Copy Markdown
Collaborator

I've improved a bit the docker build 464083d

Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
@pditommaso
Copy link
Copy Markdown
Collaborator

Likely this is ready. However what's is not clear to me and it's a key point to be described (and tested) in which path in the plugins are unzipped? Above all is a local container storage path or EFS mounted path? Has it been tested for large plugin (> 50MB) ?

@pditommaso
Copy link
Copy Markdown
Collaborator

I would say the usual EFS mounted work dir. Correct?

<< '--work-dir'
<< workDir.toString()

Copy link
Copy Markdown
Collaborator

@pditommaso pditommaso left a comment

Choose a reason for hiding this comment

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

Minor it could make sense to add an option --scan-cache-dir option align this setting

CACHE_DIR="${TRIVY_CACHE_DIR:-/root/.cache/}"

with this option

.mountPath( Trivy.CACHE_MOUNT_PATH )

Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
@pditommaso
Copy link
Copy Markdown
Collaborator

Ok, done 👉 c1fc431

Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
Copy link
Copy Markdown
Collaborator

@pditommaso pditommaso left a comment

Choose a reason for hiding this comment

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

Looks all good.

Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
@pditommaso pditommaso merged commit 5acca43 into master Oct 16, 2025
@pditommaso pditommaso deleted the WV-228-Add-nf-plugin-security-scan branch October 16, 2025 17:53
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.

3 participants