@sanity-labs/backstage-plugin-trivy-backend 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/index.cjs.js +325 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Trivy Backend Plugin
|
|
2
|
+
|
|
3
|
+
This backend plugin integrates Trivy security scan results from Google Cloud Storage into Backstage.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Fetches Trivy scan results from GCS bucket (`sanity-trivy-logs`)
|
|
8
|
+
- Parses filesystem and image scan outputs
|
|
9
|
+
- Provides REST API endpoints for security findings
|
|
10
|
+
- Links findings to CircleCI builds and GitHub PRs
|
|
11
|
+
|
|
12
|
+
## API Endpoints
|
|
13
|
+
|
|
14
|
+
### `GET /api/trivy/health`
|
|
15
|
+
Health check endpoint.
|
|
16
|
+
|
|
17
|
+
### `GET /api/trivy/scans`
|
|
18
|
+
Returns all security scans sorted by timestamp (newest first).
|
|
19
|
+
|
|
20
|
+
Response:
|
|
21
|
+
```json
|
|
22
|
+
[
|
|
23
|
+
{
|
|
24
|
+
"repo": "alert-relay",
|
|
25
|
+
"branch": "add-ci",
|
|
26
|
+
"scanId": "2627705d-5a14-4d61-ae79-e9ec457b851c",
|
|
27
|
+
"timestamp": "2024-01-15T10:30:00Z",
|
|
28
|
+
"metadata": {
|
|
29
|
+
"circleciUrl": "https://circleci.com/gh/sanity-io/alert-relay/9",
|
|
30
|
+
"githubPr": "https://github.com/sanity-io/alert-relay/pull/4",
|
|
31
|
+
"gitCommit": "6e0858eab194dcab5235b4ef41165f80c7a9dd45",
|
|
32
|
+
"committer": "obliadp"
|
|
33
|
+
},
|
|
34
|
+
"findings": [
|
|
35
|
+
{
|
|
36
|
+
"file": "deploy/base/deployment.yaml",
|
|
37
|
+
"severity": "HIGH",
|
|
38
|
+
"title": "Container should set securityContext.readOnlyRootFilesystem",
|
|
39
|
+
"description": "An immutable root file system prevents...",
|
|
40
|
+
"lines": "18-62",
|
|
41
|
+
"avdId": "https://avd.aquasec.com/misconfig/ksv014"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `GET /api/trivy/scans/:repo/:branch/:scanId`
|
|
49
|
+
Returns a specific scan by repo, branch, and scan ID.
|
|
50
|
+
|
|
51
|
+
### `GET /api/trivy/scans/:repo/latest`
|
|
52
|
+
Returns the latest scan for a specific repository.
|
|
53
|
+
|
|
54
|
+
## Setup
|
|
55
|
+
|
|
56
|
+
1. Ensure you have access to the `sanity-trivy-logs` GCS bucket
|
|
57
|
+
2. Configure GCP credentials for the Backstage backend
|
|
58
|
+
3. Add the plugin to your backend in `packages/backend/src/index.ts`:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import trivy from '@internal/backstage-plugin-trivy-backend';
|
|
62
|
+
|
|
63
|
+
const backend = createBackend();
|
|
64
|
+
backend.add(trivy());
|
|
65
|
+
backend.start();
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
### app-config.yaml
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
trivy:
|
|
74
|
+
# GCS bucket name (default: sanity-trivy-logs)
|
|
75
|
+
bucketName: sanity-trivy-logs
|
|
76
|
+
|
|
77
|
+
# Backend cache time in minutes (default: 5)
|
|
78
|
+
# How long to cache GCS responses in memory
|
|
79
|
+
cacheTime: 5
|
|
80
|
+
|
|
81
|
+
# Severity filter (default: CRITICAL, HIGH, MEDIUM)
|
|
82
|
+
severityFilter:
|
|
83
|
+
- CRITICAL
|
|
84
|
+
- HIGH
|
|
85
|
+
- MEDIUM
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### GCP Authentication
|
|
89
|
+
|
|
90
|
+
The plugin uses Google Cloud Storage with Application Default Credentials. Ensure your Backstage backend has appropriate GCP credentials configured.
|
|
91
|
+
|
|
92
|
+
### Caching
|
|
93
|
+
|
|
94
|
+
The backend implements in-memory caching to reduce GCS API calls:
|
|
95
|
+
|
|
96
|
+
- Caches all API responses for the configured `cacheTime` duration
|
|
97
|
+
- Cache keys are based on endpoint parameters
|
|
98
|
+
- Cache stats available via `/api/trivy/health` endpoint
|
|
99
|
+
- Automatic cache invalidation after TTL expires
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
// GET /api/trivy/health response
|
|
103
|
+
{
|
|
104
|
+
"status": "ok",
|
|
105
|
+
"cache": {
|
|
106
|
+
"size": 3,
|
|
107
|
+
"keys": ["all-scans", "latest-alert-relay", "scan-alert-relay-main-abc123"]
|
|
108
|
+
},
|
|
109
|
+
"config": {
|
|
110
|
+
"bucketName": "sanity-trivy-logs",
|
|
111
|
+
"cacheTime": 5
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
6
|
+
var backendCommon = require('@backstage/backend-common');
|
|
7
|
+
var express = require('express');
|
|
8
|
+
var Router = require('express-promise-router');
|
|
9
|
+
var storage = require('@google-cloud/storage');
|
|
10
|
+
|
|
11
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
12
|
+
|
|
13
|
+
var express__default = /*#__PURE__*/_interopDefaultLegacy(express);
|
|
14
|
+
var Router__default = /*#__PURE__*/_interopDefaultLegacy(Router);
|
|
15
|
+
|
|
16
|
+
var __defProp = Object.defineProperty;
|
|
17
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
18
|
+
var __publicField = (obj, key, value) => {
|
|
19
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
20
|
+
return value;
|
|
21
|
+
};
|
|
22
|
+
class CacheManager {
|
|
23
|
+
constructor(ttlMinutes = 5, logger) {
|
|
24
|
+
__publicField(this, "cache");
|
|
25
|
+
__publicField(this, "ttlMs");
|
|
26
|
+
__publicField(this, "logger");
|
|
27
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
28
|
+
this.ttlMs = ttlMinutes * 60 * 1e3;
|
|
29
|
+
this.logger = logger;
|
|
30
|
+
}
|
|
31
|
+
get(key) {
|
|
32
|
+
const entry = this.cache.get(key);
|
|
33
|
+
if (!entry) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const age = Date.now() - entry.timestamp;
|
|
37
|
+
if (age > this.ttlMs) {
|
|
38
|
+
this.cache.delete(key);
|
|
39
|
+
this.logger.debug(`Cache miss (expired): ${key}`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
this.logger.debug(`Cache hit: ${key}`);
|
|
43
|
+
return entry.data;
|
|
44
|
+
}
|
|
45
|
+
set(key, data) {
|
|
46
|
+
this.cache.set(key, {
|
|
47
|
+
data,
|
|
48
|
+
timestamp: Date.now()
|
|
49
|
+
});
|
|
50
|
+
this.logger.debug(`Cache set: ${key}`);
|
|
51
|
+
}
|
|
52
|
+
has(key) {
|
|
53
|
+
const entry = this.cache.get(key);
|
|
54
|
+
if (!entry)
|
|
55
|
+
return false;
|
|
56
|
+
const age = Date.now() - entry.timestamp;
|
|
57
|
+
if (age > this.ttlMs) {
|
|
58
|
+
this.cache.delete(key);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
clear() {
|
|
64
|
+
this.cache.clear();
|
|
65
|
+
this.logger.info("Cache cleared");
|
|
66
|
+
}
|
|
67
|
+
size() {
|
|
68
|
+
return this.cache.size;
|
|
69
|
+
}
|
|
70
|
+
stats() {
|
|
71
|
+
return {
|
|
72
|
+
size: this.cache.size,
|
|
73
|
+
keys: Array.from(this.cache.keys())
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseMetadata(content) {
|
|
79
|
+
var _a, _b, _c, _d;
|
|
80
|
+
const lines = content.split("\n");
|
|
81
|
+
const metadata = {};
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
if (line.startsWith("CircleCI Build URL:")) {
|
|
84
|
+
metadata.circleciUrl = ((_a = line.split(":", 2)[1]) == null ? void 0 : _a.trim()) || void 0;
|
|
85
|
+
} else if (line.startsWith("GitHub PR:")) {
|
|
86
|
+
metadata.githubPr = ((_b = line.split(":", 2)[1]) == null ? void 0 : _b.trim()) || void 0;
|
|
87
|
+
} else if (line.startsWith("Git Commit:")) {
|
|
88
|
+
metadata.gitCommit = ((_c = line.split(":", 2)[1]) == null ? void 0 : _c.trim()) || void 0;
|
|
89
|
+
} else if (line.startsWith("Committer:")) {
|
|
90
|
+
metadata.committer = ((_d = line.split(":", 2)[1]) == null ? void 0 : _d.trim()) || void 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return metadata;
|
|
94
|
+
}
|
|
95
|
+
function parseTrivyOutput(content) {
|
|
96
|
+
const findings = [];
|
|
97
|
+
const sections = content.split(/\n(?=[^\s])/);
|
|
98
|
+
let currentFile = "";
|
|
99
|
+
for (const section of sections) {
|
|
100
|
+
const fileMatch = section.match(/^(.+)\s+\((\w+)\)\s*\n={40}/);
|
|
101
|
+
if (fileMatch) {
|
|
102
|
+
currentFile = fileMatch[1];
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const severityMatch = section.match(/^(CRITICAL|HIGH|MEDIUM|LOW|UNKNOWN):\s+(.+)/);
|
|
106
|
+
if (severityMatch && currentFile) {
|
|
107
|
+
const severity = severityMatch[1];
|
|
108
|
+
const title = severityMatch[2];
|
|
109
|
+
const descMatch = section.match(/═{40}\n([\s\S]+?)\n(?:See|─{40})/);
|
|
110
|
+
const description = descMatch ? descMatch[1].trim() : "";
|
|
111
|
+
const avdMatch = section.match(/See\s+(https:\/\/avd\.aquasec\.com\/[^\s]+)/);
|
|
112
|
+
const avdId = avdMatch ? avdMatch[1] : void 0;
|
|
113
|
+
const linesMatch = section.match(/─{40}\n\s*(.+?):(\d+)-(\d+)/);
|
|
114
|
+
const lines = linesMatch ? `${linesMatch[2]}-${linesMatch[3]}` : "";
|
|
115
|
+
findings.push({
|
|
116
|
+
file: currentFile,
|
|
117
|
+
severity,
|
|
118
|
+
title,
|
|
119
|
+
description,
|
|
120
|
+
lines,
|
|
121
|
+
avdId
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return findings;
|
|
126
|
+
}
|
|
127
|
+
function getBackendConfig(config) {
|
|
128
|
+
const trivyConfig = config == null ? void 0 : config.getOptionalConfig("trivy");
|
|
129
|
+
return {
|
|
130
|
+
bucketName: (trivyConfig == null ? void 0 : trivyConfig.getOptionalString("bucketName")) || "sanity-trivy-logs",
|
|
131
|
+
cacheTime: (trivyConfig == null ? void 0 : trivyConfig.getOptionalNumber("cacheTime")) || 5
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async function createRouter(options) {
|
|
135
|
+
const { logger, config } = options;
|
|
136
|
+
const router = Router__default["default"]();
|
|
137
|
+
router.use(express__default["default"].json());
|
|
138
|
+
const backendConfig = getBackendConfig(config);
|
|
139
|
+
const storage$1 = new storage.Storage();
|
|
140
|
+
const bucketName = backendConfig.bucketName;
|
|
141
|
+
const cache = new CacheManager(backendConfig.cacheTime, logger);
|
|
142
|
+
router.get("/health", (_, response) => {
|
|
143
|
+
logger.info("PONG!");
|
|
144
|
+
response.json({
|
|
145
|
+
status: "ok",
|
|
146
|
+
cache: cache.stats(),
|
|
147
|
+
config: {
|
|
148
|
+
bucketName,
|
|
149
|
+
cacheTime: backendConfig.cacheTime
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
router.get("/scans", async (_, response) => {
|
|
154
|
+
try {
|
|
155
|
+
const cacheKey = "all-scans";
|
|
156
|
+
const cached = cache.get(cacheKey);
|
|
157
|
+
if (cached) {
|
|
158
|
+
logger.info("Returning cached scans");
|
|
159
|
+
response.json(cached);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const [files] = await storage$1.bucket(bucketName).getFiles({
|
|
163
|
+
prefix: "repos/"
|
|
164
|
+
});
|
|
165
|
+
const scans = /* @__PURE__ */ new Map();
|
|
166
|
+
for (const file of files) {
|
|
167
|
+
const pathParts = file.name.split("/");
|
|
168
|
+
if (pathParts.length < 4)
|
|
169
|
+
continue;
|
|
170
|
+
const repo = pathParts[1];
|
|
171
|
+
const branch = pathParts[2];
|
|
172
|
+
const scanId = pathParts[3];
|
|
173
|
+
const fileName = pathParts[4];
|
|
174
|
+
const key = `${repo}/${branch}/${scanId}`;
|
|
175
|
+
if (!scans.has(key)) {
|
|
176
|
+
scans.set(key, {
|
|
177
|
+
repo,
|
|
178
|
+
branch,
|
|
179
|
+
scanId,
|
|
180
|
+
metadata: {},
|
|
181
|
+
findings: [],
|
|
182
|
+
timestamp: file.metadata.timeCreated ? new Date(file.metadata.timeCreated) : /* @__PURE__ */ new Date()
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const scan = scans.get(key);
|
|
186
|
+
if (fileName === "trivy-metadata.txt") {
|
|
187
|
+
const [content] = await file.download();
|
|
188
|
+
scan.metadata = parseMetadata(content.toString());
|
|
189
|
+
} else if (fileName === "trivy-fs-output.txt" || fileName === "trivy-image-output.txt") {
|
|
190
|
+
const [content] = await file.download();
|
|
191
|
+
const findings = parseTrivyOutput(content.toString());
|
|
192
|
+
scan.findings.push(...findings);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const scanArray = Array.from(scans.values()).sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
196
|
+
cache.set(cacheKey, scanArray);
|
|
197
|
+
response.json(scanArray);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
logger.error("Error fetching scans:", error);
|
|
200
|
+
response.status(500).json({ error: "Failed to fetch scans" });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
router.get("/scans/:repo/:branch/:scanId", async (request, response) => {
|
|
204
|
+
try {
|
|
205
|
+
const { repo, branch, scanId } = request.params;
|
|
206
|
+
const cacheKey = `scan-${repo}-${branch}-${scanId}`;
|
|
207
|
+
const cached = cache.get(cacheKey);
|
|
208
|
+
if (cached) {
|
|
209
|
+
logger.info(`Returning cached scan: ${cacheKey}`);
|
|
210
|
+
response.json(cached);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const prefix = `repos/${repo}/${branch}/${scanId}/`;
|
|
214
|
+
const [files] = await storage$1.bucket(bucketName).getFiles({ prefix });
|
|
215
|
+
const scan = {
|
|
216
|
+
repo,
|
|
217
|
+
branch,
|
|
218
|
+
scanId,
|
|
219
|
+
metadata: {},
|
|
220
|
+
findings: [],
|
|
221
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
222
|
+
};
|
|
223
|
+
for (const file of files) {
|
|
224
|
+
const fileName = file.name.split("/").pop();
|
|
225
|
+
if (fileName === "trivy-metadata.txt") {
|
|
226
|
+
const [content] = await file.download();
|
|
227
|
+
scan.metadata = parseMetadata(content.toString());
|
|
228
|
+
} else if (fileName === "trivy-fs-output.txt" || fileName === "trivy-image-output.txt") {
|
|
229
|
+
const [content] = await file.download();
|
|
230
|
+
scan.findings.push(...parseTrivyOutput(content.toString()));
|
|
231
|
+
}
|
|
232
|
+
if (file.metadata.timeCreated) {
|
|
233
|
+
scan.timestamp = new Date(file.metadata.timeCreated);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
cache.set(cacheKey, scan);
|
|
237
|
+
response.json(scan);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
logger.error("Error fetching scan:", error);
|
|
240
|
+
response.status(500).json({ error: "Failed to fetch scan" });
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
router.get("/scans/:repo/latest", async (request, response) => {
|
|
244
|
+
try {
|
|
245
|
+
const { repo } = request.params;
|
|
246
|
+
const cacheKey = `latest-${repo}`;
|
|
247
|
+
const cached = cache.get(cacheKey);
|
|
248
|
+
if (cached) {
|
|
249
|
+
logger.info(`Returning cached latest scan: ${cacheKey}`);
|
|
250
|
+
response.json(cached);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const prefix = `repos/${repo}/`;
|
|
254
|
+
const [files] = await storage$1.bucket(bucketName).getFiles({ prefix });
|
|
255
|
+
const scans = /* @__PURE__ */ new Map();
|
|
256
|
+
for (const file of files) {
|
|
257
|
+
const pathParts = file.name.split("/");
|
|
258
|
+
if (pathParts.length < 5)
|
|
259
|
+
continue;
|
|
260
|
+
const branch = pathParts[2];
|
|
261
|
+
const scanId = pathParts[3];
|
|
262
|
+
const fileName = pathParts[4];
|
|
263
|
+
const key = `${repo}/${branch}/${scanId}`;
|
|
264
|
+
if (!scans.has(key)) {
|
|
265
|
+
scans.set(key, {
|
|
266
|
+
repo,
|
|
267
|
+
branch,
|
|
268
|
+
scanId,
|
|
269
|
+
metadata: {},
|
|
270
|
+
findings: [],
|
|
271
|
+
timestamp: file.metadata.timeCreated ? new Date(file.metadata.timeCreated) : /* @__PURE__ */ new Date()
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
const scan = scans.get(key);
|
|
275
|
+
if (fileName === "trivy-metadata.txt") {
|
|
276
|
+
const [content] = await file.download();
|
|
277
|
+
scan.metadata = parseMetadata(content.toString());
|
|
278
|
+
} else if (fileName === "trivy-fs-output.txt" || fileName === "trivy-image-output.txt") {
|
|
279
|
+
const [content] = await file.download();
|
|
280
|
+
scan.findings.push(...parseTrivyOutput(content.toString()));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const latestScan = Array.from(scans.values()).sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())[0];
|
|
284
|
+
if (!latestScan) {
|
|
285
|
+
response.status(404).json({ error: "No scans found for repo" });
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
cache.set(cacheKey, latestScan);
|
|
289
|
+
response.json(latestScan);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
logger.error("Error fetching latest scan:", error);
|
|
292
|
+
response.status(500).json({ error: "Failed to fetch latest scan" });
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
router.use(backendCommon.errorHandler());
|
|
296
|
+
return router;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const trivyPlugin = backendPluginApi.createBackendPlugin({
|
|
300
|
+
pluginId: "trivy",
|
|
301
|
+
register(env) {
|
|
302
|
+
env.registerInit({
|
|
303
|
+
deps: {
|
|
304
|
+
httpRouter: backendPluginApi.coreServices.httpRouter,
|
|
305
|
+
logger: backendPluginApi.coreServices.logger,
|
|
306
|
+
config: backendPluginApi.coreServices.rootConfig
|
|
307
|
+
},
|
|
308
|
+
async init({ httpRouter, logger, config }) {
|
|
309
|
+
httpRouter.use(
|
|
310
|
+
await createRouter({
|
|
311
|
+
logger,
|
|
312
|
+
config
|
|
313
|
+
})
|
|
314
|
+
);
|
|
315
|
+
httpRouter.addAuthPolicy({
|
|
316
|
+
path: "/health",
|
|
317
|
+
allow: "unauthenticated"
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
exports["default"] = trivyPlugin;
|
|
325
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/service/cache.ts","../src/service/router.ts","../src/plugin.ts"],"sourcesContent":["import { LoggerService } from '@backstage/backend-plugin-api';\n\ninterface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\nexport class CacheManager {\n private cache: Map<string, CacheEntry<any>>;\n private ttlMs: number;\n private logger: LoggerService;\n\n constructor(ttlMinutes: number = 5, logger: LoggerService) {\n this.cache = new Map();\n this.ttlMs = ttlMinutes * 60 * 1000;\n this.logger = logger;\n }\n\n get<T>(key: string): T | null {\n const entry = this.cache.get(key);\n\n if (!entry) {\n return null;\n }\n\n const age = Date.now() - entry.timestamp;\n\n if (age > this.ttlMs) {\n this.cache.delete(key);\n this.logger.debug(`Cache miss (expired): ${key}`);\n return null;\n }\n\n this.logger.debug(`Cache hit: ${key}`);\n return entry.data as T;\n }\n\n set<T>(key: string, data: T): void {\n this.cache.set(key, {\n data,\n timestamp: Date.now(),\n });\n this.logger.debug(`Cache set: ${key}`);\n }\n\n has(key: string): boolean {\n const entry = this.cache.get(key);\n if (!entry) return false;\n\n const age = Date.now() - entry.timestamp;\n if (age > this.ttlMs) {\n this.cache.delete(key);\n return false;\n }\n\n return true;\n }\n\n clear(): void {\n this.cache.clear();\n this.logger.info('Cache cleared');\n }\n\n size(): number {\n return this.cache.size;\n }\n\n stats(): { size: number; keys: string[] } {\n return {\n size: this.cache.size,\n keys: Array.from(this.cache.keys()),\n };\n }\n}\n","import { errorHandler } from '@backstage/backend-common';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { Storage } from '@google-cloud/storage';\nimport { CacheManager } from './cache';\n\nexport interface RouterOptions {\n logger: LoggerService;\n config?: Config;\n}\n\ninterface TrivyBackendConfig {\n bucketName: string;\n cacheTime: number;\n}\n\ninterface TrivyMetadata {\n circleciUrl?: string;\n githubPr?: string;\n gitCommit?: string;\n committer?: string;\n}\n\ninterface TrivyFinding {\n file: string;\n severity: string;\n title: string;\n description: string;\n lines: string;\n avdId?: string;\n}\n\ninterface TrivyScanResult {\n repo: string;\n branch: string;\n scanId: string;\n metadata: TrivyMetadata;\n findings: TrivyFinding[];\n timestamp: Date;\n}\n\nfunction parseMetadata(content: string): TrivyMetadata {\n const lines = content.split('\\n');\n const metadata: TrivyMetadata = {};\n\n for (const line of lines) {\n if (line.startsWith('CircleCI Build URL:')) {\n metadata.circleciUrl = line.split(':', 2)[1]?.trim() || undefined;\n } else if (line.startsWith('GitHub PR:')) {\n metadata.githubPr = line.split(':', 2)[1]?.trim() || undefined;\n } else if (line.startsWith('Git Commit:')) {\n metadata.gitCommit = line.split(':', 2)[1]?.trim() || undefined;\n } else if (line.startsWith('Committer:')) {\n metadata.committer = line.split(':', 2)[1]?.trim() || undefined;\n }\n }\n\n return metadata;\n}\n\nfunction parseTrivyOutput(content: string): TrivyFinding[] {\n const findings: TrivyFinding[] = [];\n const sections = content.split(/\\n(?=[^\\s])/);\n\n let currentFile = '';\n\n for (const section of sections) {\n // Check if this is a file header\n const fileMatch = section.match(/^(.+)\\s+\\((\\w+)\\)\\s*\\n={40}/);\n if (fileMatch) {\n currentFile = fileMatch[1];\n continue;\n }\n\n // Check if this is a finding\n const severityMatch = section.match(/^(CRITICAL|HIGH|MEDIUM|LOW|UNKNOWN):\\s+(.+)/);\n if (severityMatch && currentFile) {\n const severity = severityMatch[1];\n const title = severityMatch[2];\n\n // Extract description\n const descMatch = section.match(/═{40}\\n([\\s\\S]+?)\\n(?:See|─{40})/);\n const description = descMatch ? descMatch[1].trim() : '';\n\n // Extract AVD ID\n const avdMatch = section.match(/See\\s+(https:\\/\\/avd\\.aquasec\\.com\\/[^\\s]+)/);\n const avdId = avdMatch ? avdMatch[1] : undefined;\n\n // Extract line numbers\n const linesMatch = section.match(/─{40}\\n\\s*(.+?):(\\d+)-(\\d+)/);\n const lines = linesMatch ? `${linesMatch[2]}-${linesMatch[3]}` : '';\n\n findings.push({\n file: currentFile,\n severity,\n title,\n description,\n lines,\n avdId,\n });\n }\n }\n\n return findings;\n}\n\nfunction getBackendConfig(config?: Config): TrivyBackendConfig {\n const trivyConfig = config?.getOptionalConfig('trivy');\n return {\n bucketName: trivyConfig?.getOptionalString('bucketName') || 'sanity-trivy-logs',\n cacheTime: trivyConfig?.getOptionalNumber('cacheTime') || 5,\n };\n}\n\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const { logger, config } = options;\n const router = Router();\n router.use(express.json());\n\n const backendConfig = getBackendConfig(config);\n const storage = new Storage();\n const bucketName = backendConfig.bucketName;\n const cache = new CacheManager(backendConfig.cacheTime, logger);\n\n router.get('/health', (_, response) => {\n logger.info('PONG!');\n response.json({\n status: 'ok',\n cache: cache.stats(),\n config: {\n bucketName,\n cacheTime: backendConfig.cacheTime,\n },\n });\n });\n\n // Get all scans\n router.get('/scans', async (_, response) => {\n try {\n const cacheKey = 'all-scans';\n const cached = cache.get<TrivyScanResult[]>(cacheKey);\n\n if (cached) {\n logger.info('Returning cached scans');\n response.json(cached);\n return;\n }\n\n const [files] = await storage.bucket(bucketName).getFiles({\n prefix: 'repos/',\n });\n\n const scans = new Map<string, TrivyScanResult>();\n\n for (const file of files) {\n const pathParts = file.name.split('/');\n if (pathParts.length < 4) continue;\n\n const repo = pathParts[1];\n const branch = pathParts[2];\n const scanId = pathParts[3];\n const fileName = pathParts[4];\n\n const key = `${repo}/${branch}/${scanId}`;\n\n if (!scans.has(key)) {\n scans.set(key, {\n repo,\n branch,\n scanId,\n metadata: {},\n findings: [],\n timestamp: file.metadata.timeCreated\n ? new Date(file.metadata.timeCreated)\n : new Date(),\n });\n }\n\n const scan = scans.get(key)!;\n\n if (fileName === 'trivy-metadata.txt') {\n const [content] = await file.download();\n scan.metadata = parseMetadata(content.toString());\n } else if (fileName === 'trivy-fs-output.txt' || fileName === 'trivy-image-output.txt') {\n const [content] = await file.download();\n const findings = parseTrivyOutput(content.toString());\n scan.findings.push(...findings);\n }\n }\n\n const scanArray = Array.from(scans.values())\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());\n\n cache.set(cacheKey, scanArray);\n response.json(scanArray);\n } catch (error) {\n logger.error('Error fetching scans:', error as Error);\n response.status(500).json({ error: 'Failed to fetch scans' });\n }\n });\n\n // Get scan by ID\n router.get('/scans/:repo/:branch/:scanId', async (request, response) => {\n try {\n const { repo, branch, scanId } = request.params;\n const cacheKey = `scan-${repo}-${branch}-${scanId}`;\n const cached = cache.get<TrivyScanResult>(cacheKey);\n\n if (cached) {\n logger.info(`Returning cached scan: ${cacheKey}`);\n response.json(cached);\n return;\n }\n\n const prefix = `repos/${repo}/${branch}/${scanId}/`;\n\n const [files] = await storage.bucket(bucketName).getFiles({ prefix });\n\n const scan: TrivyScanResult = {\n repo,\n branch,\n scanId,\n metadata: {},\n findings: [],\n timestamp: new Date(),\n };\n\n for (const file of files) {\n const fileName = file.name.split('/').pop();\n\n if (fileName === 'trivy-metadata.txt') {\n const [content] = await file.download();\n scan.metadata = parseMetadata(content.toString());\n } else if (fileName === 'trivy-fs-output.txt' || fileName === 'trivy-image-output.txt') {\n const [content] = await file.download();\n scan.findings.push(...parseTrivyOutput(content.toString()));\n }\n\n if (file.metadata.timeCreated) {\n scan.timestamp = new Date(file.metadata.timeCreated);\n }\n }\n\n cache.set(cacheKey, scan);\n response.json(scan);\n } catch (error) {\n logger.error('Error fetching scan:', error as Error);\n response.status(500).json({ error: 'Failed to fetch scan' });\n }\n });\n\n // Get latest scan for a repo\n router.get('/scans/:repo/latest', async (request, response) => {\n try {\n const { repo } = request.params;\n const cacheKey = `latest-${repo}`;\n const cached = cache.get<TrivyScanResult>(cacheKey);\n\n if (cached) {\n logger.info(`Returning cached latest scan: ${cacheKey}`);\n response.json(cached);\n return;\n }\n\n const prefix = `repos/${repo}/`;\n\n const [files] = await storage.bucket(bucketName).getFiles({ prefix });\n\n const scans = new Map<string, TrivyScanResult>();\n\n for (const file of files) {\n const pathParts = file.name.split('/');\n if (pathParts.length < 5) continue;\n\n const branch = pathParts[2];\n const scanId = pathParts[3];\n const fileName = pathParts[4];\n\n const key = `${repo}/${branch}/${scanId}`;\n\n if (!scans.has(key)) {\n scans.set(key, {\n repo,\n branch,\n scanId,\n metadata: {},\n findings: [],\n timestamp: file.metadata.timeCreated\n ? new Date(file.metadata.timeCreated)\n : new Date(),\n });\n }\n\n const scan = scans.get(key)!;\n\n if (fileName === 'trivy-metadata.txt') {\n const [content] = await file.download();\n scan.metadata = parseMetadata(content.toString());\n } else if (fileName === 'trivy-fs-output.txt' || fileName === 'trivy-image-output.txt') {\n const [content] = await file.download();\n scan.findings.push(...parseTrivyOutput(content.toString()));\n }\n }\n\n const latestScan = Array.from(scans.values())\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())[0];\n\n if (!latestScan) {\n response.status(404).json({ error: 'No scans found for repo' });\n return;\n }\n\n cache.set(cacheKey, latestScan);\n response.json(latestScan);\n } catch (error) {\n logger.error('Error fetching latest scan:', error as Error);\n response.status(500).json({ error: 'Failed to fetch latest scan' });\n }\n });\n\n router.use(errorHandler() as any);\n return router;\n}\n","import {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './service/router';\n\nexport const trivyPlugin = createBackendPlugin({\n pluginId: 'trivy',\n register(env) {\n env.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n },\n async init({ httpRouter, logger, config }) {\n httpRouter.use(\n await createRouter({\n logger,\n config,\n }) as any,\n );\n httpRouter.addAuthPolicy({\n path: '/health',\n allow: 'unauthenticated',\n });\n },\n });\n },\n});\n"],"names":["Router","express","storage","Storage","errorHandler","createBackendPlugin","coreServices"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sanity-labs/backstage-plugin-trivy-backend",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "dist/index.cjs.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"backstage": {
|
|
11
|
+
"role": "backend-plugin"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@backstage/backend-common": "^0.21.0",
|
|
15
|
+
"@backstage/backend-plugin-api": "^0.6.10",
|
|
16
|
+
"@backstage/catalog-model": "^1.4.3",
|
|
17
|
+
"@backstage/config": "^1.1.1",
|
|
18
|
+
"@google-cloud/storage": "^7.7.0",
|
|
19
|
+
"express": "^4.18.2",
|
|
20
|
+
"express-promise-router": "^4.1.1",
|
|
21
|
+
"node-fetch": "^2.7.0",
|
|
22
|
+
"winston": "^3.11.0",
|
|
23
|
+
"yn": "^4.0.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@backstage/cli": "^0.25.0",
|
|
27
|
+
"@types/express": "*",
|
|
28
|
+
"@types/jest": "^30.0.0",
|
|
29
|
+
"@types/node": "*",
|
|
30
|
+
"@types/webpack-env": "^1.18.8"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"start": "backstage-cli package start",
|
|
37
|
+
"build": "backstage-cli package build",
|
|
38
|
+
"lint": "backstage-cli package lint",
|
|
39
|
+
"test": "backstage-cli package test",
|
|
40
|
+
"clean": "backstage-cli package clean",
|
|
41
|
+
"tsc": "tsc --project tsconfig.json --declaration --emitDeclarationOnly --outDir dist-types/src"
|
|
42
|
+
}
|
|
43
|
+
}
|