-
Notifications
You must be signed in to change notification settings - Fork 153
/
Copy pathmain.go
143 lines (116 loc) · 4.14 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package main
import (
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
cli "github.com/jawher/mow.cli"
"github.com/mbndr/logo"
)
var osExit = os.Exit
type vulnerabilitiesWhitelist struct {
GeneralWhitelist map[string]string // [key: CVE and value: CVE description]
Images map[string]map[string]string // Image name with [key: CVE and value: CVE description]
}
// IsEmpty checks if the vulnerabilitiesWhitelist is empty
func (w vulnerabilitiesWhitelist) IsEmpty() bool {
return (w.GeneralWhitelist == nil || len(w.GeneralWhitelist) == 0) &&
(w.Images == nil || len(w.Images) == 0)
}
type ScannerApp struct {
Logger *logo.Logger
Whitelist vulnerabilitiesWhitelist
Run func(config ScannerConfig) // Replaceable for testing
}
func main() {
scannerApp := &ScannerApp{}
scannerApp.Run = scannerApp.run // Default run implementation
app := cli.App("clair-scanner", "Scan local Docker images for vulnerabilities with Clair")
// Set up the CLI application
setupApp(app, scannerApp)
// Run the CLI application
app.Run(os.Args)
}
func setupApp(app *cli.Cli, scannerApp *ScannerApp) {
var (
whitelistFile = app.StringOpt("w whitelist", "", "Path to the whitelist file")
whitelistThreshold = app.StringOpt("t threshold", "Unknown", "CVE severity threshold")
clair = app.String(cli.StringOpt{Name: "c clair", Value: "http://127.0.0.1:6060", Desc: "Clair URL", EnvVar: "CLAIR_URL"})
ip = app.StringOpt("ip", "localhost", "IP address where clair-scanner is running on")
logFile = app.StringOpt("l log", "", "Log to a file")
reportAll = app.BoolOpt("all reportAll", true, "Display all vulnerabilities")
reportFile = app.StringOpt("r report", "", "Report output file, as JSON")
quiet = app.BoolOpt("q quiet", false, "Suppress ASCII table output")
imageName = app.StringArg("IMAGE", "", "Name of the Docker image to scan")
exitWhenNoFeatures = app.BoolOpt("exit-when-no-features", false, "Exit with status code 5 when no features are found")
)
app.Before = func() {
scannerApp.Logger = initializeLogger(*logFile)
if *whitelistFile != "" {
scannerApp.Whitelist = parseWhitelistFile(scannerApp.Logger, *whitelistFile)
}
validateThreshold(scannerApp.Logger, *whitelistThreshold)
}
app.Action = func() {
config := ScannerConfig{
Whitelist: scannerApp.Whitelist,
ClairURL: *clair,
ScannerIP: *ip,
ReportFile: *reportFile,
WhitelistThreshold: *whitelistThreshold,
ReportAll: *reportAll,
Quiet: *quiet,
ExitWhenNoFeatures: *exitWhenNoFeatures,
ImageName: *imageName,
}
scannerApp.Run(config) // Call the replaceable Run function
}
}
func (app *ScannerApp) run(config ScannerConfig) {
if config.ImageName == "" {
app.Logger.Error("Image name is required")
osExit(1)
}
app.Logger.Infof("Starting clair-scanner for image: %s", config.ImageName)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
s := <-sigChan
app.Logger.Warnf("Application interrupted [%v]", s)
osExit(1)
}()
dockerClient, err := NewRealDockerClient()
if err != nil {
app.Logger.Errorf("Failed to create Docker client: %v", err)
osExit(1)
}
scanner := NewDefaultScanner(dockerClient, RealFileSystem{}, &http.Client{})
result := scanner.Scan(app.Logger, config)
app.handleScanResult(result)
}
func (app *ScannerApp) handleScanResult(result []string) {
if result == nil {
app.Logger.Warn("No features found in the scanned image")
osExit(5)
} else if len(result) > 0 {
app.Logger.Error("Unapproved vulnerabilities found in the image")
osExit(1)
}
app.Logger.Info("Scan completed successfully with no vulnerabilities")
osExit(0)
}
func initializeLogger(logFile string) *logo.Logger {
cliRec := logo.NewReceiver(os.Stderr, "")
cliRec.Color = true
if logFile != "" {
file, err := logo.Open(logFile)
if err != nil {
fmt.Printf("Could not initialize logging file: %v\n", err)
osExit(1)
}
fileRec := logo.NewReceiver(file, "")
return logo.NewLogger(cliRec, fileRec)
}
return logo.NewLogger(cliRec)
}