1 | /* Vuls - Vulnerability Scanner |
||
2 | Copyright (C) 2016 Future Corporation , Japan. |
||
3 | |||
4 | This program is free software: you can redistribute it and/or modify |
||
5 | it under the terms of the GNU General Public License as published by |
||
6 | the Free Software Foundation, either version 3 of the License, or |
||
7 | (at your option) any later version. |
||
8 | |||
9 | This program is distributed in the hope that it will be useful, |
||
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
12 | GNU General Public License for more details. |
||
13 | |||
14 | You should have received a copy of the GNU General Public License |
||
15 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
||
16 | */ |
||
17 | |||
18 | package scan |
||
19 | |||
20 | import ( |
||
21 | "fmt" |
||
22 | "net/http" |
||
23 | "os" |
||
24 | "path/filepath" |
||
25 | "time" |
||
26 | |||
27 | "github.com/future-architect/vuls/cache" |
||
28 | "github.com/future-architect/vuls/config" |
||
29 | "github.com/future-architect/vuls/models" |
||
30 | "github.com/future-architect/vuls/report" |
||
31 | "github.com/future-architect/vuls/util" |
||
32 | "golang.org/x/xerrors" |
||
33 | ) |
||
34 | |||
35 | const ( |
||
36 | scannedViaRemote = "remote" |
||
37 | scannedViaLocal = "local" |
||
38 | scannedViaPseudo = "pseudo" |
||
39 | ) |
||
40 | |||
41 | var ( |
||
42 | errOSFamilyHeader = xerrors.New("X-Vuls-OS-Family header is required") |
||
43 | errOSReleaseHeader = xerrors.New("X-Vuls-OS-Release header is required") |
||
44 | errKernelVersionHeader = xerrors.New("X-Vuls-Kernel-Version header is required") |
||
45 | errServerNameHeader = xerrors.New("X-Vuls-Server-Name header is required") |
||
46 | ) |
||
47 | |||
48 | var servers, errServers []osTypeInterface |
||
49 | |||
50 | // Base Interface of redhat, debian, freebsd |
||
51 | type osTypeInterface interface { |
||
52 | setServerInfo(config.ServerInfo) |
||
53 | getServerInfo() config.ServerInfo |
||
54 | setDistro(string, string) |
||
55 | getDistro() config.Distro |
||
56 | detectPlatform() |
||
57 | getPlatform() models.Platform |
||
58 | |||
59 | checkScanMode() error |
||
60 | checkDeps() error |
||
61 | checkIfSudoNoPasswd() error |
||
62 | |||
63 | preCure() error |
||
64 | postScan() error |
||
65 | scanWordPress() error |
||
66 | scanPackages() error |
||
67 | convertToModel() models.ScanResult |
||
68 | |||
69 | parseInstalledPackages(string) (models.Packages, models.SrcPackages, error) |
||
70 | |||
71 | runningContainers() ([]config.Container, error) |
||
72 | exitedContainers() ([]config.Container, error) |
||
73 | allContainers() ([]config.Container, error) |
||
74 | |||
75 | getErrs() []error |
||
76 | setErrs([]error) |
||
77 | } |
||
78 | |||
79 | // osPackages is included by base struct |
||
80 | type osPackages struct { |
||
81 | // installed packages |
||
82 | Packages models.Packages |
||
83 | |||
84 | // installed source packages (Debian based only) |
||
85 | SrcPackages models.SrcPackages |
||
86 | |||
87 | // unsecure packages |
||
88 | VulnInfos models.VulnInfos |
||
89 | |||
90 | // kernel information |
||
91 | Kernel models.Kernel |
||
92 | } |
||
93 | |||
94 | // Retry as it may stall on the first SSH connection |
||
95 | // https://github.com/future-architect/vuls/pull/753 |
||
96 | func detectDebianWithRetry(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err error) { |
||
97 | type Response struct { |
||
98 | itsMe bool |
||
99 | deb osTypeInterface |
||
100 | err error |
||
101 | } |
||
102 | resChan := make(chan Response, 1) |
||
103 | go func(c config.ServerInfo) { |
||
104 | itsMe, osType, fatalErr := detectDebian(c) |
||
105 | resChan <- Response{itsMe, osType, fatalErr} |
||
106 | }(c) |
||
107 | |||
108 | timeout := time.After(time.Duration(3) * time.Second) |
||
109 | select { |
||
110 | case res := <-resChan: |
||
111 | return res.itsMe, res.deb, res.err |
||
112 | case <-timeout: |
||
113 | time.Sleep(100 * time.Millisecond) |
||
114 | return detectDebian(c) |
||
115 | } |
||
116 | } |
||
117 | |||
118 | func detectOS(c config.ServerInfo) (osType osTypeInterface) { |
||
119 | var itsMe bool |
||
120 | var fatalErr error |
||
121 | |||
122 | if itsMe, osType, _ = detectPseudo(c); itsMe { |
||
123 | util.Log.Debugf("Pseudo") |
||
124 | return |
||
125 | } |
||
126 | |||
127 | itsMe, osType, fatalErr = detectDebianWithRetry(c) |
||
128 | if fatalErr != nil { |
||
129 | osType.setErrs([]error{ |
||
130 | xerrors.Errorf("Failed to detect OS: %w", fatalErr)}) |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
131 | return |
||
132 | } |
||
133 | |||
134 | if itsMe { |
||
135 | util.Log.Debugf("Debian like Linux. Host: %s:%s", c.Host, c.Port) |
||
136 | return |
||
137 | } |
||
138 | |||
139 | if itsMe, osType = detectRedhat(c); itsMe { |
||
140 | util.Log.Debugf("Redhat like Linux. Host: %s:%s", c.Host, c.Port) |
||
141 | return |
||
142 | } |
||
143 | |||
144 | if itsMe, osType = detectSUSE(c); itsMe { |
||
145 | util.Log.Debugf("SUSE Linux. Host: %s:%s", c.Host, c.Port) |
||
146 | return |
||
147 | } |
||
148 | |||
149 | if itsMe, osType = detectFreebsd(c); itsMe { |
||
150 | util.Log.Debugf("FreeBSD. Host: %s:%s", c.Host, c.Port) |
||
151 | return |
||
152 | } |
||
153 | |||
154 | if itsMe, osType = detectAlpine(c); itsMe { |
||
155 | util.Log.Debugf("Alpine. Host: %s:%s", c.Host, c.Port) |
||
156 | return |
||
157 | } |
||
158 | |||
159 | //TODO darwin https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/darwin.rb |
||
160 | osType.setErrs([]error{xerrors.New("Unknown OS Type")}) |
||
161 | return |
||
162 | } |
||
163 | |||
164 | // PrintSSHableServerNames print SSH-able servernames |
||
165 | func PrintSSHableServerNames() bool { |
||
166 | if len(servers) == 0 { |
||
167 | util.Log.Error("No scannable servers") |
||
168 | return false |
||
169 | } |
||
170 | util.Log.Info("Scannable servers are below...") |
||
171 | for _, s := range servers { |
||
172 | if s.getServerInfo().IsContainer() { |
||
173 | fmt.Printf("%s@%s ", |
||
174 | s.getServerInfo().Container.Name, |
||
175 | s.getServerInfo().ServerName, |
||
176 | ) |
||
177 | } else { |
||
178 | fmt.Printf("%s ", s.getServerInfo().ServerName) |
||
179 | } |
||
180 | } |
||
181 | fmt.Printf("\n") |
||
182 | return true |
||
183 | } |
||
184 | |||
185 | // InitServers detect the kind of OS distribution of target servers |
||
186 | func InitServers(timeoutSec int) error { |
||
187 | servers, errServers = detectServerOSes(timeoutSec) |
||
188 | if len(servers) == 0 { |
||
189 | return xerrors.New("No scannable servers") |
||
190 | } |
||
191 | |||
192 | actives, inactives := detectContainerOSes(timeoutSec) |
||
193 | if config.Conf.ContainersOnly { |
||
194 | servers = actives |
||
195 | errServers = inactives |
||
196 | } else { |
||
197 | servers = append(servers, actives...) |
||
198 | errServers = append(errServers, inactives...) |
||
199 | } |
||
200 | return nil |
||
201 | } |
||
202 | |||
203 | func detectServerOSes(timeoutSec int) (servers, errServers []osTypeInterface) { |
||
204 | util.Log.Info("Detecting OS of servers... ") |
||
205 | osTypeChan := make(chan osTypeInterface, len(config.Conf.Servers)) |
||
206 | defer close(osTypeChan) |
||
207 | for _, s := range config.Conf.Servers { |
||
208 | go func(s config.ServerInfo) { |
||
209 | defer func() { |
||
210 | if p := recover(); p != nil { |
||
211 | util.Log.Debugf("Panic: %s on %s", p, s.ServerName) |
||
212 | } |
||
213 | }() |
||
214 | osTypeChan <- detectOS(s) |
||
215 | }(s) |
||
216 | } |
||
217 | |||
218 | timeout := time.After(time.Duration(timeoutSec) * time.Second) |
||
219 | for i := 0; i < len(config.Conf.Servers); i++ { |
||
220 | select { |
||
221 | case res := <-osTypeChan: |
||
222 | if 0 < len(res.getErrs()) { |
||
223 | errServers = append(errServers, res) |
||
224 | util.Log.Errorf("(%d/%d) Failed: %s, err: %+v", |
||
225 | i+1, len(config.Conf.Servers), |
||
226 | res.getServerInfo().ServerName, |
||
227 | res.getErrs()) |
||
228 | } else { |
||
229 | servers = append(servers, res) |
||
230 | util.Log.Infof("(%d/%d) Detected: %s: %s", |
||
231 | i+1, len(config.Conf.Servers), |
||
232 | res.getServerInfo().ServerName, |
||
233 | res.getDistro()) |
||
234 | } |
||
235 | case <-timeout: |
||
236 | msg := "Timed out while detecting servers" |
||
237 | util.Log.Error(msg) |
||
238 | for servername, sInfo := range config.Conf.Servers { |
||
239 | found := false |
||
240 | for _, o := range append(servers, errServers...) { |
||
241 | if servername == o.getServerInfo().ServerName { |
||
242 | found = true |
||
243 | break |
||
244 | } |
||
245 | } |
||
246 | if !found { |
||
247 | u := &unknown{} |
||
248 | u.setServerInfo(sInfo) |
||
249 | u.setErrs([]error{ |
||
250 | xerrors.New("Timed out"), |
||
251 | }) |
||
252 | errServers = append(errServers, u) |
||
253 | util.Log.Errorf("(%d/%d) Timed out: %s", |
||
254 | i+1, len(config.Conf.Servers), |
||
255 | servername) |
||
256 | i++ |
||
257 | } |
||
258 | } |
||
259 | } |
||
260 | } |
||
261 | return |
||
262 | } |
||
263 | |||
264 | func detectContainerOSes(timeoutSec int) (actives, inactives []osTypeInterface) { |
||
265 | util.Log.Info("Detecting OS of containers... ") |
||
266 | osTypesChan := make(chan []osTypeInterface, len(servers)) |
||
267 | defer close(osTypesChan) |
||
268 | for _, s := range servers { |
||
269 | go func(s osTypeInterface) { |
||
270 | defer func() { |
||
271 | if p := recover(); p != nil { |
||
272 | util.Log.Debugf("Panic: %s on %s", |
||
273 | p, s.getServerInfo().GetServerName()) |
||
274 | } |
||
275 | }() |
||
276 | osTypesChan <- detectContainerOSesOnServer(s) |
||
277 | }(s) |
||
278 | } |
||
279 | |||
280 | timeout := time.After(time.Duration(timeoutSec) * time.Second) |
||
281 | for i := 0; i < len(servers); i++ { |
||
282 | select { |
||
283 | case res := <-osTypesChan: |
||
284 | for _, osi := range res { |
||
285 | sinfo := osi.getServerInfo() |
||
286 | if 0 < len(osi.getErrs()) { |
||
287 | inactives = append(inactives, osi) |
||
288 | util.Log.Errorf("Failed: %s err: %+v", sinfo.ServerName, osi.getErrs()) |
||
289 | continue |
||
290 | } |
||
291 | actives = append(actives, osi) |
||
292 | util.Log.Infof("Detected: %s@%s: %s", |
||
293 | sinfo.Container.Name, sinfo.ServerName, osi.getDistro()) |
||
294 | } |
||
295 | case <-timeout: |
||
296 | msg := "Timed out while detecting containers" |
||
297 | util.Log.Error(msg) |
||
298 | for servername, sInfo := range config.Conf.Servers { |
||
299 | found := false |
||
300 | for _, o := range append(actives, inactives...) { |
||
301 | if servername == o.getServerInfo().ServerName { |
||
302 | found = true |
||
303 | break |
||
304 | } |
||
305 | } |
||
306 | if !found { |
||
307 | u := &unknown{} |
||
308 | u.setServerInfo(sInfo) |
||
309 | u.setErrs([]error{ |
||
310 | xerrors.New("Timed out"), |
||
311 | }) |
||
312 | inactives = append(inactives) |
||
313 | util.Log.Errorf("Timed out: %s", servername) |
||
314 | } |
||
315 | } |
||
316 | } |
||
317 | } |
||
318 | return |
||
319 | } |
||
320 | |||
321 | func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeInterface) { |
||
322 | containerHostInfo := containerHost.getServerInfo() |
||
323 | if len(containerHostInfo.ContainersIncluded) == 0 { |
||
324 | return |
||
325 | } |
||
326 | |||
327 | running, err := containerHost.runningContainers() |
||
328 | if err != nil { |
||
329 | containerHost.setErrs([]error{xerrors.Errorf( |
||
0 ignored issues
–
show
|
|||
330 | "Failed to get running containers on %s. err: %w", |
||
331 | containerHost.getServerInfo().ServerName, err)}) |
||
332 | return append(oses, containerHost) |
||
333 | } |
||
334 | |||
335 | if containerHostInfo.ContainersIncluded[0] == "${running}" { |
||
336 | for _, containerInfo := range running { |
||
337 | found := false |
||
338 | for _, ex := range containerHost.getServerInfo().ContainersExcluded { |
||
339 | if containerInfo.Name == ex || containerInfo.ContainerID == ex { |
||
340 | found = true |
||
341 | } |
||
342 | } |
||
343 | if found { |
||
344 | continue |
||
345 | } |
||
346 | |||
347 | copied := containerHostInfo |
||
348 | copied.SetContainer(config.Container{ |
||
349 | ContainerID: containerInfo.ContainerID, |
||
350 | Name: containerInfo.Name, |
||
351 | Image: containerInfo.Image, |
||
352 | }) |
||
353 | os := detectOS(copied) |
||
354 | oses = append(oses, os) |
||
355 | } |
||
356 | return oses |
||
357 | } |
||
358 | |||
359 | exitedContainers, err := containerHost.exitedContainers() |
||
360 | if err != nil { |
||
361 | containerHost.setErrs([]error{xerrors.Errorf( |
||
0 ignored issues
–
show
|
|||
362 | "Failed to get exited containers on %s. err: %w", |
||
363 | containerHost.getServerInfo().ServerName, err)}) |
||
364 | return append(oses, containerHost) |
||
365 | } |
||
366 | |||
367 | var exited, unknown []string |
||
368 | for _, container := range containerHostInfo.ContainersIncluded { |
||
369 | found := false |
||
370 | for _, c := range running { |
||
371 | if c.ContainerID == container || c.Name == container { |
||
372 | copied := containerHostInfo |
||
373 | copied.SetContainer(c) |
||
374 | os := detectOS(copied) |
||
375 | oses = append(oses, os) |
||
376 | found = true |
||
377 | break |
||
378 | } |
||
379 | } |
||
380 | |||
381 | if !found { |
||
382 | foundInExitedContainers := false |
||
383 | for _, c := range exitedContainers { |
||
384 | if c.ContainerID == container || c.Name == container { |
||
385 | exited = append(exited, container) |
||
386 | foundInExitedContainers = true |
||
387 | break |
||
388 | } |
||
389 | } |
||
390 | if !foundInExitedContainers { |
||
391 | unknown = append(unknown, container) |
||
392 | } |
||
393 | } |
||
394 | } |
||
395 | if 0 < len(exited) || 0 < len(unknown) { |
||
396 | containerHost.setErrs([]error{xerrors.Errorf( |
||
397 | "Some containers on %s are exited or unknown. exited: %s, unknown: %s", |
||
398 | containerHost.getServerInfo().ServerName, exited, unknown)}) |
||
399 | return append(oses, containerHost) |
||
400 | } |
||
401 | return oses |
||
402 | } |
||
403 | |||
404 | // CheckScanModes checks scan mode |
||
405 | func CheckScanModes() error { |
||
406 | for _, s := range servers { |
||
407 | if err := s.checkScanMode(); err != nil { |
||
408 | return xerrors.Errorf("servers.%s.scanMode err: %w", |
||
0 ignored issues
–
show
|
|||
409 | s.getServerInfo().GetServerName(), err) |
||
410 | } |
||
411 | } |
||
412 | return nil |
||
413 | } |
||
414 | |||
415 | // CheckDependencies checks dependencies are installed on target servers. |
||
416 | func CheckDependencies(timeoutSec int) { |
||
417 | parallelExec(func(o osTypeInterface) error { |
||
418 | return o.checkDeps() |
||
419 | }, timeoutSec) |
||
420 | return |
||
421 | } |
||
422 | |||
423 | // CheckIfSudoNoPasswd checks whether vuls can sudo with nopassword via SSH |
||
424 | func CheckIfSudoNoPasswd(timeoutSec int) { |
||
425 | parallelExec(func(o osTypeInterface) error { |
||
426 | return o.checkIfSudoNoPasswd() |
||
427 | }, timeoutSec) |
||
428 | return |
||
429 | } |
||
430 | |||
431 | // DetectPlatforms detects the platform of each servers. |
||
432 | func DetectPlatforms(timeoutSec int) { |
||
433 | detectPlatforms(timeoutSec) |
||
434 | for i, s := range servers { |
||
435 | if s.getServerInfo().IsContainer() { |
||
436 | util.Log.Infof("(%d/%d) %s on %s is running on %s", |
||
437 | i+1, len(servers), |
||
438 | s.getServerInfo().Container.Name, |
||
439 | s.getServerInfo().ServerName, |
||
440 | s.getPlatform().Name, |
||
441 | ) |
||
442 | |||
443 | } else { |
||
444 | util.Log.Infof("(%d/%d) %s is running on %s", |
||
445 | i+1, len(servers), |
||
446 | s.getServerInfo().ServerName, |
||
447 | s.getPlatform().Name, |
||
448 | ) |
||
449 | } |
||
450 | } |
||
451 | return |
||
452 | } |
||
453 | |||
454 | func detectPlatforms(timeoutSec int) { |
||
455 | parallelExec(func(o osTypeInterface) error { |
||
456 | o.detectPlatform() |
||
457 | // Logging only if platform can not be specified |
||
458 | return nil |
||
459 | }, timeoutSec) |
||
460 | return |
||
461 | } |
||
462 | |||
463 | // Scan scan |
||
464 | func Scan(timeoutSec int) error { |
||
465 | if len(servers) == 0 { |
||
466 | return xerrors.New("No server defined. Check the configuration") |
||
467 | } |
||
468 | |||
469 | if err := setupChangelogCache(); err != nil { |
||
470 | return err |
||
471 | } |
||
472 | defer func() { |
||
473 | if cache.DB != nil { |
||
474 | cache.DB.Close() |
||
475 | } |
||
476 | }() |
||
477 | |||
478 | util.Log.Info("Scanning vulnerable OS packages...") |
||
479 | scannedAt := time.Now() |
||
480 | dir, err := EnsureResultDir(scannedAt) |
||
481 | if err != nil { |
||
482 | return err |
||
483 | } |
||
484 | return scanVulns(dir, scannedAt, timeoutSec) |
||
485 | } |
||
486 | |||
487 | // ViaHTTP scans servers by HTTP header and body |
||
488 | func ViaHTTP(header http.Header, body string) (models.ScanResult, error) { |
||
489 | family := header.Get("X-Vuls-OS-Family") |
||
490 | if family == "" { |
||
491 | return models.ScanResult{}, errOSFamilyHeader |
||
492 | } |
||
493 | |||
494 | release := header.Get("X-Vuls-OS-Release") |
||
495 | if release == "" { |
||
496 | return models.ScanResult{}, errOSReleaseHeader |
||
497 | } |
||
498 | |||
499 | kernelRelease := header.Get("X-Vuls-Kernel-Release") |
||
500 | if kernelRelease == "" { |
||
501 | util.Log.Warn("If X-Vuls-Kernel-Release is not specified, there is a possibility of false detection") |
||
502 | } |
||
503 | |||
504 | kernelVersion := header.Get("X-Vuls-Kernel-Version") |
||
505 | if family == config.Debian && kernelVersion == "" { |
||
506 | return models.ScanResult{}, errKernelVersionHeader |
||
507 | } |
||
508 | |||
509 | serverName := header.Get("X-Vuls-Server-Name") |
||
510 | if config.Conf.ToLocalFile && serverName == "" { |
||
511 | return models.ScanResult{}, errServerNameHeader |
||
512 | } |
||
513 | |||
514 | distro := config.Distro{ |
||
515 | Family: family, |
||
516 | Release: release, |
||
517 | } |
||
518 | |||
519 | kernel := models.Kernel{ |
||
520 | Release: kernelRelease, |
||
521 | Version: kernelVersion, |
||
522 | } |
||
523 | base := base{ |
||
524 | Distro: distro, |
||
525 | osPackages: osPackages{ |
||
526 | Kernel: kernel, |
||
527 | }, |
||
528 | log: util.Log, |
||
529 | } |
||
530 | |||
531 | var osType osTypeInterface |
||
532 | switch family { |
||
533 | case config.Debian, config.Ubuntu: |
||
534 | osType = &debian{base: base} |
||
535 | case config.RedHat: |
||
536 | osType = &rhel{ |
||
537 | redhatBase: redhatBase{base: base}, |
||
538 | } |
||
539 | case config.CentOS: |
||
540 | osType = ¢os{ |
||
541 | redhatBase: redhatBase{base: base}, |
||
542 | } |
||
543 | default: |
||
544 | return models.ScanResult{}, xerrors.Errorf("Server mode for %s is not implemented yet", family) |
||
545 | } |
||
546 | |||
547 | installedPackages, srcPackages, err := osType.parseInstalledPackages(body) |
||
548 | if err != nil { |
||
549 | return models.ScanResult{}, err |
||
550 | } |
||
551 | |||
552 | result := models.ScanResult{ |
||
553 | ServerName: serverName, |
||
554 | Family: family, |
||
555 | Release: release, |
||
556 | RunningKernel: models.Kernel{ |
||
557 | Release: kernelRelease, |
||
558 | Version: kernelVersion, |
||
559 | }, |
||
560 | Packages: installedPackages, |
||
561 | SrcPackages: srcPackages, |
||
562 | ScannedCves: models.VulnInfos{}, |
||
563 | } |
||
564 | |||
565 | return result, nil |
||
566 | } |
||
567 | |||
568 | func setupChangelogCache() error { |
||
569 | needToSetupCache := false |
||
570 | for _, s := range servers { |
||
571 | switch s.getDistro().Family { |
||
572 | case config.Raspbian: |
||
573 | needToSetupCache = true |
||
574 | break |
||
575 | case config.Ubuntu, config.Debian: |
||
576 | //TODO changelopg cache for RedHat, Oracle, Amazon, CentOS is not implemented yet. |
||
577 | if s.getServerInfo().Mode.IsDeep() { |
||
578 | needToSetupCache = true |
||
579 | } |
||
580 | break |
||
581 | } |
||
582 | } |
||
583 | if needToSetupCache { |
||
584 | if err := cache.SetupBolt(config.Conf.CacheDBPath, util.Log); err != nil { |
||
585 | return err |
||
586 | } |
||
587 | } |
||
588 | return nil |
||
589 | } |
||
590 | |||
591 | func scanVulns(jsonDir string, scannedAt time.Time, timeoutSec int) error { |
||
592 | var results models.ScanResults |
||
593 | parallelExec(func(o osTypeInterface) (err error) { |
||
594 | if err = o.preCure(); err != nil { |
||
595 | return err |
||
596 | } |
||
597 | if err = o.scanPackages(); err != nil { |
||
598 | return err |
||
599 | } |
||
600 | if err = o.scanWordPress(); err != nil { |
||
601 | return xerrors.Errorf("Failed to scan WordPress: %w", err) |
||
0 ignored issues
–
show
|
|||
602 | } |
||
603 | return o.postScan() |
||
604 | }, timeoutSec) |
||
605 | |||
606 | hostname, _ := os.Hostname() |
||
607 | ipv4s, ipv6s, err := util.IP() |
||
608 | if err != nil { |
||
609 | util.Log.Errorf("Failed to fetch scannedIPs. err: %+v", err) |
||
610 | } |
||
611 | |||
612 | for _, s := range append(servers, errServers...) { |
||
613 | r := s.convertToModel() |
||
614 | r.ScannedAt = scannedAt |
||
615 | r.ScannedVersion = config.Version |
||
616 | r.ScannedRevision = config.Revision |
||
617 | r.ScannedBy = hostname |
||
618 | r.ScannedIPv4Addrs = ipv4s |
||
619 | r.ScannedIPv6Addrs = ipv6s |
||
620 | r.Config.Scan = config.Conf |
||
621 | results = append(results, r) |
||
622 | } |
||
623 | |||
624 | config.Conf.FormatJSON = true |
||
625 | ws := []report.ResultWriter{ |
||
626 | report.LocalFileWriter{CurrentDir: jsonDir}, |
||
627 | } |
||
628 | for _, w := range ws { |
||
629 | if err := w.Write(results...); err != nil { |
||
630 | return xerrors.Errorf("Failed to write summary report: %s", err) |
||
631 | } |
||
632 | } |
||
633 | |||
634 | report.StdoutWriter{}.WriteScanSummary(results...) |
||
635 | return nil |
||
636 | } |
||
637 | |||
638 | // EnsureResultDir ensures the directory for scan results |
||
639 | func EnsureResultDir(scannedAt time.Time) (currentDir string, err error) { |
||
640 | jsonDirName := scannedAt.Format(time.RFC3339) |
||
641 | |||
642 | resultsDir := config.Conf.ResultsDir |
||
643 | if len(resultsDir) == 0 { |
||
644 | wd, _ := os.Getwd() |
||
645 | resultsDir = filepath.Join(wd, "results") |
||
646 | } |
||
647 | jsonDir := filepath.Join(resultsDir, jsonDirName) |
||
648 | if err := os.MkdirAll(jsonDir, 0700); err != nil { |
||
649 | return "", xerrors.Errorf("Failed to create dir: %w", err) |
||
0 ignored issues
–
show
|
|||
650 | } |
||
651 | |||
652 | symlinkPath := filepath.Join(resultsDir, "current") |
||
653 | if _, err := os.Lstat(symlinkPath); err == nil { |
||
654 | if err := os.Remove(symlinkPath); err != nil { |
||
655 | return "", xerrors.Errorf( |
||
0 ignored issues
–
show
|
|||
656 | "Failed to remove symlink. path: %s, err: %w", symlinkPath, err) |
||
657 | } |
||
658 | } |
||
659 | |||
660 | if err := os.Symlink(jsonDir, symlinkPath); err != nil { |
||
661 | return "", xerrors.Errorf( |
||
0 ignored issues
–
show
|
|||
662 | "Failed to create symlink: path: %s, err: %w", symlinkPath, err) |
||
663 | } |
||
664 | return jsonDir, nil |
||
665 | } |
||
666 |