Skip to content

Commit 5acca43

Browse files
Add security scanning for Nextflow plugins in OCI registries (#912)
This PR implements unified security scanning for both container images and Nextflow plugin artifacts hosted in OCI registries. Key improvements: - Unified scanner script handles both container and plugin scans - ORAS integration for OCI artifact downloads - Trivy rootfs scanning for plugin dependencies - Explicit cache directory configuration - Multi-architecture support (amd64/arm64) The scanner automatically detects scan type from image name patterns and executes the appropriate workflow, providing consistent vulnerability detection across all Wave artifacts. Scanner image: public.cr.seqera.io/wave/scanner:v1
1 parent b056aff commit 5acca43

20 files changed

+1219
-284
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Build and publish Scanner container image
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
trivy_version:
7+
description: 'trivy version (e.g., 0.65.0)'
8+
required: true
9+
default: '0.65.0'
10+
oras_version :
11+
description : 'oras version (e.g., 1.3.0)'
12+
required : true
13+
default : '1.3.0'
14+
15+
jobs:
16+
build-and-push:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Checkout code
20+
uses: actions/checkout@v4
21+
22+
- name: Set up Docker Buildx
23+
id: buildx
24+
uses: docker/setup-buildx-action@v3
25+
26+
- name: Docker Login
27+
uses: docker/login-action@v3
28+
with:
29+
registry: public.cr.seqera.io
30+
username: ${{ vars.SEQERA_PUBLIC_CR_USERNAME }}
31+
password: ${{ secrets.SEQERA_PUBLIC_CR_PASSWORD }}
32+
33+
- name: Build and Push Image to public.cr.seqera.io
34+
run: |
35+
cd plugin-scanner
36+
make all trivy_version=${{ github.event.inputs.trivy_version }} oras_version=${{ github.event.inputs.oras_version }}

adr/20251016-plugin-scanning.md

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
# Security Scanning for Nextflow Plugins in OCI Registries
2+
3+
## Summary
4+
5+
This document describes the implementation of security scanning for 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.
6+
7+
## Context
8+
9+
Nextflow plugins are distributed as OCI artifacts in container registries, similar to container images but with different content types. These plugins contain JAR files and dependencies that need security scanning to detect vulnerabilities, just like container images. However, the scanning approach differs since plugins are not container images but rather ZIP archives containing Java artifacts.
10+
11+
## Feature Overview
12+
13+
### What it does
14+
- **Unified Scanning Infrastructure**: Single scanner implementation handles both container images and Nextflow plugins
15+
- **OCI Registry Support**: Pulls plugin artifacts from any OCI-compliant registry using ORAS
16+
- **Vulnerability Detection**: Scans plugin dependencies and JAR libraries for known CVEs using Trivy
17+
- **Multiple Output Formats**: Generates reports in JSON, SPDX, and CycloneDX formats
18+
- **Multi-Architecture Support**: Scanner image works on both AMD64 and ARM64 platforms
19+
20+
### How it works
21+
22+
**Detection Flow:**
23+
1. Scan request comes in with target image name
24+
2. System detects scan type based on image name pattern (`nextflow/plugin` → plugin scan)
25+
3. Scanner executes appropriate workflow:
26+
27+
**For Plugins:**
28+
- Downloads artifact using ORAS from OCI registry
29+
- Extracts plugin ZIP to temporary filesystem
30+
- Performs rootfs scan with Trivy (`trivy rootfs --scanners vuln`)
31+
- Generates vulnerability reports
32+
33+
**For Containers:**
34+
- Performs standard container image scan with Trivy (`trivy image`)
35+
- Analyzes all layers for vulnerabilities
36+
- Generates vulnerability reports
37+
38+
## Decision Drivers
39+
40+
1. **Unified Infrastructure**: Need to minimize operational complexity by using a single scanner for all artifact types
41+
2. **Flexibility**: Must support scanning of both standard OCI images and non-image OCI artifacts (plugins)
42+
3. **Consistency**: Same vulnerability database and reporting format across all scan types
43+
4. **Performance**: Efficient caching and resource utilization
44+
5. **Multi-Architecture**: Support for both AMD64 and ARM64 platforms
45+
46+
## Technical Decisions
47+
48+
### 1. Unified Scanner Script
49+
50+
**Decision:** Refactored `scanner/scan.sh` into a unified script with `--type` parameter
51+
- `--type container` → Container image scan
52+
- `--type plugin` → Nextflow plugin scan
53+
54+
**Rationale:**
55+
- Single script reduces maintenance overhead
56+
- Ensures consistent scanning behavior across types
57+
- Simplifies testing and deployment
58+
- Easier to add new scan types in the future
59+
60+
**Alternatives Considered:**
61+
- Separate scanner images for each type (rejected: increases maintenance)
62+
- Runtime auto-detection without explicit type (rejected: ambiguous for edge cases)
63+
64+
### 2. ORAS for Plugin Downloads
65+
66+
**Decision:** Integrated [ORAS](https://oras.land/) (OCI Registry As Storage) tool into scanner image
67+
68+
**Rationale:**
69+
- Industry-standard tool for OCI artifact management
70+
- Native support for non-container artifacts (plugins, charts, etc.)
71+
- Simple CLI interface integrates easily with bash scripts
72+
- Maintained by CNCF, ensuring long-term support
73+
74+
**Alternatives Considered:**
75+
- Custom OCI client implementation (rejected: reinventing the wheel)
76+
- Using docker/skopeo for artifact pull (rejected: designed for container images)
77+
78+
### 3. Filesystem Scanning for Plugins
79+
80+
**Decision:** Plugins scanned using Trivy's `rootfs` scanner mode with `--scanners vuln` flag
81+
82+
**Rationale:**
83+
- Plugins are JAR/ZIP artifacts, not container images
84+
- Filesystem scanning detects vulnerabilities in Java dependencies
85+
- Uses same Trivy vulnerability database as container scans
86+
- Provides consistent security posture across all artifacts
87+
88+
**Alternatives Considered:**
89+
- Container image wrapping (rejected: unnecessary complexity)
90+
- Custom vulnerability scanner (rejected: maintenance burden)
91+
92+
### 4. Explicit Cache Directory Configuration
93+
94+
**Decision:** Added `--cache-dir` option to configure Trivy's cache location explicitly
95+
96+
**Rationale:**
97+
- Improves reproducibility across environments
98+
- Allows mounting external cache volumes in production for persistence
99+
- Uses constant `Trivy.CACHE_MOUNT_PATH` for consistency across codebase
100+
- Better control over disk usage and cache lifecycle
101+
102+
**Alternatives Considered:**
103+
- Environment variable only (rejected: less explicit)
104+
- Hardcoded path (rejected: inflexible for different environments)
105+
106+
### 5. Multi-Architecture Scanner Image
107+
108+
**Decision:** Fixed Dockerfile to properly support both `amd64` and `arm64` architectures using Docker's `TARGETARCH` build arg
109+
110+
**Rationale:**
111+
- Critical for ARM-based infrastructure (AWS Graviton, Apple Silicon)
112+
- Single image tag works across all architectures
113+
- Reduces image management complexity
114+
- Improves deployment flexibility
115+
116+
**Alternatives Considered:**
117+
- Separate images per architecture (rejected: complex tag management)
118+
- AMD64 only (rejected: excludes ARM environments)
119+
120+
### 6. Pattern-Based Scan Type Detection
121+
122+
**Decision:** Detects plugin scans by checking if image name contains `nextflow/plugin`
123+
124+
**Rationale:**
125+
- Simple and effective for current use case
126+
- No additional registry roundtrips required
127+
- Provides clear migration path to manifest media type detection
128+
- Works immediately without registry changes
129+
130+
**Alternatives Considered:**
131+
- Manifest media type inspection (planned for future: #919)
132+
- User-provided scan type parameter (rejected: additional API complexity)
133+
134+
## Architecture
135+
136+
```
137+
┌─────────────────────────────────────────────────────────────┐
138+
│ Wave Application │
139+
│ │
140+
│ ┌────────────────────────────────────────────────────┐ │
141+
│ │ ContainerScanServiceImpl │ │
142+
│ │ • fromBuild() │ │
143+
│ │ • fromMirror() │ │
144+
│ │ • fromContainer() │ │
145+
│ └──────────────────┬──────────────────────────────────┘ │
146+
│ │ │
147+
│ ▼ │
148+
│ ┌────────────────────────────────────────────────────┐ │
149+
│ │ ScanStrategy │ │
150+
│ │ • buildScanCommand() │ │
151+
│ │ • Detects: container vs plugin │ │
152+
│ └──────────────────┬──────────────────────────────────┘ │
153+
│ │ │
154+
│ ▼ │
155+
│ ┌────────────────────────────────────────────────────┐ │
156+
│ │ KubeScanStrategy / DockerScanStrategy │ │
157+
│ │ • Launches scanner container │ │
158+
│ └──────────────────┬──────────────────────────────────┘ │
159+
└───────────────────────┼────────────────────────────────────┘
160+
161+
162+
┌─────────────────────────────────────────────────────────────┐
163+
│ Scanner Container Image │
164+
│ (public.cr.seqera.io/wave/scanner:v1) │
165+
│ │
166+
│ ┌────────────────────────────────────────────────────┐ │
167+
│ │ /usr/local/bin/scan.sh │ │
168+
│ │ │ │
169+
│ │ if [type == container]: │ │
170+
│ │ trivy image --platform $PLATFORM $TARGET │ │
171+
│ │ │ │
172+
│ │ if [type == plugin]: │ │
173+
│ │ oras pull $TARGET │ │
174+
│ │ unzip plugin.zip │ │
175+
│ │ trivy rootfs --scanners vuln /extracted │ │
176+
│ └────────────────────────────────────────────────────┘ │
177+
│ │
178+
│ Components: │
179+
│ • Trivy 0.65.0 (vulnerability scanner) │
180+
│ • ORAS 1.3.0 (OCI artifact client) │
181+
│ • bash, unzip (utilities) │
182+
└─────────────────────────────────────────────────────────────┘
183+
```
184+
185+
## Implementation Details
186+
187+
### Files Changed
188+
189+
**Scanner Implementation:**
190+
- `scanner/scan.sh` - Unified bash script with plugin support and `--cache-dir` option
191+
- `scanner/Dockerfile` - Multi-arch build with ORAS integration and `TARGETARCH` support
192+
- `scanner/Makefile` - Build configuration for multi-platform images
193+
194+
**Application Code:**
195+
- `ScanStrategy.groovy` - Added `buildScanCommand()` with scan type detection and `--cache-dir` parameter
196+
- `KubeScanStrategy.groovy` - Simplified to use unified command builder
197+
- `DockerScanStrategy.groovy` - Simplified to use unified command builder
198+
- `Trivy.groovy` - Added `CACHE_MOUNT_PATH` constant
199+
200+
**Tests:**
201+
- `ScanStrategyTest.groovy` - Updated command expectations with `--cache-dir`
202+
- `BuildScanCommandTest.groovy` - Updated command expectations with `--cache-dir`
203+
204+
### Testing
205+
206+
All tests passing:
207+
```bash
208+
./gradlew test --tests 'io.seqera.wave.service.scan.*Test'
209+
```
210+
211+
Test coverage includes:
212+
- Container scans with/without platform and severity
213+
- Plugin scans with/without severity
214+
- Scan type auto-detection logic
215+
- Timeout conversions and parameter handling
216+
- Cache directory configuration
217+
218+
### Deployment
219+
220+
**Building the Scanner Image:**
221+
222+
```bash
223+
cd scanner
224+
make build # Builds for linux/amd64 and linux/arm64
225+
```
226+
227+
This publishes:
228+
- `public.cr.seqera.io/wave/scanner:v1-0.65.0-oras-1.3.0`
229+
- `public.cr.seqera.io/wave/scanner:v1` (latest)
230+
231+
**Configuration:**
232+
233+
The application automatically uses the scanner via existing configuration:
234+
```yaml
235+
wave:
236+
scan:
237+
image:
238+
name: public.cr.seqera.io/wave/scanner:v1
239+
```
240+
241+
## Example Usage
242+
243+
**Container Scan:**
244+
```bash
245+
/usr/local/bin/scan.sh \
246+
--type container \
247+
--target ubuntu:latest \
248+
--work-dir /tmp/scan \
249+
--platform linux/amd64 \
250+
--timeout 15 \
251+
--cache-dir /root/.cache/
252+
```
253+
254+
**Plugin Scan:**
255+
```bash
256+
/usr/local/bin/scan.sh \
257+
--type plugin \
258+
--target ghcr.io/nextflow-io/nextflow/plugins/nf-amazon:1.0.0 \
259+
--work-dir /tmp/scan \
260+
--timeout 15 \
261+
--cache-dir /root/.cache/
262+
```
263+
264+
## Benefits
265+
266+
1.**Unified Infrastructure** - Single image handles all security scanning
267+
2.**Simplified Maintenance** - One scanner to build, test, and deploy
268+
3.**Consistent Security** - Same Trivy version and vulnerability database
269+
4.**Better Performance** - Explicit cache configuration and reuse
270+
5.**Multi-Architecture** - Works on AMD64 and ARM64 platforms
271+
6.**Future-Ready** - Easy to extend for new artifact types
272+
273+
## Consequences
274+
275+
### Positive
276+
- Reduced operational complexity with single scanner image
277+
- Consistent vulnerability detection across all artifact types
278+
- Better resource utilization through unified caching
279+
- Multi-architecture support enables broader platform compatibility
280+
- Clear path for future artifact type additions
281+
282+
### Negative
283+
- Pattern-based detection less robust than media type inspection (mitigated: planned for #919)
284+
- Additional runtime dependency (ORAS) increases image size by ~10MB
285+
- Plugin extraction requires temporary disk space during scanning
286+
287+
### Neutral
288+
- Scanner image size increased from ~300MB to ~310MB (ORAS addition)
289+
- Slight increase in scan time for plugins due to extraction step (~2-3 seconds)
290+
291+
## References
292+
293+
- [ORAS Project](https://oras.land/)
294+
- [Trivy Documentation](https://aquasecurity.github.io/trivy/)
295+
- [OCI Distribution Spec](https://github.com/opencontainers/distribution-spec)
296+
- [Nextflow Plugins Documentation](https://www.nextflow.io/docs/latest/plugins.html)
297+
- Wave Issue #919: Manifest media type detection
298+
299+
---
300+
301+
**Status:** Implemented
302+
**Date:** 2025-10-16
303+
**Authors:** Wave Team
304+
**Related PR:** #912

scanner/Dockerfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
ARG version=0.65.0
2+
ARG oras_version=1.3.0
3+
FROM aquasec/trivy:${version}
4+
# Disable zipbomb detection in unzip utility. Container layers can have high compression ratios
5+
# that trigger false positives. The scanner needs to examine all compressed artifacts without
6+
# restrictions as part of security scanning operations.
7+
ENV UNZIP_DISABLE_ZIPBOMB_DETECTION=TRUE
8+
RUN apk add --no-cache curl unzip bash
9+
# Redeclare ARGs after FROM to use them in RUN commands
10+
ARG oras_version=1.3.0
11+
ARG TARGETARCH
12+
RUN curl -LO "https://github.com/oras-project/oras/releases/download/v${oras_version}/oras_${oras_version}_linux_${TARGETARCH}.tar.gz" \
13+
&& tar -zxf oras_${oras_version}_linux_${TARGETARCH}.tar.gz \
14+
&& mv oras /usr/local/bin/ \
15+
&& rm oras_${oras_version}_linux_${TARGETARCH}.tar.gz
16+
17+
COPY scan.sh /usr/local/bin/scan.sh
18+
RUN chmod +x /usr/local/bin/scan.sh
19+
20+
ENTRYPOINT []

scanner/Makefile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
trivy_version ?= 0.65.0
2+
oras_version ?= 1.3.0
3+
4+
all: build latest
5+
6+
build:
7+
docker buildx build \
8+
--push \
9+
--platform linux/amd64,linux/arm64 \
10+
--build-arg version=${trivy_version} \
11+
--build-arg oras_version=${oras_version} \
12+
--tag public.cr.seqera.io/wave/scanner:v1-${trivy_version}-oras-${oras_version} \
13+
.
14+
15+
latest:
16+
docker buildx build \
17+
--push \
18+
--platform linux/amd64,linux/arm64 \
19+
--build-arg version=${trivy_version} \
20+
--build-arg oras_version=${oras_version} \
21+
--tag public.cr.seqera.io/wave/scanner:v1 \
22+
.

0 commit comments

Comments
 (0)