Cybersecurity-Projects/PROJECTS/docker-security-audit/internal/finding/finding.go

272 lines
4.9 KiB
Go

/*
AngelaMos | 2026
finding.go
*/
package finding
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
"time"
)
const (
containerIDShortLength = 12
imageIDShortLength = 12
)
type Severity int
const (
SeverityInfo Severity = iota
SeverityLow
SeverityMedium
SeverityHigh
SeverityCritical
)
var severityToString = map[Severity]string{
SeverityInfo: "INFO",
SeverityLow: "LOW",
SeverityMedium: "MEDIUM",
SeverityHigh: "HIGH",
SeverityCritical: "CRITICAL",
}
var stringToSeverity = func() map[string]Severity {
m := make(map[string]Severity, len(severityToString))
for sev, str := range severityToString {
m[str] = sev
}
return m
}()
var severityColors = map[Severity]string{
SeverityInfo: "\033[36m",
SeverityLow: "\033[34m",
SeverityMedium: "\033[33m",
SeverityHigh: "\033[31m",
SeverityCritical: "\033[35m",
}
func (s Severity) String() string {
if str, ok := severityToString[s]; ok {
return str
}
return "UNKNOWN"
}
func (s Severity) Color() string {
if color, ok := severityColors[s]; ok {
return color
}
return "\033[0m"
}
func ParseSeverity(s string) (Severity, bool) {
sev, ok := stringToSeverity[strings.ToUpper(strings.TrimSpace(s))]
return sev, ok
}
type TargetType string
const (
TargetContainer TargetType = "container"
TargetImage TargetType = "image"
TargetDockerfile TargetType = "dockerfile"
TargetCompose TargetType = "compose"
TargetDaemon TargetType = "daemon"
)
type Target struct {
Type TargetType
Name string
ID string
}
func (t Target) String() string {
if t.ID != "" && len(t.ID) >= containerIDShortLength {
return fmt.Sprintf(
"%s:%s (%s)",
t.Type,
t.Name,
t.ID[:containerIDShortLength],
)
}
if t.ID != "" {
return fmt.Sprintf("%s:%s (%s)", t.Type, t.Name, t.ID)
}
return fmt.Sprintf("%s:%s", t.Type, t.Name)
}
type Location struct {
Path string
Line int
Column int
}
func (l Location) String() string {
if l.Line > 0 {
return fmt.Sprintf("%s:%d", l.Path, l.Line)
}
return l.Path
}
type CISControl struct {
ID string
Section string
Title string
Description string
Scored bool
Level int
}
func (c CISControl) String() string {
return fmt.Sprintf("CIS %s", c.ID)
}
type Finding struct {
ID string
RuleID string
Title string
Description string
Severity Severity
Category string
Target Target
Location *Location
Remediation string
References []string
CISControl *CISControl
Timestamp time.Time
}
func New(
ruleID string,
title string,
severity Severity,
target Target,
) *Finding {
f := &Finding{
RuleID: ruleID,
Title: title,
Severity: severity,
Target: target,
Timestamp: time.Now(),
}
f.ID = f.generateID()
return f
}
func (f *Finding) generateID() string {
data := fmt.Sprintf(
"%s|%s|%s|%s",
f.RuleID,
f.Target.Type,
f.Target.Name,
f.Target.ID,
)
if f.Location != nil {
data += fmt.Sprintf("|%s:%d", f.Location.Path, f.Location.Line)
}
hash := sha256.Sum256([]byte(data))
return hex.EncodeToString(hash[:8])
}
func (f *Finding) WithDescription(desc string) *Finding {
f.Description = desc
return f
}
func (f *Finding) WithCategory(cat string) *Finding {
f.Category = cat
return f
}
func (f *Finding) WithLocation(loc *Location) *Finding {
f.Location = loc
f.ID = f.generateID()
return f
}
func (f *Finding) WithRemediation(rem string) *Finding {
f.Remediation = rem
return f
}
func (f *Finding) WithReferences(refs ...string) *Finding {
f.References = refs
return f
}
func (f *Finding) WithCISControl(control *CISControl) *Finding {
f.CISControl = control
return f
}
// Collection is a slice of findings with filtering and aggregation methods
type Collection []*Finding
func (c Collection) BySeverity(sev Severity) Collection {
result := make(Collection, 0, len(c))
for _, f := range c {
if f.Severity == sev {
result = append(result, f)
}
}
return result
}
func (c Collection) AtOrAbove(sev Severity) Collection {
result := make(Collection, 0, len(c))
for _, f := range c {
if f.Severity >= sev {
result = append(result, f)
}
}
return result
}
func (c Collection) ByCategory(cat string) Collection {
result := make(Collection, 0, len(c))
for _, f := range c {
if f.Category == cat {
result = append(result, f)
}
}
return result
}
func (c Collection) ByTargetType(tt TargetType) Collection {
result := make(Collection, 0, len(c))
for _, f := range c {
if f.Target.Type == tt {
result = append(result, f)
}
}
return result
}
func (c Collection) CountBySeverity() map[Severity]int {
counts := make(map[Severity]int, len(severityToString))
for _, f := range c {
counts[f.Severity]++
}
return counts
}
func (c Collection) HasSeverityAtOrAbove(sev Severity) bool {
for _, f := range c {
if f.Severity >= sev {
return true
}
}
return false
}
func (c Collection) Total() int {
return len(c)
}