kestra-io / kestra-io/plugin-snyk

Add ListIssues task to retrieve security vulnerabilities via Snyk REST API

Open
#3 0 comments 0 reactions 0 assignees View on GitHub
area/plugin kind/highlight
Dominant language
Dockerfile
Stars
0
Forks
0
Avg merge
4d 4h
Merged PRs (30d)
3

Description

## Summary

This task integrates Kestra with the [Snyk REST API](https://docs.snyk.io/developer-tools/snyk-api/rest-api/about-the-rest-api) by implementing a `ListIssues` task that retrieves security vulnerabilities for a given Snyk organization. It enables automation workflows to gate deployments on critical findings, aggregate vulnerability data for downstream reporting, and monitor security posture across projects — all without leaving a Kestra flow.

## Motivation

Without this task, users must shell out to the Snyk CLI, write custom Python scripts, or wire up raw HTTP calls to retrieve vulnerability data inside a flow. Security and platform engineering teams who already run Snyk need a native Kestra integration to:
- Block deployments when critical or high-severity vulnerabilities are introduced
- Export vulnerability data to SIEM systems, data warehouses, or notification channels
- Automate security reporting across multiple Snyk organizations

## Context

This is the first implementation task for `plugin-snyk`. Kestra's `plugin-core.http` tasks and any REST-based plugin (e.g. `plugin-ee-netbox`) can serve as reference implementations for the HTTP client and `Property` usage patterns.

API documentation: https://docs.snyk.io/developer-tools/snyk-api/rest-api/about-the-rest-api

## API Reference

- **Official docs**: https://docs.snyk.io/developer-tools/snyk-api/rest-api/about-the-rest-api
- **Authentication**: API token via `Authorization: token ` header
- **Base URL (default)**: `https://api.snyk.io/rest` — user-overridable to support regional deployments:
- SNYK-US-01: `https://api.snyk.io/rest`
- SNYK-US-02: `https://api.us.snyk.io/rest`
- SNYK-EU-01: `https://api.eu.snyk.io/rest`
- SNYK-AU-01: `https://api.au.snyk.io/rest`
- **Versioning**: `?version=YYYY-MM-DD` query parameter (recommended default: `2024-10-15`); older versions may append a stability level, e.g. `2023-11-27~beta`
- **Content-Type**: `application/vnd.api+json`
- **Pagination**: cursor-based via `starting_after` / `ending_before` query parameters
- **Rate limit**: 1620 requests/minute per API key; `429` on excess
- **SDK / client library**: No official Java SDK — use Kestra's internal HTTP client (`io.kestra.core.http.client`)

### Key endpoints

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/orgs/{org_id}/issues` | List all issues for an organization |
| `GET` | `/orgs/{org_id}/issues/{issue_id}` | Get a specific issue by ID |
| `POST` | `/orgs/{org_id}/packages/issues` | Batch-query issues for a list of packages by purl |
| `GET` | `/orgs/{org_id}/packages/{purl}/issues` | Get issues for a specific package |

## Gradle Dependencies

No additional HTTP client dependency is required — Kestra's internal HTTP client and Jackson serializers are provided by the framework.

```groovy
// No extra dependencies needed.
// Kestra's internal HTTP client (io.kestra.core.http.client) and Jackson
// are provided by the framework.
```

> If a future task requires Package URL (purl) parsing, consider:
> `implementation "com.github.package-url:packageurl-java:1.5.0"` (verify latest on Maven Central)

## Plugin Structure

- **Repository**: `kestra-io/plugin-snyk`
- **Namespace**: `io.kestra.plugin.snyk`
- **Sub-plugins**: none (flat structure for this initial task)
- **Categories**: `INFRASTRUCTURE`

## Suggested Tasks

1. Implement an abstract connection base class holding `token`, `baseUrl`, and `version` shared properties
2. `ListIssues` — `GET /orgs/{org_id}/issues` — list security issues for an organization, with optional severity filtering and `FetchType` support (`FETCH_ONE`, `FETCH`, `STORE`); handle cursor-based pagination automatically for `FETCH` and `STORE` modes
3. `IssueTrigger` — polling trigger that periodically calls `GET /orgs/{org_id}/issues`, emits one execution per new issue detected since the last poll, and advances its internal cursor to avoid re-emitting the same issue
4. Write unit tests using Wiremock (no public Docker image for Snyk; use `testImplementation "org.wiremock:wiremock-jetty12"`)
5. Add `package-info.java` with `@PluginSubGroup(category = PluginSubGroup.PluginCategory.INFRASTRUCTURE)`
6. Add `metadata/index.yaml` and plugin icon SVG
7. Add YAML examples and plugin documentation

### Task design notes

The `ListIssues` task should expose:
- `token` — required `Property`, annotated `@PluginProperty(secret = true)`, group `"connection"`
- `baseUrl` — optional `Property`, default `https://api.snyk.io/rest`, group `"connection"`
- `version` — optional `Property`, default `2024-10-15` (YYYY-MM-DD format), group `"connection"`
- `orgId` — required `Property`, group `"main"`
- `severities` — optional `Property>` where `Severity` is an enum of `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, group `"processing"`
- `fetchType` — `Property`, default `FETCH`, group `"processing"`

The `IssueTrigger` should extend `AbstractTrigger`, implement `PollingTriggerInterface` and `TriggerOutput`, and expose the same `token`, `baseUrl`, `version`, `orgId`, and `severities` properties as `ListIssues`. On each evaluation it fetches new issues since the last known issue ID/cursor, emits one execution per new issue, and stores the cursor in trigger context to avoid re-emission.

## YAML Examples

### Example 1 — List all critical and high issues for an organization

```yaml
id: snyk_list_critical_issues
namespace: company.team

tasks:
- id: list_issues
type: io.kestra.plugin.snyk.ListIssues
token: "{{ secret('SNYK_API_TOKEN') }}"
orgId: "{{ secret('SNYK_ORG_ID') }}"
severities:
- CRITICAL
- HIGH
fetchType: STORE
```

### Example 2 — List all issues in the EU region and log the total count

```yaml
id: snyk_issue_count_eu
namespace: company.team

tasks:
- id: list_issues
type: io.kestra.plugin.snyk.ListIssues
token: "{{ secret('SNYK_API_TOKEN') }}"
orgId: "{{ secret('SNYK_ORG_ID') }}"
baseUrl: "https://api.eu.snyk.io/rest"
fetchType: FETCH

- id: log_count
type: io.kestra.plugin.core.log.Log
message: "Found {{ outputs.list_issues.size }} issues"
```

### Example 3 — Poll for new critical issues and trigger an alert

```yaml
id: snyk_alert_on_new_critical
namespace: company.team

triggers:
- id: on_new_critical_issue
type: io.kestra.plugin.snyk.triggers.IssueTrigger
token: "{{ secret('SNYK_API_TOKEN') }}"
orgId: "{{ secret('SNYK_ORG_ID') }}"
severities:
- CRITICAL
interval: PT15M

tasks:
- id: handle_issue
type: io.kestra.plugin.core.log.Log
message: "New critical issue detected: {{ trigger.issueId }}"
```

## Acceptance Criteria

### Functional
- [ ] Abstract connection base class with `token`, `baseUrl` (default `https://api.snyk.io/rest`), and `version` (default `2024-10-15`) implemented
- [ ] `ListIssues` task implemented with optional severity filtering and `FetchType` support (`FETCH_ONE`, `FETCH`, `STORE`)
- [ ] Cursor-based pagination handled automatically in `FETCH` and `STORE` modes
- [ ] `IssueTrigger` polling trigger implemented — emits one execution per new issue, advances cursor to avoid re-emission
- [ ] Unit tests using Wiremock pass (`./gradlew test`)
- [ ] Build passes (`./gradlew build`)

### Kestra Plugin Coding Standards
- [ ] HTTP calls use Kestra's internal HTTP client (`io.kestra.core.http.client`) — no OkHttp, Apache HttpClient, or similar
- [ ] All new properties use `Property` — no legacy `@PluginProperty(dynamic = true)` on new code
- [ ] `token` annotated with `@PluginProperty(secret = true)`
- [ ] Every property and output carries a `@Schema` annotation
- [ ] Task class carries the five mandatory Lombok annotations (`@SuperBuilder`, `@ToString`, `@EqualsAndHashCode`, `@Getter`, `@NoArgsConstructor`)
- [ ] Logging via `runContext.logger()` only
- [ ] JSON serialization uses Jackson mappers from `io.kestra.core.serializers`
- [ ] `@JsonIgnoreProperties(ignoreUnknown = true)` on all Snyk response model classes
- [ ] All `Property` fields support Kestra expression language

### Documentation & Structure
- [ ] `@Plugin(examples = ...)` entries each set `full = true` with a complete runnable flow (id + namespace + tasks/triggers)
- [ ] Sensitive values in examples use `{{ secret('SECRET_NAME') }}`
- [ ] `package-info.java` with `@PluginSubGroup(category = PluginSubGroup.PluginCategory.INFRASTRUCTURE)`
- [ ] `metadata/index.yaml` and plugin icon SVG present

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reading Kestra's plugin-core HTTP tasks and the plugin-ee-netbox REST implementation for client and Property patterns. Define the ListIssues entry point around GET /orgs/{org_id}/issues, then use WireMock tests to cover filtering, fetch modes, and pagination. Done means ./gradlew test and ./gradlew build pass with the listed acceptance criteria satisfied.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
api, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.