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