1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.agent;
19
20 import org.owasp.dependencycheck.Engine;
21 import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
22 import org.owasp.dependencycheck.data.update.exception.UpdateException;
23 import org.owasp.dependencycheck.dependency.Dependency;
24 import org.owasp.dependencycheck.dependency.Vulnerability;
25 import org.owasp.dependencycheck.dependency.naming.Identifier;
26 import org.owasp.dependencycheck.exception.ExceptionCollection;
27 import org.owasp.dependencycheck.exception.ReportException;
28 import org.owasp.dependencycheck.exception.ScanAgentException;
29 import org.owasp.dependencycheck.reporting.ReportGenerator;
30 import org.owasp.dependencycheck.utils.Settings;
31 import org.owasp.dependencycheck.utils.SeverityUtil;
32 import org.owasp.dependencycheck.utils.scarf.TelemetryCollector;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 import javax.annotation.concurrent.NotThreadSafe;
37 import java.io.File;
38 import java.io.IOException;
39 import java.util.List;
40 import java.util.stream.Collectors;
41 import java.util.stream.Stream;
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 @SuppressWarnings("unused")
70 @NotThreadSafe
71 public class DependencyCheckScanAgent {
72
73
74
75
76
77 private static final String NEW_LINE = System.getProperty("line.separator", "\n").intern();
78
79
80
81 private static final Logger LOGGER = LoggerFactory.getLogger(DependencyCheckScanAgent.class);
82
83
84
85 private String applicationName = "Dependency-Check";
86
87
88
89 private List<Dependency> dependencies;
90
91
92
93 private String dataDirectory = null;
94
95
96
97
98 private String reportOutputDirectory;
99
100
101
102
103
104
105
106 private Double failBuildOnCVSS = 11.0;
107
108
109
110
111 private boolean autoUpdate = true;
112
113
114
115 private String nvdApiKey;
116
117
118
119
120
121 private boolean updateOnly = false;
122
123
124
125 private boolean generateReport = true;
126
127
128
129
130
131
132 private ReportGenerator.Format reportFormat = ReportGenerator.Format.HTML;
133
134
135
136 private String proxyServer;
137
138
139
140 private String proxyPort;
141
142
143
144 private String proxyUsername;
145
146
147
148 private String proxyPassword;
149
150
151
152 private String connectionTimeout;
153
154
155
156 private String readTimeout;
157
158
159
160 private String logFile = null;
161
162
163
164 private boolean showSummary = true;
165
166
167
168 private String suppressionFile;
169
170
171
172 private String databasePassword;
173
174
175
176
177 private String cpeStartsWithFilter;
178
179
180
181 private boolean centralAnalyzerEnabled = true;
182
183
184
185 private boolean failOnUnusedSuppressionRule = false;
186
187
188
189 private String centralUrl;
190
191
192
193 private boolean nexusAnalyzerEnabled = true;
194
195
196
197 private String nexusUrl;
198
199
200
201 private boolean nexusUsesProxy = true;
202
203
204
205 private String databaseDriverName;
206
207
208
209 private String databaseDriverPath;
210
211
212
213 private String connectionString;
214
215
216
217 private String databaseUser;
218
219
220
221
222 private String zipExtensions;
223
224
225
226 private String pathToCore;
227
228
229
230 private Settings settings;
231
232
233
234
235
236 private String propertiesFilePath;
237
238
239
240
241
242
243
244
245 public String getApplicationName() {
246 return applicationName;
247 }
248
249
250
251
252
253
254 public void setApplicationName(String applicationName) {
255 this.applicationName = applicationName;
256 }
257
258
259
260
261
262
263 public String getNvdApiKey() {
264 return nvdApiKey;
265 }
266
267
268
269
270
271
272 public void setNvdApiKey(String nvdApiKey) {
273 this.nvdApiKey = nvdApiKey;
274 }
275
276
277
278
279
280
281 public List<Dependency> getDependencies() {
282 return dependencies;
283 }
284
285
286
287
288
289
290 public void setDependencies(List<Dependency> dependencies) {
291 this.dependencies = dependencies;
292 }
293
294
295
296
297
298
299 public String getDataDirectory() {
300 return dataDirectory;
301 }
302
303
304
305
306
307
308 public void setDataDirectory(String dataDirectory) {
309 this.dataDirectory = dataDirectory;
310 }
311
312
313
314
315
316
317 public String getReportOutputDirectory() {
318 return reportOutputDirectory;
319 }
320
321
322
323
324
325
326 public void setReportOutputDirectory(String reportOutputDirectory) {
327 this.reportOutputDirectory = reportOutputDirectory;
328 }
329
330
331
332
333
334
335 public Double getFailBuildOnCVSS() {
336 return failBuildOnCVSS;
337 }
338
339
340
341
342
343
344 public void setFailBuildOnCVSS(Double failBuildOnCVSS) {
345 this.failBuildOnCVSS = failBuildOnCVSS;
346 }
347
348
349
350
351
352
353 public boolean isAutoUpdate() {
354 return autoUpdate;
355 }
356
357
358
359
360
361
362 public void setAutoUpdate(boolean autoUpdate) {
363 this.autoUpdate = autoUpdate;
364 }
365
366
367
368
369
370
371 public boolean isUpdateOnly() {
372 return updateOnly;
373 }
374
375
376
377
378
379
380 public void setUpdateOnly(boolean updateOnly) {
381 this.updateOnly = updateOnly;
382 }
383
384
385
386
387
388
389 public boolean isGenerateReport() {
390 return generateReport;
391 }
392
393
394
395
396
397
398 public void setGenerateReport(boolean generateReport) {
399 this.generateReport = generateReport;
400 }
401
402
403
404
405
406
407 public ReportGenerator.Format getReportFormat() {
408 return reportFormat;
409 }
410
411
412
413
414
415
416 public void setReportFormat(ReportGenerator.Format reportFormat) {
417 this.reportFormat = reportFormat;
418 }
419
420
421
422
423
424
425 public String getProxyServer() {
426 return proxyServer;
427 }
428
429
430
431
432
433
434 public void setProxyServer(String proxyServer) {
435 this.proxyServer = proxyServer;
436 }
437
438
439
440
441
442
443 public String getProxyPort() {
444 return proxyPort;
445 }
446
447
448
449
450
451
452 public void setProxyPort(String proxyPort) {
453 this.proxyPort = proxyPort;
454 }
455
456
457
458
459
460
461 public String getProxyUsername() {
462 return proxyUsername;
463 }
464
465
466
467
468
469
470 public void setProxyUsername(String proxyUsername) {
471 this.proxyUsername = proxyUsername;
472 }
473
474
475
476
477
478
479 public String getProxyPassword() {
480 return proxyPassword;
481 }
482
483
484
485
486
487
488 public void setProxyPassword(String proxyPassword) {
489 this.proxyPassword = proxyPassword;
490 }
491
492
493
494
495
496
497 public String getConnectionTimeout() {
498 return connectionTimeout;
499 }
500
501
502
503
504
505
506 public void setConnectionTimeout(String connectionTimeout) {
507 this.connectionTimeout = connectionTimeout;
508 }
509
510
511
512
513
514
515 public String getReadTimeout() {
516 return readTimeout;
517 }
518
519
520
521
522
523
524 public void setReadTimeout(String readTimeout) {
525 this.readTimeout = readTimeout;
526 }
527
528
529
530
531
532
533 public String getLogFile() {
534 return logFile;
535 }
536
537
538
539
540
541
542 public void setLogFile(String logFile) {
543 this.logFile = logFile;
544 }
545
546
547
548
549
550
551 public String getSuppressionFile() {
552 return suppressionFile;
553 }
554
555
556
557
558
559
560 public void setSuppressionFile(String suppressionFile) {
561 this.suppressionFile = suppressionFile;
562 }
563
564
565
566
567
568
569 public boolean isShowSummary() {
570 return showSummary;
571 }
572
573
574
575
576
577
578 public void setShowSummary(boolean showSummary) {
579 this.showSummary = showSummary;
580 }
581
582
583
584
585
586
587
588
589 public void setCpeStartsWithFilter(String cpeStartsWithFilter) {
590 this.cpeStartsWithFilter = cpeStartsWithFilter;
591 }
592
593
594
595
596
597
598
599 public String getCpeStartsWithFilter() {
600 return cpeStartsWithFilter;
601 }
602
603
604
605
606
607
608 public boolean isFailOnUnusedSuppressionRule() {
609 return failOnUnusedSuppressionRule;
610 }
611
612
613
614
615
616
617 public void setFailOnUnusedSuppressionRule(boolean failOnUnusedSuppressionRule) {
618 this.failOnUnusedSuppressionRule = failOnUnusedSuppressionRule;
619 }
620
621
622
623
624
625
626 public boolean isCentralAnalyzerEnabled() {
627 return centralAnalyzerEnabled;
628 }
629
630
631
632
633
634
635 public void setCentralAnalyzerEnabled(boolean centralAnalyzerEnabled) {
636 this.centralAnalyzerEnabled = centralAnalyzerEnabled;
637 }
638
639
640
641
642
643
644 public String getCentralUrl() {
645 return centralUrl;
646 }
647
648
649
650
651
652
653 public void setCentralUrl(String centralUrl) {
654 this.centralUrl = centralUrl;
655 }
656
657
658
659
660
661
662 public boolean isNexusAnalyzerEnabled() {
663 return nexusAnalyzerEnabled;
664 }
665
666
667
668
669
670
671 public void setNexusAnalyzerEnabled(boolean nexusAnalyzerEnabled) {
672 this.nexusAnalyzerEnabled = nexusAnalyzerEnabled;
673 }
674
675
676
677
678
679
680 public String getNexusUrl() {
681 return nexusUrl;
682 }
683
684
685
686
687
688
689 public void setNexusUrl(String nexusUrl) {
690 this.nexusUrl = nexusUrl;
691 }
692
693
694
695
696
697
698 public boolean isNexusUsesProxy() {
699 return nexusUsesProxy;
700 }
701
702
703
704
705
706
707 public void setNexusUsesProxy(boolean nexusUsesProxy) {
708 this.nexusUsesProxy = nexusUsesProxy;
709 }
710
711
712
713
714
715
716 public String getDatabaseDriverName() {
717 return databaseDriverName;
718 }
719
720
721
722
723
724
725 public void setDatabaseDriverName(String databaseDriverName) {
726 this.databaseDriverName = databaseDriverName;
727 }
728
729
730
731
732
733
734 public String getDatabaseDriverPath() {
735 return databaseDriverPath;
736 }
737
738
739
740
741
742
743 public void setDatabaseDriverPath(String databaseDriverPath) {
744 this.databaseDriverPath = databaseDriverPath;
745 }
746
747
748
749
750
751
752 public String getConnectionString() {
753 return connectionString;
754 }
755
756
757
758
759
760
761 public void setConnectionString(String connectionString) {
762 this.connectionString = connectionString;
763 }
764
765
766
767
768
769
770 public String getDatabaseUser() {
771 return databaseUser;
772 }
773
774
775
776
777
778
779 public void setDatabaseUser(String databaseUser) {
780 this.databaseUser = databaseUser;
781 }
782
783
784
785
786
787
788 public String getDatabasePassword() {
789 return databasePassword;
790 }
791
792
793
794
795
796
797 public void setDatabasePassword(String databasePassword) {
798 this.databasePassword = databasePassword;
799 }
800
801
802
803
804
805
806 public String getZipExtensions() {
807 return zipExtensions;
808 }
809
810
811
812
813
814
815 public void setZipExtensions(String zipExtensions) {
816 this.zipExtensions = zipExtensions;
817 }
818
819
820
821
822
823
824 public String getPathToDotnetCore() {
825 return pathToCore;
826 }
827
828
829
830
831
832
833 public void setPathToDotnetCore(String pathToCore) {
834 this.pathToCore = pathToCore;
835 }
836
837
838
839
840
841
842 public String getPropertiesFilePath() {
843 return propertiesFilePath;
844 }
845
846
847
848
849
850
851 public void setPropertiesFilePath(String propertiesFilePath) {
852 this.propertiesFilePath = propertiesFilePath;
853 }
854
855
856
857
858
859
860
861
862
863
864
865 @SuppressWarnings("squid:S2095")
866 private Engine executeDependencyCheck() throws ExceptionCollection {
867 populateSettings();
868 String version = settings.getString(Settings.KEYS.APPLICATION_VERSION, "Unknown");
869 TelemetryCollector.send(settings, "dependency-check-scan-agent", version);
870 final Engine engine;
871 try {
872 engine = new Engine(settings);
873 } catch (DatabaseException ex) {
874 throw new ExceptionCollection(ex, true);
875 }
876 if (this.updateOnly) {
877 try {
878 engine.doUpdates();
879 } catch (UpdateException ex) {
880 throw new ExceptionCollection(ex);
881 } finally {
882 engine.close();
883 }
884 } else {
885 engine.setDependencies(this.dependencies);
886 engine.analyzeDependencies();
887 }
888 return engine;
889 }
890
891
892
893
894
895
896
897
898
899 private void generateExternalReports(Engine engine, File outDirectory) throws ScanAgentException {
900 try {
901 engine.writeReports(applicationName, outDirectory, this.reportFormat.name(), null);
902 } catch (ReportException ex) {
903 LOGGER.debug("Unexpected exception occurred during analysis; please see the verbose error log for more details.", ex);
904 throw new ScanAgentException("Error generating the report", ex);
905 }
906 }
907
908
909
910
911
912
913 private void populateSettings() {
914 settings = new Settings();
915 if (dataDirectory != null) {
916 settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory);
917 } else {
918 final File jarPath = new File(DependencyCheckScanAgent.class.getProtectionDomain().getCodeSource().getLocation().getPath());
919 final File base = jarPath.getParentFile();
920 final String sub = settings.getString(Settings.KEYS.DATA_DIRECTORY);
921 final File dataDir = new File(base, sub);
922 settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
923 }
924 if (propertiesFilePath != null) {
925 try {
926 settings.mergeProperties(propertiesFilePath);
927 LOGGER.info("Successfully loaded user-defined properties");
928 } catch (IOException e) {
929 LOGGER.error("Unable to merge user-defined properties", e);
930 LOGGER.error("Continuing execution");
931 }
932 }
933
934 settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
935 settings.setStringIfNotEmpty(Settings.KEYS.PROXY_SERVER, proxyServer);
936 settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PORT, proxyPort);
937 settings.setStringIfNotEmpty(Settings.KEYS.PROXY_USERNAME, proxyUsername);
938 settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PASSWORD, proxyPassword);
939 settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout);
940 settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_READ_TIMEOUT, readTimeout);
941 settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressionFile);
942 settings.setStringIfNotEmpty(Settings.KEYS.CVE_CPE_STARTS_WITH_FILTER, cpeStartsWithFilter);
943 settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, centralAnalyzerEnabled);
944 settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_CENTRAL_URL, centralUrl);
945 settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, nexusAnalyzerEnabled);
946 settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl);
947 settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_USES_PROXY, nexusUsesProxy);
948 settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_NAME, databaseDriverName);
949 settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_PATH, databaseDriverPath);
950 settings.setStringIfNotEmpty(Settings.KEYS.DB_CONNECTION_STRING, connectionString);
951 settings.setStringIfNotEmpty(Settings.KEYS.DB_USER, databaseUser);
952 settings.setStringIfNotEmpty(Settings.KEYS.DB_PASSWORD, databasePassword);
953 settings.setStringIfNotEmpty(Settings.KEYS.ADDITIONAL_ZIP_EXTENSIONS, zipExtensions);
954 settings.setStringIfNotEmpty(Settings.KEYS.NVD_API_KEY, nvdApiKey);
955 settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_ASSEMBLY_DOTNET_PATH, pathToCore);
956 settings.setBoolean(Settings.KEYS.FAIL_ON_UNUSED_SUPPRESSION_RULE, failOnUnusedSuppressionRule);
957 }
958
959
960
961
962
963
964
965
966 public Engine execute() throws ScanAgentException {
967 Engine engine = null;
968 try {
969 engine = executeDependencyCheck();
970 if (!this.updateOnly) {
971 if (this.generateReport) {
972 generateExternalReports(engine, new File(this.reportOutputDirectory));
973 }
974 if (this.showSummary) {
975 showSummary(engine.getDependencies());
976 }
977 if (this.failBuildOnCVSS <= 10.0) {
978 checkForFailure(engine.getDependencies());
979 }
980 }
981 } catch (ExceptionCollection ex) {
982 if (ex.isFatal()) {
983 LOGGER.error("A fatal exception occurred during analysis; analysis has stopped. Please see the debug log for more details.");
984 LOGGER.debug("", ex);
985 }
986 throw new ScanAgentException("One or more exceptions occurred during analysis; please see the debug log for more details.", ex);
987 } finally {
988 if (engine != null) {
989 engine.close();
990 }
991 settings.cleanup(true);
992 }
993 return engine;
994 }
995
996
997
998
999
1000
1001
1002
1003
1004 private void checkForFailure(Dependency[] dependencies) throws ScanAgentException {
1005 final StringBuilder ids = new StringBuilder();
1006 for (Dependency d : dependencies) {
1007 boolean addName = true;
1008 for (Vulnerability v : d.getVulnerabilities()) {
1009 final double cvssV2 = v.getCvssV2() != null && v.getCvssV2().getCvssData() != null
1010 && v.getCvssV2().getCvssData().getBaseScore() != null ? v.getCvssV2().getCvssData().getBaseScore() : -1;
1011 final double cvssV3 = v.getCvssV3() != null && v.getCvssV3().getCvssData() != null
1012 && v.getCvssV3().getCvssData().getBaseScore() != null ? v.getCvssV3().getCvssData().getBaseScore() : -1;
1013 final double cvssV4 = v.getCvssV4() != null && v.getCvssV4().getCvssData() != null
1014 && v.getCvssV4().getCvssData().getBaseScore() != null ? v.getCvssV4().getCvssData().getBaseScore() : -1;
1015 final boolean useUnscored = cvssV2 == -1 && cvssV3 == -1 && cvssV4 == -1;
1016 final double unscoredCvss = (useUnscored && v.getUnscoredSeverity() != null) ? SeverityUtil.estimateCvssV2(v.getUnscoredSeverity()) : -1;
1017 if (cvssV2 >= failBuildOnCVSS
1018 || cvssV3 >= failBuildOnCVSS
1019 || cvssV4 >= failBuildOnCVSS
1020 || unscoredCvss >= failBuildOnCVSS
1021
1022 || failBuildOnCVSS <= 0.0f
1023 ) {
1024 if (addName) {
1025 addName = false;
1026 ids.append(NEW_LINE).append(d.getFileName()).append(" (")
1027 .append(Stream.concat(d.getSoftwareIdentifiers().stream(), d.getVulnerableSoftwareIdentifiers().stream())
1028 .map(Identifier::getValue)
1029 .collect(Collectors.joining(", ")))
1030 .append("): ")
1031 .append(v.getName());
1032 } else {
1033 ids.append(", ").append(v.getName());
1034 }
1035 }
1036 }
1037 }
1038 if (ids.length() > 0) {
1039 final String msg;
1040 if (showSummary) {
1041 msg = String.format("%n%nDependency-Check Failure:%n"
1042 + "One or more dependencies were identified with vulnerabilities that have a CVSS score greater than or equal to '%.1f': %s%n"
1043 + "See the dependency-check report for more details.%n%n", failBuildOnCVSS, ids);
1044 } else {
1045 msg = String.format("%n%nDependency-Check Failure:%n"
1046 + "One or more dependencies were identified with vulnerabilities.%n%n"
1047 + "See the dependency-check report for more details.%n%n");
1048 }
1049 throw new ScanAgentException(msg);
1050 }
1051 }
1052
1053
1054
1055
1056
1057
1058
1059 public static void showSummary(Dependency[] dependencies) {
1060 showSummary(null, dependencies);
1061 }
1062
1063
1064
1065
1066
1067
1068
1069
1070 public static void showSummary(String projectName, Dependency[] dependencies) {
1071 final StringBuilder summary = new StringBuilder();
1072 for (Dependency d : dependencies) {
1073 final String ids = d.getVulnerabilities(true).stream()
1074 .map(Vulnerability::getName)
1075 .collect(Collectors.joining(", "));
1076 if (ids.length() > 0) {
1077 summary.append(d.getFileName()).append(" (");
1078 summary.append(Stream.concat(d.getSoftwareIdentifiers().stream(), d.getVulnerableSoftwareIdentifiers().stream())
1079 .map(Identifier::getValue)
1080 .collect(Collectors.joining(", ")));
1081 summary.append(") : ").append(ids).append(NEW_LINE);
1082 }
1083 }
1084 if (summary.length() > 0) {
1085 if (projectName == null || projectName.isEmpty()) {
1086 LOGGER.warn("\n\nOne or more dependencies were identified with known vulnerabilities:\n\n{}\n\n"
1087 + "See the dependency-check report for more details.\n\n",
1088 summary);
1089 } else {
1090 LOGGER.warn("\n\nOne or more dependencies were identified with known vulnerabilities in {}:\n\n{}\n\n"
1091 + "See the dependency-check report for more details.\n\n",
1092 projectName,
1093 summary);
1094 }
1095 }
1096 }
1097 }