View Javadoc
1   /*
2    * This file is part of dependency-check-core.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2014 Steve Springett. All Rights Reserved.
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   * This class provides a way to easily conduct a scan solely based on existing
45   * evidence metadata rather than collecting evidence from the files themselves.
46   * This class is based on the Ant task and Maven plugin with the exception that
47   * it takes a list of dependencies that can be programmatically added from data
48   * in a spreadsheet, database or some other datasource and conduct a scan based
49   * on this pre-defined evidence.
50   *
51   * <h2>Example:</h2>
52   * <pre>
53   * List&lt;Dependency&gt; dependencies = new ArrayList&lt;Dependency&gt;();
54   * Dependency dependency = new Dependency(new File(FileUtils.getBitBucket()));
55   * dependency.addEvidence(EvidenceType.PRODUCT, "my-datasource", "name", "Jetty", Confidence.HIGH);
56   * dependency.addEvidence(EvidenceType.VERSION, "my-datasource", "version", "5.1.10", Confidence.HIGH);
57   * dependency.addEvidence(EvidenceType.VENDOR, "my-datasource", "vendor", "mortbay", Confidence.HIGH);
58   * dependencies.add(dependency);
59   *
60   * DependencyCheckScanAgent scan = new DependencyCheckScanAgent();
61   * scan.setDependencies(dependencies);
62   * scan.setReportFormat(ReportGenerator.Format.ALL);
63   * scan.setReportOutputDirectory(System.getProperty("user.home"));
64   * scan.execute();
65   * </pre>
66   *
67   * @author Steve Springett
68   */
69  @SuppressWarnings("unused")
70  @NotThreadSafe
71  public class DependencyCheckScanAgent {
72  
73      //<editor-fold defaultstate="collapsed" desc="private fields">
74      /**
75       * System specific new line character.
76       */
77      private static final String NEW_LINE = System.getProperty("line.separator", "\n").intern();
78      /**
79       * Logger for use throughout the class.
80       */
81      private static final Logger LOGGER = LoggerFactory.getLogger(DependencyCheckScanAgent.class);
82      /**
83       * The application name for the report.
84       */
85      private String applicationName = "Dependency-Check";
86      /**
87       * The pre-determined dependencies to scan
88       */
89      private List<Dependency> dependencies;
90      /**
91       * The location of the data directory that contains
92       */
93      private String dataDirectory = null;
94      /**
95       * Specifies the destination directory for the generated Dependency-Check
96       * report.
97       */
98      private String reportOutputDirectory;
99      /**
100      * Specifies if the build should be failed if a CVSS score above a specified
101      * level is identified. The default is 11 which means since the CVSS scores
102      * are 0-10, by default the build will never fail and the CVSS score is set
103      * to 11. The valid range for the fail build on CVSS is 0 to 11, where
104      * anything above 10 will not cause the build to fail.
105      */
106     private Double failBuildOnCVSS = 11.0;
107     /**
108      * Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not
109      * recommended that this be turned to false. Default is true.
110      */
111     private boolean autoUpdate = true;
112     /**
113      * The NVD API key.
114      */
115     private String nvdApiKey;
116 
117     /**
118      * Sets whether the data directory should be updated without performing a
119      * scan. Default is false.
120      */
121     private boolean updateOnly = false;
122     /**
123      * flag indicating whether to generate a report of findings.
124      */
125     private boolean generateReport = true;
126     /**
127      * The report format to be generated (HTML, XML, CSV, JSON, JUNIT, SARIF,
128      * JENKINS, GITLAB, ALL). This configuration option has no affect if using
129      * this within the Site plugin unless the externalReport is set to true.
130      * Default is HTML.
131      */
132     private ReportGenerator.Format reportFormat = ReportGenerator.Format.HTML;
133     /**
134      * The Proxy Server.
135      */
136     private String proxyServer;
137     /**
138      * The Proxy Port.
139      */
140     private String proxyPort;
141     /**
142      * The Proxy username.
143      */
144     private String proxyUsername;
145     /**
146      * The Proxy password.
147      */
148     private String proxyPassword;
149     /**
150      * The Connection Timeout.
151      */
152     private String connectionTimeout;
153     /**
154      * The Connection Read Timeout.
155      */
156     private String readTimeout;
157     /**
158      * The file path used for verbose logging.
159      */
160     private String logFile = null;
161     /**
162      * flag indicating whether to show a summary of findings.
163      */
164     private boolean showSummary = true;
165     /**
166      * The path to the suppression file.
167      */
168     private String suppressionFile;
169     /**
170      * The password to use when connecting to the database.
171      */
172     private String databasePassword;
173     /**
174      * The starting string that identifies CPEs that are qualified to be
175      * imported.
176      */
177     private String cpeStartsWithFilter;
178     /**
179      * Whether the Maven Central analyzer is enabled.
180      */
181     private boolean centralAnalyzerEnabled = true;
182     /**
183      * Whether the build should fail if there are unused suppression rules.
184      */
185     private boolean failOnUnusedSuppressionRule = false;
186     /**
187      * The URL of Maven Central.
188      */
189     private String centralUrl;
190     /**
191      * Whether the nexus analyzer is enabled.
192      */
193     private boolean nexusAnalyzerEnabled = true;
194     /**
195      * The URL of the Nexus server.
196      */
197     private String nexusUrl;
198     /**
199      * Whether the defined proxy should be used when connecting to Nexus.
200      */
201     private boolean nexusUsesProxy = true;
202     /**
203      * The database driver name; such as org.h2.Driver.
204      */
205     private String databaseDriverName;
206     /**
207      * The path to the database driver JAR file if it is not on the class path.
208      */
209     private String databaseDriverPath;
210     /**
211      * The database connection string.
212      */
213     private String connectionString;
214     /**
215      * The username for connecting to the database.
216      */
217     private String databaseUser;
218     /**
219      * Additional ZIP File extensions to add analyze. This should be a
220      * comma-separated list of file extensions to treat like ZIP files.
221      */
222     private String zipExtensions;
223     /**
224      * The path to dotnet core for .NET assembly analysis.
225      */
226     private String pathToCore;
227     /**
228      * The configured settings.
229      */
230     private Settings settings;
231     /**
232      * The path to optional dependency-check properties file. This will be used
233      * to side-load additional user-defined properties.
234      * {@link Settings#mergeProperties(String)}
235      */
236     private String propertiesFilePath;
237     //</editor-fold>
238     //<editor-fold defaultstate="collapsed" desc="getters/setters">
239 
240     /**
241      * Get the value of applicationName.
242      *
243      * @return the value of applicationName
244      */
245     public String getApplicationName() {
246         return applicationName;
247     }
248 
249     /**
250      * Set the value of applicationName.
251      *
252      * @param applicationName new value of applicationName
253      */
254     public void setApplicationName(String applicationName) {
255         this.applicationName = applicationName;
256     }
257 
258     /**
259      * Get the value of nvdApiKey.
260      *
261      * @return the value of nvdApiKey
262      */
263     public String getNvdApiKey() {
264         return nvdApiKey;
265     }
266 
267     /**
268      * Set the value of nvdApiKey.
269      *
270      * @param nvdApiKey new value of nvdApiKey
271      */
272     public void setNvdApiKey(String nvdApiKey) {
273         this.nvdApiKey = nvdApiKey;
274     }
275 
276     /**
277      * Returns a list of pre-determined dependencies.
278      *
279      * @return returns a list of dependencies
280      */
281     public List<Dependency> getDependencies() {
282         return dependencies;
283     }
284 
285     /**
286      * Sets the list of dependencies to scan.
287      *
288      * @param dependencies new value of dependencies
289      */
290     public void setDependencies(List<Dependency> dependencies) {
291         this.dependencies = dependencies;
292     }
293 
294     /**
295      * Get the value of dataDirectory.
296      *
297      * @return the value of dataDirectory
298      */
299     public String getDataDirectory() {
300         return dataDirectory;
301     }
302 
303     /**
304      * Set the value of dataDirectory.
305      *
306      * @param dataDirectory new value of dataDirectory
307      */
308     public void setDataDirectory(String dataDirectory) {
309         this.dataDirectory = dataDirectory;
310     }
311 
312     /**
313      * Get the value of reportOutputDirectory.
314      *
315      * @return the value of reportOutputDirectory
316      */
317     public String getReportOutputDirectory() {
318         return reportOutputDirectory;
319     }
320 
321     /**
322      * Set the value of reportOutputDirectory.
323      *
324      * @param reportOutputDirectory new value of reportOutputDirectory
325      */
326     public void setReportOutputDirectory(String reportOutputDirectory) {
327         this.reportOutputDirectory = reportOutputDirectory;
328     }
329 
330     /**
331      * Get the value of failBuildOnCVSS.
332      *
333      * @return the value of failBuildOnCVSS
334      */
335     public Double getFailBuildOnCVSS() {
336         return failBuildOnCVSS;
337     }
338 
339     /**
340      * Set the value of failBuildOnCVSS.
341      *
342      * @param failBuildOnCVSS new value of failBuildOnCVSS
343      */
344     public void setFailBuildOnCVSS(Double failBuildOnCVSS) {
345         this.failBuildOnCVSS = failBuildOnCVSS;
346     }
347 
348     /**
349      * Get the value of autoUpdate.
350      *
351      * @return the value of autoUpdate
352      */
353     public boolean isAutoUpdate() {
354         return autoUpdate;
355     }
356 
357     /**
358      * Set the value of autoUpdate.
359      *
360      * @param autoUpdate new value of autoUpdate
361      */
362     public void setAutoUpdate(boolean autoUpdate) {
363         this.autoUpdate = autoUpdate;
364     }
365 
366     /**
367      * Get the value of updateOnly.
368      *
369      * @return the value of updateOnly
370      */
371     public boolean isUpdateOnly() {
372         return updateOnly;
373     }
374 
375     /**
376      * Set the value of updateOnly.
377      *
378      * @param updateOnly new value of updateOnly
379      */
380     public void setUpdateOnly(boolean updateOnly) {
381         this.updateOnly = updateOnly;
382     }
383 
384     /**
385      * Get the value of generateReport.
386      *
387      * @return the value of generateReport
388      */
389     public boolean isGenerateReport() {
390         return generateReport;
391     }
392 
393     /**
394      * Set the value of generateReport.
395      *
396      * @param generateReport new value of generateReport
397      */
398     public void setGenerateReport(boolean generateReport) {
399         this.generateReport = generateReport;
400     }
401 
402     /**
403      * Get the value of reportFormat.
404      *
405      * @return the value of reportFormat
406      */
407     public ReportGenerator.Format getReportFormat() {
408         return reportFormat;
409     }
410 
411     /**
412      * Set the value of reportFormat.
413      *
414      * @param reportFormat new value of reportFormat
415      */
416     public void setReportFormat(ReportGenerator.Format reportFormat) {
417         this.reportFormat = reportFormat;
418     }
419 
420     /**
421      * Get the value of proxyServer.
422      *
423      * @return the value of proxyServer
424      */
425     public String getProxyServer() {
426         return proxyServer;
427     }
428 
429     /**
430      * Set the value of proxyServer.
431      *
432      * @param proxyServer new value of proxyServer
433      */
434     public void setProxyServer(String proxyServer) {
435         this.proxyServer = proxyServer;
436     }
437 
438     /**
439      * Get the value of proxyPort.
440      *
441      * @return the value of proxyPort
442      */
443     public String getProxyPort() {
444         return proxyPort;
445     }
446 
447     /**
448      * Set the value of proxyPort.
449      *
450      * @param proxyPort new value of proxyPort
451      */
452     public void setProxyPort(String proxyPort) {
453         this.proxyPort = proxyPort;
454     }
455 
456     /**
457      * Get the value of proxyUsername.
458      *
459      * @return the value of proxyUsername
460      */
461     public String getProxyUsername() {
462         return proxyUsername;
463     }
464 
465     /**
466      * Set the value of proxyUsername.
467      *
468      * @param proxyUsername new value of proxyUsername
469      */
470     public void setProxyUsername(String proxyUsername) {
471         this.proxyUsername = proxyUsername;
472     }
473 
474     /**
475      * Get the value of proxyPassword.
476      *
477      * @return the value of proxyPassword
478      */
479     public String getProxyPassword() {
480         return proxyPassword;
481     }
482 
483     /**
484      * Set the value of proxyPassword.
485      *
486      * @param proxyPassword new value of proxyPassword
487      */
488     public void setProxyPassword(String proxyPassword) {
489         this.proxyPassword = proxyPassword;
490     }
491 
492     /**
493      * Get the value of connectionTimeout.
494      *
495      * @return the value of connectionTimeout
496      */
497     public String getConnectionTimeout() {
498         return connectionTimeout;
499     }
500 
501     /**
502      * Set the value of connectionTimeout.
503      *
504      * @param connectionTimeout new value of connectionTimeout
505      */
506     public void setConnectionTimeout(String connectionTimeout) {
507         this.connectionTimeout = connectionTimeout;
508     }
509 
510     /**
511      * Get the value of readTimeout.
512      *
513      * @return the value of readTimeout
514      */
515     public String getReadTimeout() {
516         return readTimeout;
517     }
518 
519     /**
520      * Set the value of readTimeout.
521      *
522      * @param readTimeout new value of readTimeout
523      */
524     public void setReadTimeout(String readTimeout) {
525         this.readTimeout = readTimeout;
526     }
527 
528     /**
529      * Get the value of logFile.
530      *
531      * @return the value of logFile
532      */
533     public String getLogFile() {
534         return logFile;
535     }
536 
537     /**
538      * Set the value of logFile.
539      *
540      * @param logFile new value of logFile
541      */
542     public void setLogFile(String logFile) {
543         this.logFile = logFile;
544     }
545 
546     /**
547      * Get the value of suppressionFile.
548      *
549      * @return the value of suppressionFile
550      */
551     public String getSuppressionFile() {
552         return suppressionFile;
553     }
554 
555     /**
556      * Set the value of suppressionFile.
557      *
558      * @param suppressionFile new value of suppressionFile
559      */
560     public void setSuppressionFile(String suppressionFile) {
561         this.suppressionFile = suppressionFile;
562     }
563 
564     /**
565      * Get the value of showSummary.
566      *
567      * @return the value of showSummary
568      */
569     public boolean isShowSummary() {
570         return showSummary;
571     }
572 
573     /**
574      * Set the value of showSummary.
575      *
576      * @param showSummary new value of showSummary
577      */
578     public void setShowSummary(boolean showSummary) {
579         this.showSummary = showSummary;
580     }
581 
582     /**
583      * Sets starting string that identifies CPEs that are qualified to be
584      * imported.
585      *
586      * @param cpeStartsWithFilter filters CPEs based on this starting string
587      * (i.e. cpe:/a: )
588      */
589     public void setCpeStartsWithFilter(String cpeStartsWithFilter) {
590         this.cpeStartsWithFilter = cpeStartsWithFilter;
591     }
592 
593     /**
594      * Returns the starting string that identifies CPEs that are qualified to be
595      * imported.
596      *
597      * @return the CPE starting filter (i.e. cpe:/a: )
598      */
599     public String getCpeStartsWithFilter() {
600         return cpeStartsWithFilter;
601     }
602 
603     /**
604      * Get the value of failOnUnusedSuppressionRule.
605      *
606      * @return the value of failOnUnusedSuppressionRule
607      */
608     public boolean isFailOnUnusedSuppressionRule() {
609         return failOnUnusedSuppressionRule;
610     }
611 
612     /**
613      * Set the value of failOnUnusedSuppressionRule.
614      *
615      * @param failOnUnusedSuppressionRule new value of failOnUnusedSuppressionRule
616      */
617     public void setFailOnUnusedSuppressionRule(boolean failOnUnusedSuppressionRule) {
618         this.failOnUnusedSuppressionRule = failOnUnusedSuppressionRule;
619     }
620 
621     /**
622      * Get the value of centralAnalyzerEnabled.
623      *
624      * @return the value of centralAnalyzerEnabled
625      */
626     public boolean isCentralAnalyzerEnabled() {
627         return centralAnalyzerEnabled;
628     }
629 
630     /**
631      * Set the value of centralAnalyzerEnabled.
632      *
633      * @param centralAnalyzerEnabled new value of centralAnalyzerEnabled
634      */
635     public void setCentralAnalyzerEnabled(boolean centralAnalyzerEnabled) {
636         this.centralAnalyzerEnabled = centralAnalyzerEnabled;
637     }
638 
639     /**
640      * Get the value of centralUrl.
641      *
642      * @return the value of centralUrl
643      */
644     public String getCentralUrl() {
645         return centralUrl;
646     }
647 
648     /**
649      * Set the value of centralUrl.
650      *
651      * @param centralUrl new value of centralUrl
652      */
653     public void setCentralUrl(String centralUrl) {
654         this.centralUrl = centralUrl;
655     }
656 
657     /**
658      * Get the value of nexusAnalyzerEnabled.
659      *
660      * @return the value of nexusAnalyzerEnabled
661      */
662     public boolean isNexusAnalyzerEnabled() {
663         return nexusAnalyzerEnabled;
664     }
665 
666     /**
667      * Set the value of nexusAnalyzerEnabled.
668      *
669      * @param nexusAnalyzerEnabled new value of nexusAnalyzerEnabled
670      */
671     public void setNexusAnalyzerEnabled(boolean nexusAnalyzerEnabled) {
672         this.nexusAnalyzerEnabled = nexusAnalyzerEnabled;
673     }
674 
675     /**
676      * Get the value of nexusUrl.
677      *
678      * @return the value of nexusUrl
679      */
680     public String getNexusUrl() {
681         return nexusUrl;
682     }
683 
684     /**
685      * Set the value of nexusUrl.
686      *
687      * @param nexusUrl new value of nexusUrl
688      */
689     public void setNexusUrl(String nexusUrl) {
690         this.nexusUrl = nexusUrl;
691     }
692 
693     /**
694      * Get the value of nexusUsesProxy.
695      *
696      * @return the value of nexusUsesProxy
697      */
698     public boolean isNexusUsesProxy() {
699         return nexusUsesProxy;
700     }
701 
702     /**
703      * Set the value of nexusUsesProxy.
704      *
705      * @param nexusUsesProxy new value of nexusUsesProxy
706      */
707     public void setNexusUsesProxy(boolean nexusUsesProxy) {
708         this.nexusUsesProxy = nexusUsesProxy;
709     }
710 
711     /**
712      * Get the value of databaseDriverName.
713      *
714      * @return the value of databaseDriverName
715      */
716     public String getDatabaseDriverName() {
717         return databaseDriverName;
718     }
719 
720     /**
721      * Set the value of databaseDriverName.
722      *
723      * @param databaseDriverName new value of databaseDriverName
724      */
725     public void setDatabaseDriverName(String databaseDriverName) {
726         this.databaseDriverName = databaseDriverName;
727     }
728 
729     /**
730      * Get the value of databaseDriverPath.
731      *
732      * @return the value of databaseDriverPath
733      */
734     public String getDatabaseDriverPath() {
735         return databaseDriverPath;
736     }
737 
738     /**
739      * Set the value of databaseDriverPath.
740      *
741      * @param databaseDriverPath new value of databaseDriverPath
742      */
743     public void setDatabaseDriverPath(String databaseDriverPath) {
744         this.databaseDriverPath = databaseDriverPath;
745     }
746 
747     /**
748      * Get the value of connectionString.
749      *
750      * @return the value of connectionString
751      */
752     public String getConnectionString() {
753         return connectionString;
754     }
755 
756     /**
757      * Set the value of connectionString.
758      *
759      * @param connectionString new value of connectionString
760      */
761     public void setConnectionString(String connectionString) {
762         this.connectionString = connectionString;
763     }
764 
765     /**
766      * Get the value of databaseUser.
767      *
768      * @return the value of databaseUser
769      */
770     public String getDatabaseUser() {
771         return databaseUser;
772     }
773 
774     /**
775      * Set the value of databaseUser.
776      *
777      * @param databaseUser new value of databaseUser
778      */
779     public void setDatabaseUser(String databaseUser) {
780         this.databaseUser = databaseUser;
781     }
782 
783     /**
784      * Get the value of databasePassword.
785      *
786      * @return the value of databasePassword
787      */
788     public String getDatabasePassword() {
789         return databasePassword;
790     }
791 
792     /**
793      * Set the value of databasePassword.
794      *
795      * @param databasePassword new value of databasePassword
796      */
797     public void setDatabasePassword(String databasePassword) {
798         this.databasePassword = databasePassword;
799     }
800 
801     /**
802      * Get the value of zipExtensions.
803      *
804      * @return the value of zipExtensions
805      */
806     public String getZipExtensions() {
807         return zipExtensions;
808     }
809 
810     /**
811      * Set the value of zipExtensions.
812      *
813      * @param zipExtensions new value of zipExtensions
814      */
815     public void setZipExtensions(String zipExtensions) {
816         this.zipExtensions = zipExtensions;
817     }
818 
819     /**
820      * Get the value of pathToCore.
821      *
822      * @return the value of pathToCore
823      */
824     public String getPathToDotnetCore() {
825         return pathToCore;
826     }
827 
828     /**
829      * Set the value of pathToCore.
830      *
831      * @param pathToCore new value of pathToCore
832      */
833     public void setPathToDotnetCore(String pathToCore) {
834         this.pathToCore = pathToCore;
835     }
836 
837     /**
838      * Get the value of propertiesFilePath.
839      *
840      * @return the value of propertiesFilePath
841      */
842     public String getPropertiesFilePath() {
843         return propertiesFilePath;
844     }
845 
846     /**
847      * Set the value of propertiesFilePath.
848      *
849      * @param propertiesFilePath new value of propertiesFilePath
850      */
851     public void setPropertiesFilePath(String propertiesFilePath) {
852         this.propertiesFilePath = propertiesFilePath;
853     }
854     //</editor-fold>
855 
856     /**
857      * Executes the Dependency-Check on the dependent libraries. <b>Note</b>,
858      * the engine object returned from this method must be closed by calling
859      * `close()`
860      *
861      * @return the Engine used to scan the dependencies.
862      * @throws ExceptionCollection a collection of one or more exceptions that
863      * occurred during analysis.
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      * Generates the reports for a given dependency-check engine.
893      *
894      * @param engine a dependency-check engine
895      * @param outDirectory the directory to write the reports to
896      * @throws ScanAgentException thrown if there is an error generating the
897      * report
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      * Takes the properties supplied and updates the dependency-check settings.
910      * Additionally, this sets the system properties required to change the
911      * proxy server, port, and connection timeout.
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      * Executes the dependency-check and generates the report.
961      *
962      * @return a reference to the engine used to perform the scan.
963      * @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if
964      * there is an exception executing the scan.
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      * Checks to see if a vulnerability has been identified with a CVSS score
998      * that is above the threshold set in the configuration.
999      *
1000      * @param dependencies the list of dependency objects
1001      * @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if
1002      * there is an exception executing the scan.
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                         //safety net to fail on any if for some reason the above misses on 0
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      * Generates a warning message listing a summary of dependencies and their
1055      * associated CPE and CVE entries.
1056      *
1057      * @param dependencies a list of dependency objects
1058      */
1059     public static void showSummary(Dependency[] dependencies) {
1060         showSummary(null, dependencies);
1061     }
1062 
1063     /**
1064      * Generates a warning message listing a summary of dependencies and their
1065      * associated CPE and CVE entries.
1066      *
1067      * @param projectName the name of the project
1068      * @param dependencies a list of dependency objects
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 }