Skip to content

Instantly share code, notes, and snippets.

@esseti
Last active September 22, 2023 10:53
Show Gist options
  • Save esseti/f2ba0a23608b06a2c2ad3d6616c9cf11 to your computer and use it in GitHub Desktop.
Save esseti/f2ba0a23608b06a2c2ad3d6616c9cf11 to your computer and use it in GitHub Desktop.
Clair report json to html
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport">
<!-- Bootstrap CSS -->
<link crossorigin="anonymous" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" rel="stylesheet">
<style>
.row-Defcon1 {
background-color: rgba(192, 57, 43,0.5)
}
.row-Critical {
background-color: rgba(231, 76, 60,0.5);
}
.row-High {
background-color: rgba(255, 118, 117,0.5);
}
.row-Medium {
background-color: rgba(230, 126, 34,0.2);
}
.row-Low {
background-color:rgba(241, 196, 15,0.2);
}
.row-Negligible {
background-color: rgba(75, 192, 192, 0.5);
}
.row-Unknown {
background-color: rgba(75, 75, 75, 0.5);
}
.fixed{
background-color: red;
color:white;
font-weight: bold;
}
</style>
<title>{{image}}</title>
</head>
<body>
<div class="container-fluid">
<div class="row pt-5 pb-4">
<div class="col-4">
<h1>Project {{image}}, {{unapproved|length}} vulnerabilities</h1>
<table class="table table-sm">
<thead class="">
<tr>
<th scope="col-6">Top 3 NameSpace</th>
<th scope="col">#</th>
</tr>
</thead>
{% for k,v in causes.items()|sort(attribute='1', reverse=True) %}
{% if loop.index<4 %}
<tr>
<td>{{k}}</td>
<td>{{v}}</td>
</tr>
{%endif %}
{% endfor %}
</table>
<table class="table table-sm">
<thead class="">
<tr>
<th scope="col-6">Top 5 by Name</th>
<th scope="col">#</th>
</tr>
</thead>
{% for k,v in causes_details.items()|sort(attribute='1', reverse=True) %}
{% if loop.index<6 %}
<tr>
<td>{{k}}</td>
<td>{{v}}</td>
</tr>
{%endif %}
{% endfor %}
</table>
</div>
<div class="col-8">
<canvas id="myChartBar" height="100px"></canvas>
</div>
</div>
<div class="row">
<div class="col">
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">version</th>
<th scope="col">code</th>
<th scope="col">namespace</th>
<th scope="col">desc</th>
<th scope="col">link</th>
<th scope="col">severity</th>
<th scope="col">fixedby</th>
</tr>
</thead>
{% for vul in vulnerabilities %}
<tr class="row-{{vul.severity}}">
<th scope="row">{{loop.index}}</th>
<td>{{vul.featurename}}</td>
<td>{{vul.featureversion}}</td>
<td>{{vul.vulnerability}}</td>
<td>{{vul.namespace}}</td>
<td>{{vul.description}}</td>
<td><a href="{{vul.link}}">{{vul.link}}</a></td>
<td>{{vul.severity}}</td>
<td class="{%if vul.fixedby %}fixed{%endif%}">{{vul.fixedby}}</td>
</tr>
{%endfor%}
</table>
</div>
</div>
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script crossorigin="anonymous" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script crossorigin="anonymous" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script crossorigin="anonymous" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.js"></script>
<script>
var ctxBar = document.getElementById('myChartBar');
var myChartBar = new Chart(ctxBar, {
type: 'bar',
data: {
labels: ['Defcon1', 'Critical', 'High', 'Medium', 'Low', 'Negligible', 'Unknown'],
datasets: [{
data: {{stats}},
backgroundColor: [
'rgba(192, 57, 43,0.2)',
'rgba(231, 76, 60,.2)',
'rgba(255, 118, 117,0.2)',
'rgba(230, 126, 34,0.2)',
'rgba(241, 196, 15,0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(75, 75, 75, 0.2)',
],
borderColor: [
'rgba(192, 57, 43,1.0)',
'rgba(231, 76, 60,1.0)',
'rgba(255, 118, 117,1.0)',
'rgba(230, 126, 34,1.0)',
'rgba(241, 196, 15,1.0)',
'rgba(75, 192, 192, 1)',
'rgba(75, 75, 75, 1)',
],
borderWidth: 1
}]},options: {
legend: {
display: false
},scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
})
;
</script>
</body>
</html>
"""
Stefano Tranquillini stefanotranquillini.com
Renders the json report of clairtl (the on of gitlab pipelines) in html
"""
import json
from datetime import date, datetime
from pathlib import Path
from jinja2 import Environment, FileSystemLoader, select_autoescape
env = Environment(
loader=FileSystemLoader(searchpath="./"),
autoescape=select_autoescape(['html', 'xml'])
)
def _compute_stats(ctx):
count = {
"Defcon1": 0,
"Critical": 0,
"High": 0,
"Medium": 0,
"Low": 0,
"Negligible": 0,
"Unknown": 0
}
for vul in ctx['vulnerabilities']:
count[vul['severity']] += 1
return [count['Defcon1'], count['Critical'],
count['High'], count['Medium'],
count['Low'], count['Negligible'],
count['Unknown']]
def _compute_causes(ctx, field):
count = {
}
for vul in ctx['vulnerabilities']:
count[vul[field]] = count[vul[field]] + 1 if count.get(vul[field], None) else 1
return count
def read(filename):
p = Path(filename)
return json.loads(p.read_text())
def render(ctx, output):
template = env.get_template('base.html')
ctx['stats'] = _compute_stats(ctx)
ctx['causes'] = _compute_causes(ctx, 'namespace')
ctx['causes_details'] = _compute_causes(ctx, 'featurename')
res = template.render(ctx)
out=output or f"out_{datetime.now().isoformat()}.html"
p = Path(out)
p.write_text(res)
print(f"Done {out}")
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("input", help="Input file, the json created by clair")
parser.add_argument("--output", help="file to write (html)")
args = parser.parse_args()
ctx = read(args.input)
render(ctx, args.output or None)
python clair.py <json_report> --output filename.html
"""
Stefano Tranquillini stefanotranquillini.com
Renders the json report of clairtl (the on of gitlab pipelines) in html
New report support
"""
import json
from datetime import date, datetime
from pathlib import Path
from jinja2 import Environment, FileSystemLoader, select_autoescape
env = Environment(
loader=FileSystemLoader(searchpath="./"),
autoescape=select_autoescape(['html', 'xml'])
)
def _compute_stats(ctx):
count = {
"Defcon1": 0,
"Critical": 0,
"High": 0,
"Medium": 0,
"Low": 0,
"Negligible": 0,
"Unknown": 0
}
for vul in ctx['vulnerabilities']:
count[vul['severity']] += 1
return [count['Defcon1'], count['Critical'],
count['High'], count['Medium'],
count['Low'], count['Negligible'],
count['Unknown']]
def _compute_causes_name(ctx):
count = {
}
for vul in ctx['vulnerabilities']:
el = vul['location']['dependency']['package']['name']
count[el] = count[el] + 1 if count.get(el, None) else 1
return count
def read(filename):
p = Path(filename)
return json.loads(p.read_text())
def _get_severity_number(severity):
print(severity)
if severity == "Defcon1":
return 7
elif severity == "Critical":
return 6
elif severity == "High":
return 5
elif severity == "Medium":
return 4
elif severity == "Low":
return 3
elif severity == "Negligible":
return 2
elif severity == "Unknown":
return 1
else:
return 0
def fix_ctx(ctx):
# add title for url, we need shorter text
from urllib.parse import urlparse
vulnerabilites = []
for vul in ctx['vulnerabilities']:
new_link = []
for link in vul['links']:
link['text'] = urlparse(link['url']).netloc
new_link.append(link)
vul['links'] = new_link
# use text only if solution is provided
vul['solution'] = vul['solution'] if vul['solution'] != "No solution provided" else ""
# add a number for serveryt, for ordering
vul['severity_number'] = _get_severity_number(vul['severity'])
return ctx
def render(ctx, output):
template = env.get_template('render.html')
ctx['stats'] = _compute_stats(ctx)
ctx['causes'] = _compute_causes_name(ctx)
ctx = fix_ctx(ctx)
ctx['vulnerabilities'] = sorted(
ctx['vulnerabilities'], key=lambda x: x['severity_number'], reverse=True)
res = template.render(ctx)
out = output or f"out_{datetime.now().isoformat()}.html"
p = Path(out)
p.write_text(res)
print(f"Done {out}")
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("input", help="Input file, the json created by clair")
parser.add_argument("--output", help="file to write (html)")
args = parser.parse_args()
ctx = read(args.input)
render(ctx, args.output or None)
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport">
<!-- Bootstrap CSS -->
<link crossorigin="anonymous" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" rel="stylesheet">
<style>
.row-Defcon1 {
background-color: rgba(192, 57, 43,0.5)
}
.row-Critical {
background-color: rgba(231, 76, 60,0.5);
}
.row-High {
background-color: rgba(255, 118, 117,0.5);
}
.row-Medium {
background-color: rgba(230, 126, 34,0.2);
}
.row-Low {
background-color:rgba(241, 196, 15,0.2);
}
.row-Negligible {
background-color: rgba(75, 192, 192, 0.5);
}
.row-Unknown {
background-color: rgba(75, 75, 75, 0.5);
}
.fixed{
background-color: red;
color:white;
font-weight: bold;
}
</style>
<title>{{image}}</title>
</head>
<body>
<div class="container-fluid">
<div class="row pt-5 pb-4">
<div class="col-4">
<h1>Project {{image}}, {{unapproved|length}} vulnerabilities</h1>
<table class="table table-sm">
<thead class="">
<tr>
<th scope="col-6">Top 10 </th>
<th scope="col">#</th>
</tr>
</thead>
{% for k,v in causes.items()|sort(attribute='1', reverse=True) %}
{% if loop.index<11 %}
<tr>
<td>{{k}}</td>
<td>{{v}}</td>
</tr>
{%endif %}
{% endfor %}
</table>
</div>
<div class="col-8">
<canvas id="myChartBar" height="100px"></canvas>
</div>
</div>
<div class="row">
<div class="col">
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">#</th>
<th scope="col">Identifiers</th>
<th scope="col">Package</th>
<th scope="col">Version</th>
<th scope="col">desc</th>
<th scope="col">link</th>
<th scope="col">severity</th>
<th scope="col">fixedby</th>
</tr>
</thead>
{% for vul in vulnerabilities %}
<tr class="row-{{vul.severity}}">
<th scope="row">{{loop.index}}</th>
<td> {% for identifier in vul.identifiers %}
<a href="{{identifier.url}}">{{identifier.name}}</a>
{% endfor %}
</td>
<td>{{vul.location.dependency.package.name}}</td>
<td>{{vul.location.dependency.version}}</td>
<td>{{vul.description}}</td>
<td>
{% for link in vul.links %}
<a href="{{link.url}}">{{link.text}}</a><br/>
{% endfor %}
</td>
<td>{{vul.severity}}</td>
<td class="{% if vul.solution %}fixed{%endif%}">{{vul.solution}}</td>
</tr>
{%endfor%}
</table>
</div>
</div>
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script crossorigin="anonymous" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script crossorigin="anonymous" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script crossorigin="anonymous" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.js"></script>
<script>
var ctxBar = document.getElementById('myChartBar');
var myChartBar = new Chart(ctxBar, {
type: 'bar',
data: {
labels: ['Defcon1', 'Critical', 'High', 'Medium', 'Low', 'Negligible', 'Unknown'],
datasets: [{
data: {{stats}},
backgroundColor: [
'rgba(192, 57, 43,0.2)',
'rgba(231, 76, 60,.2)',
'rgba(255, 118, 117,0.2)',
'rgba(230, 126, 34,0.2)',
'rgba(241, 196, 15,0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(75, 75, 75, 0.2)',
],
borderColor: [
'rgba(192, 57, 43,1.0)',
'rgba(231, 76, 60,1.0)',
'rgba(255, 118, 117,1.0)',
'rgba(230, 126, 34,1.0)',
'rgba(241, 196, 15,1.0)',
'rgba(75, 192, 192, 1)',
'rgba(75, 75, 75, 1)',
],
borderWidth: 1
}]},options: {
legend: {
display: false
},scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
})
;
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment