Generate an HTML Trivy Report Ordered by Severity

Trivy is an Open Source tools for scanning software artifacts, and image vulnerabilities, which is maintained by Aqua Security. We can also generate Trivy reports and displaying the list of vulnerabilities as an HTML report. We can also create our own custom HTML template that would suitable for our needs.

Below is a sample HTML report that we use for sorting vulnerabilities based on its severity level,

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
{{- if . }}
    <style>
      * {
        font-family: Arial, Helvetica, sans-serif;
      }
      h1 {
        text-align: center;
      }
      .group-header th {
        font-size: 200%;
      }
      .sub-header th {
        font-size: 150%;
      }
      table, th, td {
        border: 1px solid black;
        border-collapse: collapse;
        white-space: nowrap;
        padding: .3em;
      }
      table {
        margin: 0 auto;
      }
      .severity {
        text-align: center;
        font-weight: bold;
        color: #fafafa;
      }
      .severity-LOW .severity { background-color: #5fbb31; }
      .severity-MEDIUM .severity { background-color: #e9c600; }
      .severity-HIGH .severity { background-color: #ff8800; }
      .severity-CRITICAL .severity { background-color: #e40000; }
      .severity-UNKNOWN .severity { background-color: #747474; }
      .severity-LOW { background-color: #5fbb3160; }
      .severity-MEDIUM { background-color: #e9c60060; }
      .severity-HIGH { background-color: #ff880060; }
      .severity-CRITICAL { background-color: #e4000060; }
      .severity-UNKNOWN { background-color: #74747460; }
      table tr td:first-of-type {
        font-weight: bold;
      }
      .links a,
      .links[data-more-links=on] a {
        display: block;
      }
      .links[data-more-links=off] a:nth-of-type(1n+5) {
        display: none;
      }
      a.toggle-more-links { cursor: pointer; }
    </style>
    <title>{{- escapeXML ( index . 0 ).Target }} - Trivy Report - {{ now }} </title>
    <script>
      window.onload = function() {
        document.querySelectorAll('td.links').forEach(function(linkCell) {
          var links = [].concat.apply([], linkCell.querySelectorAll('a'));
          [].sort.apply(links, function(a, b) {
            return a.href > b.href ? 1 : -1;
          });
          links.forEach(function(link, idx) {
            if (links.length > 3 && 3 === idx) {
              var toggleLink = document.createElement('a');
              toggleLink.innerText = "Toggle more links";
              toggleLink.href = "#toggleMore";
              toggleLink.setAttribute("class", "toggle-more-links");
              linkCell.appendChild(toggleLink);
            }
            linkCell.appendChild(link);
          });
        });
        document.querySelectorAll('a.toggle-more-links').forEach(function(toggleLink) {
          toggleLink.onclick = function() {
            var expanded = toggleLink.parentElement.getAttribute("data-more-links");
            toggleLink.parentElement.setAttribute("data-more-links", "on" === expanded ? "off" : "on");
            return false;
          };
        });
      };
	  
	  window.addEventListener('DOMContentLoaded', () => {	  
			const severityOrder = {
			  "CRITICAL": 1,
			  "HIGH": 2,
			  "MEDIUM": 3,
			  "LOW": 4
			};

			const table = document.getElementById("myTable");
			const tbody = table.tBodies[0];
			const rows = Array.from(tbody.rows);
			
			const columnIndex = 2; 
			
			rows.sort((a, b) => {
				  const cellA = a.cells[columnIndex];
				  const cellB = b.cells[columnIndex];

				  if (!cellA || !cellB) {					
					return 0; // Skip sort if data is malformed
				  }
				  
				  if (cellA.textContent.trim().toUpperCase()=='SEVERITY' || cellB.textContent.trim().toUpperCase()=='SEVERITY') {		
					return 0; // Skip sort if data is malformed
				  }

				  const sevA = cellA.textContent.trim().toUpperCase();
				  const sevB = cellB.textContent.trim().toUpperCase();

				  const orderA = severityOrder[sevA] ?? 999;
				  const orderB = severityOrder[sevB] ?? 999;

				  return orderA - orderB;
			});

			rows.forEach(row => tbody.appendChild(row));
	  });
	
    </script>
  </head>
  <body>
    <h1>{{- escapeXML ( index . 0 ).Target }} - Trivy Report - {{ now }}</h1>
    <table id="myTable">
    {{- range . }}
      <tr class="group-header"><th colspan="6">{{ .Type | toString | escapeXML }}</th></tr>
      {{- if (eq (len .Vulnerabilities) 0) }}
      <tr><th colspan="6">No Vulnerabilities found</th></tr>
      {{- else }}
      <tr class="sub-header">
        <th>Package</th>
        <th>Vulnerability ID</th>
        <th>Severity</th>
        <th>Installed Version</th>
        <th>Fixed Version</th>
        <th>Links</th>
      </tr>
        {{- range .Vulnerabilities }}
      <tr class="severity-{{ escapeXML .Vulnerability.Severity }}">
        <td class="pkg-name">{{ escapeXML .PkgName }}</td>
        <td>{{ escapeXML .VulnerabilityID }}</td>
        <td class="severity">{{ escapeXML .Vulnerability.Severity }}</td>
        <td class="pkg-version">{{ escapeXML .InstalledVersion }}</td>
        <td>{{ escapeXML .FixedVersion }}</td>
        <td class="links" data-more-links="off">
          {{- range .Vulnerability.References }}
          <a href={{ escapeXML . | printf "%q" }}>{{ escapeXML . }}</a>
          {{- end }}
        </td>
      </tr>
        {{- end }}
      {{- end }}
      {{- if (eq (len .Misconfigurations ) 0) }}
      <tr><th colspan="6">No Misconfigurations found</th></tr>
      {{- else }}
      <tr class="sub-header">
        <th>Type</th>
        <th>Misconf ID</th>
        <th>Check</th>
        <th>Severity</th>
        <th>Message</th>
      </tr>
        {{- range .Misconfigurations }}
      <tr class="severity-{{ escapeXML .Severity }}">
        <td class="misconf-type">{{ escapeXML .Type }}</td>
        <td>{{ escapeXML .ID }}</td>
        <td class="misconf-check">{{ escapeXML .Title }}</td>
        <td class="severity">{{ escapeXML .Severity }}</td>
        <td class="link" data-more-links="off"  style="white-space:normal;">
          {{ escapeXML .Message }}
          <br>
            <a href={{ escapeXML .PrimaryURL | printf "%q" }}>{{ escapeXML .PrimaryURL }}</a>
          </br>
        </td>
      </tr>
        {{- end }}
      {{- end }}
    {{- end }}
    </table>
{{- else }}
  </head>
  <body>
    <h1>Trivy Returned Empty Report</h1>
{{- end }}
  </body>
</html>

Save it as “html.tpl”, and run the below command

$ trivy image --scanners vuln  my-image:latest --format template \
       --template "@/tmp/html.tpl" -o /tmp/my-image-vulnerabilities.html

It shall generate a report like below,

Java 21 and Maven Error PKIX when Connecting to Self Signed Nexus Registry

Had this error while doing a maven build

PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

With a complete error log,

$ mvn clean package -s settings.xml

.......

[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[ERROR] Unresolveable build extension: Plugin com.redhat.quarkus.platform:quarkus-maven-plugin:3.15.3.SP1-redhat-00002 or one of its dependencies could not be resolved: Failed to collect dependencies at com.redhat.quarkus.platform:quarkus-maven-plugin:jar:3.15.3.SP1-redhat-00002 -> io.quarkus:quarkus-bootstrap-maven-resolver:jar:3.15.3.redhat-00004 -> io.smallrye.beanbag:smallrye-beanbag-maven:jar:1.5.2.redhat-00001 -> io.smallrye.beanbag:smallrye-beanbag-sisu:jar:1.5.2.redhat-00001 -> javax.inject:javax.inject:jar:1.0.0.redhat-00014 @
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: com.redhat.quarkus.platform:quarkus-camel-bom:pom:3.15.3.SP1-redhat-00002 (present, but unavailable): Could not transfer artifact com.redhat.quarkus.platform:quarkus-camel-bom:pom:3.15.3.SP1-redhat-00002 from/to mvn-repository (https://nexus/maven-group/): PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target @ line 32, column 25

But somehow the previous solution on my previous post is not working. Maybe because of a different Java version or Maven version. So I need to find another solution, and this is what was working on my end.

First we need to take the self-signed certificate that belongs to the remote Nexus instance

$ echo "" | openssl s_client -connect nexus:8443  -showcerts 2>/dev/null | openssl x509 -out nexus.crt

Import it into our key

$ keytool -import -alias mycert -keystore /tmp/customcacerts -file nexus.crt -storepass changeit -noprompt

And use it on our Maven build

$ ./mvnw -Djavax.net.ssl.trustStore=/tmp/customcacerts \
        -Djavax.net.ssl.trustStorePassword=changeit clean install \ 
		-s settings.xml 

Everything is working well after that.

Maven Error PKIX When Connecting to Self Signed Nexus Repository

Had this error while doing a maven build

PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

With a complete error log,

$ mvn clean package -s settings.xml

.......

[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[ERROR] Unresolveable build extension: Plugin com.redhat.quarkus.platform:quarkus-maven-plugin:3.15.3.SP1-redhat-00002 or one of its dependencies could not be resolved: Failed to collect dependencies at com.redhat.quarkus.platform:quarkus-maven-plugin:jar:3.15.3.SP1-redhat-00002 -> io.quarkus:quarkus-bootstrap-maven-resolver:jar:3.15.3.redhat-00004 -> io.smallrye.beanbag:smallrye-beanbag-maven:jar:1.5.2.redhat-00001 -> io.smallrye.beanbag:smallrye-beanbag-sisu:jar:1.5.2.redhat-00001 -> javax.inject:javax.inject:jar:1.0.0.redhat-00014 @
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: com.redhat.quarkus.platform:quarkus-camel-bom:pom:3.15.3.SP1-redhat-00002 (present, but unavailable): Could not transfer artifact com.redhat.quarkus.platform:quarkus-camel-bom:pom:3.15.3.SP1-redhat-00002 from/to mvn-repository (https://nexus/maven-group/): PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target @ line 32, column 25
[ERROR] 'dependencies.dependency.version' for org.apache.camel.quarkus:camel-quarkus-direct:jar is missing. @ line 45, column 21
[ERROR] 'dependencies.dependency.version' for org.apache.camel.quarkus:camel-quarkus-jackson:jar is missing. @ line 49, column 21
[ERROR] 'dependencies.dependency.version' for org.apache.camel.quarkus:camel-quarkus-rest-openapi:jar is missing. @ line 53, column 21
[ERROR] 'dependencies.dependency.version' for org.apache.camel.quarkus:camel-quarkus-rest:jar is missing. @ line 57, column 21
 @
[ERROR] The build could not read 1 project -> [Help 1]

Workaround is quite simple,

$ mvn -Dmaven.wagon.http.ssl.insecure=true clean package -s settings.xml

Script to Generate Series of Thread Dump

There are times when we want to see which threads are blocking our requests, and generating a thread dump is one way to find it out. The thing is sometimes we need to create a series of thread dumps, that’s why i have this script to do it for me. Create a file with the name of “jstack.sh”, with below script as its content

# number of cycles.
LOOP=3
# seconds between cycles.
INTERVAL=10

for ((i=1; i <= $LOOP; i++))
do
   _now=$(date)
   echo "\n \n ${_now}" >> cpu.out
   top -l 1 -o cpu -pid $1 >> cpu.out
   echo "\n \n ${_now}" >> tdump.out
   jstack -l $1 >> tdump.out
   echo "thread dump #" $i
   if [ $i -lt $LOOP ]; then
      echo "Sleeping..."
      sleep $INTERVAL
   fi
done

Repository for above script can be found below,

https://github.com/edwin/java-thread-dump

Recovery Proyek Renovasi yang Mangkrak

Baru aja kemarin selesai melakukan renovasi rumah tinggal, dan mungkin melewati salah satu proses yang paling bikin sakit kepala karena kebanyakan drama. Dan semua berawal dari memilih kontraktor yang kurang tepat.

Dan seperti hal-nya project management IT, recovery proyek konstruksi bisa jadi tidak jauh berbeda. Kita akan fokus ke 3 hal yang paling penting yaitu Budget, Timeline, dan Output. Tapi sebelum kita fokus ke recovery, ada baiknya kita fokus dulu ke kontraktor sebelumnya yang pekerjaannya tidak maksimal.

Seperti yang sudah ditulis di post sebelumnya, kontraktor bangunan gw menghilang begitu saja tanpa kabar sama sekali. Meninggalkan banyak pekerjaan yang belum selesai, tanpa sama sekali menunjukkan itikad baik.

Oleh karena itu selain fokus ke recovery, gw juga fokus ke konsekuensi untuk kontraktor supaya ada efek jera dan tidak mengulangi kesalahan yang sama lagi untuk customer yang lain.

Okay lanjut, terkait budgeting tentunya lumayan bikin kepala pusing karena sekarang harus mikirin budget buat melanjutkan pekerjaan dari kontraktor. Dan pertama yang gw lakukan adalah inventarisasi apa pekerjaan yang sudah selesai, yang selesai namun tidak sempurna, dan apa yang sama sekali belum dilakukan oleh kontraktor sebelumnya.

Setelah selesai inventarisasi, selanjutnya adalah cari kontraktor baru atau pekerja bangunan (tukang) yang terpercaya dan bisa diandalkan untuk melanjutkan pekerjaan ini. Untungnya gw punya kenalan tukang harian yang sudah lama bantu di rumah gw. Gw minta beliau untuk melanjutkan pekerjaan yang berantakan, sekaligus minta hitung estimasi budget berapa. Budgeting menjadi sangat dinamis karena banyak pekerjaan yang harus dilakukan dan dengan rate tukang yang harian. Selain dari budgeting tukang, yang harus dihitung juga adalah budgeting untuk material. Terutama untuk pekerjaan finishing seperti granite lantai dan kitchen set.

Estimasi timeline juga perlu dilakukan untuk menghitung berapa lama waktu yang dibutuhkan untuk menyelesaikan pekerjaan yang ditinggalkan oleh kontraktor. Dari estimasi awal, kontraktor untuk butuh waktu 2 bulan untuk mengerjakan pekerjaan sesuai dengan kontrak yang sudah disepakati. Namun karena kontraktor menghilang begitu saja, timeline pekerjaan bisa jadi molor sangat jauh dari rencana awal. Sehingga dari rencana awal 2 bulan, bisa molor menjadi 9 bulan hahaa.

Untuk output pekerjaan juga menjadi sangat melebar, apalagi dikarenakan banyak referensi dari Instagram dan banyak inspirasi karena sering jalan-jalan ke toko bahan bangunan. Yang tadinya renovasi sederhana untuk 1 lantai saja, bisa berubah menjadi renovasi total semua bagian rumah. Namun pastikan juga bahwa pekerjaan yang diinginkan, sesuai dengan budget dan timeline yang dimiliki.

Overall, budget dan timeline untuk recovery dan finishing malah lebih besar daripada budget dan timeline yang yang diestimasikan oleh kontraktor sebelumnya. Contohnya adalah budget untuk finishing ternyata bisa hampir dua kali lipat dibandingkan budget struktur fisik, itu pun belum menghitung biaya untuk furniture dan peralatan elektronik lain seperti AC atau Kulkas.

Kesimpulan.
Always have backup plan, and backup plan for your backup plan. Selalu miliki cadangan budget yang bisa digunakan apabila terjadi “force majeur” atau kendala ketika implementasi, serta spare waktu yang agak lumayan. Apalagi jika bekerjasama dengan kontraktor yang belum punya nama atau reputasi. Usahakan juga untuk punya “advisor” atau orang yang bisa dipercaya untuk mengawal jalannya project, terutama apabila memiliki limitasi knowledge tentang konstruksi.