Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
410 lines
10 KiB
Go
410 lines
10 KiB
Go
// Package scanner manages security scanning tools
|
|
package scanner
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// Manager orchestrates security scanning tools
|
|
type Manager struct {
|
|
logger *zap.Logger
|
|
tools map[string]*Tool
|
|
scans map[string]*Scan
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// Tool represents a security scanning tool
|
|
type Tool struct {
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
Available bool `json:"available"`
|
|
Description string `json:"description"`
|
|
Category string `json:"category"` // secrets, sast, container, dependencies, sbom
|
|
}
|
|
|
|
// Scan represents a security scan
|
|
type Scan struct {
|
|
ID string `json:"id"`
|
|
Status string `json:"status"` // PENDING, RUNNING, COMPLETED, FAILED
|
|
Tools []string `json:"tools"`
|
|
TargetPath string `json:"target_path"`
|
|
StartedAt time.Time `json:"started_at"`
|
|
CompletedAt time.Time `json:"completed_at,omitempty"`
|
|
Findings []Finding `json:"findings"`
|
|
Summary Summary `json:"summary"`
|
|
}
|
|
|
|
// Finding represents a security finding
|
|
type Finding struct {
|
|
ID string `json:"id"`
|
|
Tool string `json:"tool"`
|
|
Severity string `json:"severity"` // CRITICAL, HIGH, MEDIUM, LOW
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
File string `json:"file,omitempty"`
|
|
Line int `json:"line,omitempty"`
|
|
Recommendation string `json:"recommendation"`
|
|
Status string `json:"status"` // OPEN, IN_PROGRESS, RESOLVED, FALSE_POSITIVE
|
|
CreatedAt time.Time `json:"created_at"`
|
|
CVE string `json:"cve,omitempty"`
|
|
}
|
|
|
|
// Summary represents scan summary
|
|
type Summary struct {
|
|
Critical int `json:"critical"`
|
|
High int `json:"high"`
|
|
Medium int `json:"medium"`
|
|
Low int `json:"low"`
|
|
Total int `json:"total"`
|
|
}
|
|
|
|
// SBOM represents a Software Bill of Materials
|
|
type SBOM struct {
|
|
ID string `json:"id"`
|
|
Format string `json:"format"` // cyclonedx, spdx
|
|
Version string `json:"version"`
|
|
GeneratedAt time.Time `json:"generated_at"`
|
|
Components []Component `json:"components"`
|
|
Dependencies []string `json:"dependencies"`
|
|
}
|
|
|
|
// Component represents an SBOM component
|
|
type Component struct {
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
Type string `json:"type"` // library, application, framework
|
|
License string `json:"license"`
|
|
Category string `json:"category"`
|
|
Vulnerabilities []string `json:"vulnerabilities,omitempty"`
|
|
}
|
|
|
|
// NewManager creates a new scanner manager
|
|
func NewManager(logger *zap.Logger) *Manager {
|
|
m := &Manager{
|
|
logger: logger,
|
|
tools: make(map[string]*Tool),
|
|
scans: make(map[string]*Scan),
|
|
}
|
|
|
|
// Initialize tools
|
|
m.initializeTools()
|
|
|
|
return m
|
|
}
|
|
|
|
func (m *Manager) initializeTools() {
|
|
m.tools = map[string]*Tool{
|
|
"gitleaks": {
|
|
Name: "Gitleaks",
|
|
Version: "8.18.0",
|
|
Available: true,
|
|
Description: "Detect secrets and sensitive data in git repositories",
|
|
Category: "secrets",
|
|
},
|
|
"semgrep": {
|
|
Name: "Semgrep",
|
|
Version: "1.51.0",
|
|
Available: true,
|
|
Description: "Static analysis for multiple languages",
|
|
Category: "sast",
|
|
},
|
|
"bandit": {
|
|
Name: "Bandit",
|
|
Version: "1.7.6",
|
|
Available: true,
|
|
Description: "Security linter for Python code",
|
|
Category: "sast",
|
|
},
|
|
"trivy": {
|
|
Name: "Trivy",
|
|
Version: "0.48.0",
|
|
Available: true,
|
|
Description: "Vulnerability scanner for containers and filesystems",
|
|
Category: "container",
|
|
},
|
|
"grype": {
|
|
Name: "Grype",
|
|
Version: "0.73.0",
|
|
Available: true,
|
|
Description: "Vulnerability scanner for dependencies",
|
|
Category: "dependencies",
|
|
},
|
|
"syft": {
|
|
Name: "Syft",
|
|
Version: "0.98.0",
|
|
Available: true,
|
|
Description: "SBOM generator",
|
|
Category: "sbom",
|
|
},
|
|
}
|
|
}
|
|
|
|
// AvailableTools returns list of available tools
|
|
func (m *Manager) AvailableTools() []string {
|
|
tools := make([]string, 0, len(m.tools))
|
|
for name, tool := range m.tools {
|
|
if tool.Available {
|
|
tools = append(tools, name)
|
|
}
|
|
}
|
|
return tools
|
|
}
|
|
|
|
// GetTools returns all tools with their status
|
|
func (m *Manager) GetTools() map[string]*Tool {
|
|
return m.tools
|
|
}
|
|
|
|
// StartScan starts a new security scan
|
|
func (m *Manager) StartScan(tools []string, targetPath string) *Scan {
|
|
scan := &Scan{
|
|
ID: uuid.New().String(),
|
|
Status: "RUNNING",
|
|
Tools: tools,
|
|
TargetPath: targetPath,
|
|
StartedAt: time.Now(),
|
|
Findings: []Finding{},
|
|
}
|
|
|
|
m.mu.Lock()
|
|
m.scans[scan.ID] = scan
|
|
m.mu.Unlock()
|
|
|
|
// Run scan asynchronously
|
|
go m.runScan(scan)
|
|
|
|
return scan
|
|
}
|
|
|
|
func (m *Manager) runScan(scan *Scan) {
|
|
m.logger.Info("Starting scan", zap.String("id", scan.ID), zap.Strings("tools", scan.Tools))
|
|
|
|
// Simulate scanning
|
|
time.Sleep(3 * time.Second)
|
|
|
|
// Generate sample findings
|
|
findings := m.generateSampleFindings(scan.Tools)
|
|
|
|
// Update scan
|
|
m.mu.Lock()
|
|
scan.Status = "COMPLETED"
|
|
scan.CompletedAt = time.Now()
|
|
scan.Findings = findings
|
|
scan.Summary = m.calculateSummary(findings)
|
|
m.mu.Unlock()
|
|
|
|
m.logger.Info("Scan completed",
|
|
zap.String("id", scan.ID),
|
|
zap.Int("findings", len(findings)))
|
|
}
|
|
|
|
func (m *Manager) generateSampleFindings(tools []string) []Finding {
|
|
findings := []Finding{}
|
|
|
|
for _, tool := range tools {
|
|
switch tool {
|
|
case "gitleaks":
|
|
// Usually no findings in clean repos
|
|
case "trivy":
|
|
findings = append(findings, Finding{
|
|
ID: uuid.New().String(),
|
|
Tool: "trivy",
|
|
Severity: "HIGH",
|
|
Title: "CVE-2024-1234 in lodash",
|
|
Description: "Prototype pollution vulnerability in lodash < 4.17.21",
|
|
Recommendation: "Upgrade lodash to version 4.17.21 or higher",
|
|
Status: "OPEN",
|
|
CreatedAt: time.Now(),
|
|
CVE: "CVE-2024-1234",
|
|
})
|
|
case "semgrep":
|
|
findings = append(findings, Finding{
|
|
ID: uuid.New().String(),
|
|
Tool: "semgrep",
|
|
Severity: "MEDIUM",
|
|
Title: "Potential SQL injection",
|
|
Description: "User input used in SQL query without proper sanitization",
|
|
File: "src/db/queries.ts",
|
|
Line: 42,
|
|
Recommendation: "Use parameterized queries",
|
|
Status: "OPEN",
|
|
CreatedAt: time.Now(),
|
|
})
|
|
case "grype":
|
|
findings = append(findings, Finding{
|
|
ID: uuid.New().String(),
|
|
Tool: "grype",
|
|
Severity: "LOW",
|
|
Title: "Outdated dependency",
|
|
Description: "axios@0.21.0 has known vulnerabilities",
|
|
Recommendation: "Update to axios@1.6.0 or higher",
|
|
Status: "OPEN",
|
|
CreatedAt: time.Now(),
|
|
})
|
|
}
|
|
}
|
|
|
|
return findings
|
|
}
|
|
|
|
func (m *Manager) calculateSummary(findings []Finding) Summary {
|
|
summary := Summary{}
|
|
for _, f := range findings {
|
|
switch f.Severity {
|
|
case "CRITICAL":
|
|
summary.Critical++
|
|
case "HIGH":
|
|
summary.High++
|
|
case "MEDIUM":
|
|
summary.Medium++
|
|
case "LOW":
|
|
summary.Low++
|
|
}
|
|
summary.Total++
|
|
}
|
|
return summary
|
|
}
|
|
|
|
// GetScan returns a scan by ID
|
|
func (m *Manager) GetScan(scanID string) *Scan {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return m.scans[scanID]
|
|
}
|
|
|
|
// GetAllFindings returns all findings across scans
|
|
func (m *Manager) GetAllFindings() []Finding {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
findings := []Finding{}
|
|
for _, scan := range m.scans {
|
|
findings = append(findings, scan.Findings...)
|
|
}
|
|
return findings
|
|
}
|
|
|
|
// UpdateFindingStatus updates the status of a finding
|
|
func (m *Manager) UpdateFindingStatus(findingID, status string) bool {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
for _, scan := range m.scans {
|
|
for i := range scan.Findings {
|
|
if scan.Findings[i].ID == findingID {
|
|
scan.Findings[i].Status = status
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GenerateSBOM generates a Software Bill of Materials
|
|
func (m *Manager) GenerateSBOM(targetPath string) *SBOM {
|
|
sbom := &SBOM{
|
|
ID: uuid.New().String(),
|
|
Format: "cyclonedx",
|
|
Version: "1.5",
|
|
GeneratedAt: time.Now(),
|
|
Components: m.generateSampleComponents(),
|
|
}
|
|
return sbom
|
|
}
|
|
|
|
func (m *Manager) generateSampleComponents() []Component {
|
|
return []Component{
|
|
{
|
|
Name: "react",
|
|
Version: "18.2.0",
|
|
Type: "library",
|
|
License: "MIT",
|
|
Category: "frontend",
|
|
},
|
|
{
|
|
Name: "typescript",
|
|
Version: "5.3.3",
|
|
Type: "library",
|
|
License: "Apache-2.0",
|
|
Category: "tooling",
|
|
},
|
|
{
|
|
Name: "express",
|
|
Version: "4.18.2",
|
|
Type: "framework",
|
|
License: "MIT",
|
|
Category: "backend",
|
|
},
|
|
{
|
|
Name: "lodash",
|
|
Version: "4.17.21",
|
|
Type: "library",
|
|
License: "MIT",
|
|
Category: "utility",
|
|
},
|
|
}
|
|
}
|
|
|
|
// GetRecommendations generates recommendations based on findings
|
|
func (m *Manager) GetRecommendations() []Recommendation {
|
|
findings := m.GetAllFindings()
|
|
recs := []Recommendation{}
|
|
|
|
// Group by category
|
|
hasVulns := false
|
|
hasSecrets := false
|
|
hasSAST := false
|
|
|
|
for _, f := range findings {
|
|
switch m.tools[f.Tool].Category {
|
|
case "dependencies", "container":
|
|
hasVulns = true
|
|
case "secrets":
|
|
hasSecrets = true
|
|
case "sast":
|
|
hasSAST = true
|
|
}
|
|
}
|
|
|
|
if hasVulns {
|
|
recs = append(recs, Recommendation{
|
|
Priority: "HIGH",
|
|
Category: "DEPENDENCIES",
|
|
Title: "Update vulnerable dependencies",
|
|
Description: "Several dependencies have known vulnerabilities. Run 'npm audit fix' or equivalent.",
|
|
})
|
|
}
|
|
|
|
if hasSecrets {
|
|
recs = append(recs, Recommendation{
|
|
Priority: "CRITICAL",
|
|
Category: "SECRETS",
|
|
Title: "Remove exposed secrets",
|
|
Description: "Secrets detected in codebase. Remove and rotate immediately.",
|
|
})
|
|
}
|
|
|
|
if hasSAST {
|
|
recs = append(recs, Recommendation{
|
|
Priority: "MEDIUM",
|
|
Category: "CODE_QUALITY",
|
|
Title: "Address code quality issues",
|
|
Description: "Static analysis found potential security issues in code.",
|
|
})
|
|
}
|
|
|
|
return recs
|
|
}
|
|
|
|
// Recommendation represents a security recommendation
|
|
type Recommendation struct {
|
|
Priority string `json:"priority"`
|
|
Category string `json:"category"`
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
}
|