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