Skip to content

Commit

Permalink
Add exposed secrets dashboard (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
Starttoaster committed Apr 30, 2024
1 parent 4729041 commit e027cad
Show file tree
Hide file tree
Showing 10 changed files with 575 additions and 4 deletions.
24 changes: 24 additions & 0 deletions internal/kube/exposedsecretreport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package kube

import (
"context"

"github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
)

const exposedSecretReportsResource = "exposedsecretreports"

// GetExposedSecretReportList retrieves all resources of type exposedsecretreports in all namespaces.
func GetExposedSecretReportList() (*v1alpha1.ExposedSecretReportList, error) {
var list v1alpha1.ExposedSecretReportList
err := client.
Get().
Resource(exposedSecretReportsResource).
Do(context.TODO()).
Into(&list)
if err != nil {
return nil, err
}

return &list, nil
}
91 changes: 87 additions & 4 deletions internal/web/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
clusterrolesview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/clusterroles"
configauditview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/configaudit"
configauditsview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/configaudits"
exposedsecretview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/exposedsecret"
exposedsecretsview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/exposedsecrets"
imageview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/image"
imagesview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/images"
roleview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/role"
Expand All @@ -27,14 +29,16 @@ func Start(port string) error {
mux := http.NewServeMux()
mux.HandleFunc("/", imagesHandler)
mux.HandleFunc("/image", imageHandler)
mux.HandleFunc("/roles", rolesHandler)
mux.HandleFunc("/role", roleHandler)
mux.HandleFunc("/clusterroles", clusterrolesHandler)
mux.HandleFunc("/clusterrole", clusterroleHandler)
mux.HandleFunc("/configaudits", configauditsHandler)
mux.HandleFunc("/configaudit", configauditHandler)
mux.HandleFunc("/clusteraudits", clusterauditsHandler)
mux.HandleFunc("/clusteraudit", clusterauditHandler)
mux.HandleFunc("/clusterroles", clusterrolesHandler)
mux.HandleFunc("/clusterrole", clusterroleHandler)
mux.HandleFunc("/exposedsecrets", exposedsecretsHandler)
mux.HandleFunc("/exposedsecret", exposedsecretHandler)
mux.HandleFunc("/roles", rolesHandler)
mux.HandleFunc("/role", roleHandler)
mux.Handle("/static/", http.FileServer(http.FS(content.Static)))
return http.ListenAndServe(fmt.Sprintf(":%s", port), mux)
}
Expand Down Expand Up @@ -441,3 +445,82 @@ func clusterauditHandler(w http.ResponseWriter, r *http.Request) {
return
}
}

func exposedsecretsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFS(content.Static, "static/exposedsecrets.html", "static/sidebar.html"))
if tmpl == nil {
log.Logger.Error("encountered error parsing exposed secrets html template")
http.Error(w, "Internal Server Error, check server logs", http.StatusInternalServerError)
return
}

data, err := kube.GetExposedSecretReportList()
if err != nil {
log.Logger.Error("error getting ExposedSecretReports", "error", err.Error())
return
}
imageData := exposedsecretsview.GetView(data)

err = tmpl.Execute(w, imageData)
if err != nil {
log.Logger.Error("encountered error executing exposed secrets html template", "error", err)
http.Error(w, "Internal Server Error, check server logs", http.StatusInternalServerError)
return
}
}

func exposedsecretHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFS(content.Static, "static/exposedsecret.html", "static/sidebar.html"))
if tmpl == nil {
log.Logger.Error("encountered error parsing exposed secret html template")
http.Error(w, "Internal Server Error, check server logs", http.StatusInternalServerError)
return
}

// Parse URL query params
q := r.URL.Query()

// Check query params -- 404 if required params not passed
imageName := q.Get("image")
if imageName == "" {
log.Logger.Error("image name query param missing from request")
http.NotFound(w, r)
return
}
imageDigest := q.Get("digest")
if imageDigest == "" {
log.Logger.Error("image digest query param missing from request")
http.NotFound(w, r)
return
}
severity := q.Get("severity")

// Get secret reports
data, err := kube.GetExposedSecretReportList()
if err != nil {
log.Logger.Error("error getting ExposedSecretReports", "error", err.Error())
return
}

// Get image view from reports
view, found := exposedsecretview.GetView(data, exposedsecretview.Filters{
Name: imageName,
Digest: imageDigest,
Severity: severity,
})

// If the selected image from query params was not found, 404
if !found {
log.Logger.Error("image name and digest query params did not produce a valid result from scraped data", "image", imageName, "digest", imageDigest)
http.NotFound(w, r)
return
}

// Execute html template
err = tmpl.Execute(w, view)
if err != nil {
log.Logger.Error("encountered error executing exposed secret html template", "error", err)
http.Error(w, "Internal Server Error, check server logs", http.StatusInternalServerError)
return
}
}
97 changes: 97 additions & 0 deletions internal/web/views/exposedsecret/image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package image

import (
"fmt"
"sort"
"strings"

"github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
)

// Filters contains the supported filters for the image view
type Filters struct {
Name string
Digest string

// optional filters
Severity string
}

// GetView converts some report data to the /exposedsecret view
// returns view data and "true" if the image was found in the report list
func GetView(data *v1alpha1.ExposedSecretReportList, filters Filters) (View, bool) {
for _, item := range data.Items {
itemImageName := getImageNameFromLabels(item.Report.Registry.Server, item.Report.Artifact.Repository, item.Report.Artifact.Tag)
if filters.Name != itemImageName || filters.Digest != item.Report.Artifact.Digest {
continue
}

i := View{
Name: itemImageName,
Digest: item.Report.Artifact.Digest,
}

for _, v := range item.Report.Secrets {
secret := Secret{
Severity: string(v.Severity),
Title: v.Title,
Target: v.Target,
Match: v.Match,
}

uniqueSecret := i.isUniqueImageSecret(secret.Severity, secret.Title, secret.Target, secret.Match)
if uniqueSecret {
// Skip vulnerability if any filters don't match
// Filter severity
if filters.Severity != "" && !strings.EqualFold(secret.Severity, filters.Severity) {
continue
}

i.Secrets = append(i.Secrets, secret)
}
}

i = sortView(i)

return i, true
}

return View{}, false
}

func getImageNameFromLabels(registry, repo, tag string) string {
if registry == "index.docker.io" {
// If Docker Hub, trim the registry prefix for readability
// Also trims `library/` from the prefix of the image name, which is a hidden username for Docker Hub official images
return fmt.Sprintf("%s:%s", strings.TrimPrefix(repo, "library/"), tag)
}
return fmt.Sprintf("%s/%s:%s", registry, repo, tag)
}

func (i View) isUniqueImageSecret(severity, title, target, match string) bool {
for _, secret := range i.Secrets {
if severity == secret.Severity && title == secret.Title && target == secret.Target && match == secret.Match {
return false
}
}

return true
}

func sortView(v View) View {
// Create an order for severities to sort by
// Define custom priority order
severityOrder := map[string]int{
"CRITICAL": 3,
"HIGH": 2,
"MEDIUM": 1,
"LOW": 0,
}

// Sort the slice by severity in descending order
sort.Slice(v.Secrets, func(j, k int) bool {
return severityOrder[v.Secrets[j].Severity] > severityOrder[v.Secrets[k].Severity]
})

return v
}
19 changes: 19 additions & 0 deletions internal/web/views/exposedsecret/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package image

// View data about an image and their exposed secrets
type View Data

// Data contains data about an image and its exposed secrets
type Data struct {
Name string // name of the image
Digest string // sha digest of the image
Secrets []Secret
}

// Secret data related to an exposed secret
type Secret struct {
Severity string
Title string
Target string
Match string
}
Loading

0 comments on commit e027cad

Please sign in to comment.