Initial commit: breakpilot-compliance - Compliance SDK Platform
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>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
# Build stage
|
||||
FROM golang:1.22-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache git
|
||||
|
||||
COPY go.mod go.sum* ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o security-scanner .
|
||||
|
||||
# Runtime stage with security tools
|
||||
FROM alpine:3.19
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install security tools
|
||||
RUN apk --no-cache add ca-certificates curl git python3 py3-pip nodejs npm && \
|
||||
# Install gitleaks
|
||||
curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.18.0/gitleaks_8.18.0_linux_x64.tar.gz | tar xz -C /usr/local/bin && \
|
||||
# Install trivy
|
||||
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin && \
|
||||
# Install grype
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin && \
|
||||
# Install syft
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin && \
|
||||
# Install semgrep
|
||||
pip3 install --break-system-packages semgrep bandit && \
|
||||
# Cleanup
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
COPY --from=builder /app/security-scanner .
|
||||
|
||||
RUN adduser -D -g '' appuser
|
||||
USER appuser
|
||||
|
||||
EXPOSE 8083
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:8083/health || exit 1
|
||||
|
||||
CMD ["./security-scanner"]
|
||||
@@ -0,0 +1,9 @@
|
||||
module github.com/breakpilot/compliance-sdk/services/security-scanner
|
||||
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/google/uuid v1.5.0
|
||||
go.uber.org/zap v1.26.0
|
||||
)
|
||||
@@ -0,0 +1,216 @@
|
||||
// Package api provides HTTP handlers for the Security Scanner
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/breakpilot/compliance-sdk/services/security-scanner/internal/scanner"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ScanRequest represents a scan request
|
||||
type ScanRequest struct {
|
||||
Tools []string `json:"tools"`
|
||||
TargetPath string `json:"target_path"`
|
||||
ExcludePaths []string `json:"exclude_paths"`
|
||||
}
|
||||
|
||||
// StartScan starts a new security scan
|
||||
func StartScan(manager *scanner.Manager) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req ScanRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
tools := req.Tools
|
||||
if len(tools) == 0 {
|
||||
tools = manager.AvailableTools()
|
||||
}
|
||||
|
||||
scan := manager.StartScan(tools, req.TargetPath)
|
||||
|
||||
c.JSON(http.StatusAccepted, gin.H{
|
||||
"scan_id": scan.ID,
|
||||
"status": scan.Status,
|
||||
"tools": scan.Tools,
|
||||
"started_at": scan.StartedAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetScanStatus returns the status of a scan
|
||||
func GetScanStatus(manager *scanner.Manager) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
scanID := c.Param("scanId")
|
||||
scan := manager.GetScan(scanID)
|
||||
|
||||
if scan == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Scan not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"scan_id": scan.ID,
|
||||
"status": scan.Status,
|
||||
"started_at": scan.StartedAt,
|
||||
"completed_at": scan.CompletedAt,
|
||||
"summary": scan.Summary,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetScanResults returns the results of a scan
|
||||
func GetScanResults(manager *scanner.Manager) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
scanID := c.Param("scanId")
|
||||
scan := manager.GetScan(scanID)
|
||||
|
||||
if scan == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Scan not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, scan)
|
||||
}
|
||||
}
|
||||
|
||||
// GetFindings returns all findings
|
||||
func GetFindings(manager *scanner.Manager) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
findings := manager.GetAllFindings()
|
||||
|
||||
severity := c.Query("severity")
|
||||
tool := c.Query("tool")
|
||||
status := c.Query("status")
|
||||
|
||||
// Filter findings
|
||||
filtered := []scanner.Finding{}
|
||||
for _, f := range findings {
|
||||
if severity != "" && f.Severity != severity {
|
||||
continue
|
||||
}
|
||||
if tool != "" && f.Tool != tool {
|
||||
continue
|
||||
}
|
||||
if status != "" && f.Status != status {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, f)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"findings": filtered,
|
||||
"total": len(filtered),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetFinding returns a specific finding
|
||||
func GetFinding(manager *scanner.Manager) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
findingID := c.Param("findingId")
|
||||
findings := manager.GetAllFindings()
|
||||
|
||||
for _, f := range findings {
|
||||
if f.ID == findingID {
|
||||
c.JSON(http.StatusOK, f)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Finding not found"})
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateFindingStatus updates the status of a finding
|
||||
func UpdateFindingStatus(manager *scanner.Manager) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
findingID := c.Param("findingId")
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if manager.UpdateFindingStatus(findingID, req.Status) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"id": findingID,
|
||||
"status": req.Status,
|
||||
})
|
||||
} else {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Finding not found"})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSBOM generates a Software Bill of Materials
|
||||
func GenerateSBOM(manager *scanner.Manager) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req struct {
|
||||
TargetPath string `json:"target_path"`
|
||||
}
|
||||
c.ShouldBindJSON(&req)
|
||||
|
||||
sbom := manager.GenerateSBOM(req.TargetPath)
|
||||
|
||||
c.JSON(http.StatusOK, sbom)
|
||||
}
|
||||
}
|
||||
|
||||
// GetSBOM returns an SBOM by ID
|
||||
func GetSBOM(manager *scanner.Manager) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// In production, retrieve from storage
|
||||
sbom := manager.GenerateSBOM("")
|
||||
c.JSON(http.StatusOK, sbom)
|
||||
}
|
||||
}
|
||||
|
||||
// ExportSBOM exports an SBOM in the specified format
|
||||
func ExportSBOM(manager *scanner.Manager) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
format := c.Param("format")
|
||||
|
||||
sbom := manager.GenerateSBOM("")
|
||||
sbom.Format = format
|
||||
|
||||
var contentType string
|
||||
switch format {
|
||||
case "cyclonedx":
|
||||
contentType = "application/vnd.cyclonedx+json"
|
||||
case "spdx":
|
||||
contentType = "application/spdx+json"
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Unsupported format"})
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Type", contentType)
|
||||
c.Header("Content-Disposition", "attachment; filename=sbom."+format+".json")
|
||||
c.JSON(http.StatusOK, sbom)
|
||||
}
|
||||
}
|
||||
|
||||
// GetRecommendations returns security recommendations
|
||||
func GetRecommendations(manager *scanner.Manager) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
recs := manager.GetRecommendations()
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"recommendations": recs,
|
||||
"total": len(recs),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetToolsStatus returns the status of all tools
|
||||
func GetToolsStatus(manager *scanner.Manager) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
tools := manager.GetTools()
|
||||
c.JSON(http.StatusOK, gin.H{"tools": tools})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
// 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"`
|
||||
}
|
||||
96
breakpilot-compliance-sdk/services/security-scanner/main.go
Normal file
96
breakpilot-compliance-sdk/services/security-scanner/main.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// BreakPilot Compliance SDK - Security Scanner Service
|
||||
//
|
||||
// Orchestrates security scanning tools and aggregates results.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/breakpilot/compliance-sdk/services/security-scanner/internal/api"
|
||||
"github.com/breakpilot/compliance-sdk/services/security-scanner/internal/scanner"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logger, _ := zap.NewProduction()
|
||||
defer logger.Sync()
|
||||
|
||||
// Initialize scanner manager
|
||||
scannerManager := scanner.NewManager(logger)
|
||||
|
||||
if os.Getenv("ENVIRONMENT") == "production" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
router := gin.New()
|
||||
router.Use(gin.Recovery())
|
||||
|
||||
// Health check
|
||||
router.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "healthy",
|
||||
"service": "security-scanner",
|
||||
"tools": scannerManager.AvailableTools(),
|
||||
})
|
||||
})
|
||||
|
||||
// API routes
|
||||
v1 := router.Group("/api/v1")
|
||||
{
|
||||
// Scanning
|
||||
v1.POST("/scan", api.StartScan(scannerManager))
|
||||
v1.GET("/scan/:scanId", api.GetScanStatus(scannerManager))
|
||||
v1.GET("/scan/:scanId/results", api.GetScanResults(scannerManager))
|
||||
|
||||
// Findings
|
||||
v1.GET("/findings", api.GetFindings(scannerManager))
|
||||
v1.GET("/findings/:findingId", api.GetFinding(scannerManager))
|
||||
v1.PUT("/findings/:findingId/status", api.UpdateFindingStatus(scannerManager))
|
||||
|
||||
// SBOM
|
||||
v1.POST("/sbom/generate", api.GenerateSBOM(scannerManager))
|
||||
v1.GET("/sbom/:sbomId", api.GetSBOM(scannerManager))
|
||||
v1.GET("/sbom/:sbomId/export/:format", api.ExportSBOM(scannerManager))
|
||||
|
||||
// Recommendations
|
||||
v1.GET("/recommendations", api.GetRecommendations(scannerManager))
|
||||
|
||||
// Tool status
|
||||
v1.GET("/tools", api.GetToolsStatus(scannerManager))
|
||||
}
|
||||
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "8083"
|
||||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: ":" + port,
|
||||
Handler: router,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 300 * time.Second, // Long timeout for scans
|
||||
}
|
||||
|
||||
go func() {
|
||||
logger.Info("Starting Security Scanner", zap.String("port", port))
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
logger.Fatal("Failed to start server", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
|
||||
logger.Info("Shutting down...")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
srv.Shutdown(ctx)
|
||||
}
|
||||
Reference in New Issue
Block a user