Create viewer for SBOM and VEX data.
[oam.git] / code / container-analysis / viewer / routes / index.js
1 /*
2  * Copyright 2023 highstreet technologies and others
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 const express = require('express');
18 const router = express.Router();
19 const fs = require('fs');
20 const path = require('path');
21
22 const directoryPath = 'public/files';
23 const fileExtensions = ['.sbom.spdx.json', '.vulnerabilities.vex.json'];
24 const imageList = [];
25 const severities = [
26   { name: 'critical', color: 'red' },
27   { name: 'high', color: 'orange' },
28   { name: 'medium', color: 'yellow' },
29   { name: 'low', color: 'lightblue' },
30   { name: 'unknown', color: 'lightgrey' },
31   { name: 'none', color: 'lightgrey' }
32 ];
33 const getScaleFromTotal = (total) => {
34   let scale = Math.round(total / 120);
35   if (scale < 4) scale = 4;
36   if (scale > 10) scale = 10;
37   return scale;
38 }
39
40 /* GET home page. */
41 router.get('/', function (req, res, next) {
42
43   fs.readdir(directoryPath, function (err, files) {
44     if (err) {
45       console.error('Error reading directory:', err);
46       return res.status(500).send('Error reading directory (' + directoryPath + ')');
47     }
48
49     const fileList = files.filter(file => file.charAt(0) !== '.').map(file => ({
50       name: file,
51       type: file.endsWith(fileExtensions[0]) ? 'sbom' : 'vex',
52       url: path.join('files', file)
53     }));
54
55     const vulnerabilities = (list) => {
56       const result = {};
57       const data = severities.map((severity, index) => {
58         const count = list.filter((vul) => {
59           return vul.ratings[0].severity === severity.name;
60         }).length
61         result[severity.name] = count;
62         return { ...severity, count }
63       });
64       const total = data.reduce((sum, d) => sum + d.count, 0);
65       result.scale = getScaleFromTotal(total);
66
67       let angle = 0;
68       const slices = data.map((d, i) => {
69         const startAngle = i === 0 ? 0 : angle
70         angle = startAngle + 2 * Math.PI * (d.count / total)
71         const endAngle = angle;
72         return { ...d, startAngle, endAngle, arc: endAngle - startAngle > Math.PI ? 1 : 0 }
73       });
74       result.slices = slices;
75       result.count = list.length;
76       return result;
77     };
78
79     const analyser = (type, file) => {
80       const result = {
81         url: file
82       };
83       switch (type) {
84         case 'sbom':
85           const sbomRawData = fs.readFileSync('./public/' + file);
86           const sbom = JSON.parse(sbomRawData);
87           result.created = new Date(sbom.creationInfo.created).toISOString().replace('.000Z', 'Z');
88           result.packages = sbom.packages.length;
89           result.files = sbom.files.length;
90           break;
91         case 'vex':
92           const vexRawData = fs.readFileSync('./public/' + file);
93           const vex = JSON.parse(vexRawData);
94           result.created = new Date(vex.metadata.timestamp).toISOString().replace('.000Z', 'Z');
95           result.components = vex.components.length;
96           result.vulnerabilities = vulnerabilities(vex.vulnerabilities);
97           break;
98         default:
99           result.type = type;
100       }
101       return result;
102     };
103
104     fileList.forEach((file) => {
105       const imageInfo = file.name.split(':');
106       const imageVersion = file.type === 'sbom' ? imageInfo[1].substring(0, imageInfo[1].indexOf(fileExtensions[0])) : imageInfo[1].substring(0, imageInfo[1].indexOf(fileExtensions[1]));
107       const exists = imageList.filter((image) => {
108         return image.name === imageInfo[0];
109       })
110       if (exists.length === 0) {
111         const imageObj = {
112           name: imageInfo[0],
113           version: imageVersion
114         }
115         imageObj[file.type] = analyser(file.type, file.url);
116         imageList.push(imageObj);
117       } else {
118         exists[0][file.type] = analyser(file.type, file.url);
119       }
120     });
121
122     res.render('index', {
123       title: 'Container Analysis',
124       date: new Date().toISOString(),
125       images: imageList
126     });
127   });
128
129 });
130 module.exports = router;