View Javadoc
1   /*
2    * This file is part of dependency-check-ant.
3    *
4    * Dependency-check-ant is free software: you can redistribute it and/or modify it
5    * under the terms of the GNU General Public License as published by the Free
6    * Software Foundation, either version 3 of the License, or (at your option) any
7    * later version.
8    *
9    * Dependency-check-ant is distributed in the hope that it will be useful, but
10   * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11   * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12   * details.
13   *
14   * You should have received a copy of the GNU General Public License along with
15   * dependency-check-ant. If not, see http://www.gnu.org/licenses/.
16   *
17   * Copyright (c) 2013 Jeremy Long. All Rights Reserved.
18   */
19  package org.owasp.dependencycheck.taskdefs;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.util.List;
25  import java.util.logging.Level;
26  import java.util.logging.Logger;
27  import org.apache.tools.ant.BuildException;
28  import org.apache.tools.ant.Task;
29  import org.apache.tools.ant.types.EnumeratedAttribute;
30  import org.apache.tools.ant.types.Reference;
31  import org.apache.tools.ant.types.Resource;
32  import org.apache.tools.ant.types.ResourceCollection;
33  import org.apache.tools.ant.types.resources.FileProvider;
34  import org.apache.tools.ant.types.resources.Resources;
35  import org.owasp.dependencycheck.Engine;
36  import org.owasp.dependencycheck.dependency.Dependency;
37  import org.owasp.dependencycheck.dependency.Identifier;
38  import org.owasp.dependencycheck.dependency.Vulnerability;
39  import org.owasp.dependencycheck.reporting.ReportGenerator;
40  import org.owasp.dependencycheck.reporting.ReportGenerator.Format;
41  import org.owasp.dependencycheck.utils.LogUtils;
42  import org.owasp.dependencycheck.utils.Settings;
43  
44  /**
45   * An Ant task definition to execute dependency-check during an Ant build.
46   *
47   * @author Jeremy Long <jeremy.long@owasp.org>
48   */
49  public class DependencyCheckTask extends Task {
50  
51      /**
52       * The properties file location.
53       */
54      private static final String PROPERTIES_FILE = "task.properties";
55      /**
56       * Name of the logging properties file.
57       */
58      private static final String LOG_PROPERTIES_FILE = "log.properties";
59      /**
60       * System specific new line character.
61       */
62      private static final String NEW_LINE = System.getProperty("line.separator", "\n").intern();
63  
64      /**
65       * Construct a new DependencyCheckTask.
66       */
67      public DependencyCheckTask() {
68          super();
69      }
70      //The following code was copied Apache Ant PathConvert
71      //BEGIN COPY from org.apache.tools.ant.taskdefs.PathConvert
72      /**
73       * Path to be converted
74       */
75      private Resources path = null;
76      /**
77       * Reference to path/fileset to convert
78       */
79      private Reference refid = null;
80  
81      /**
82       * Add an arbitrary ResourceCollection.
83       *
84       * @param rc the ResourceCollection to add.
85       * @since Ant 1.7
86       */
87      public void add(ResourceCollection rc) {
88          if (isReference()) {
89              throw new BuildException("Nested elements are not allowed when using the refid attribute.");
90          }
91          getPath().add(rc);
92      }
93  
94      /**
95       * Returns the path. If the path has not been initialized yet, this class is
96       * synchronized, and will instantiate the path object.
97       *
98       * @return the path
99       */
100     private synchronized Resources getPath() {
101         if (path == null) {
102             path = new Resources(getProject());
103             path.setCache(true);
104         }
105         return path;
106     }
107 
108     /**
109      * Learn whether the refid attribute of this element been set.
110      *
111      * @return true if refid is valid.
112      */
113     public boolean isReference() {
114         return refid != null;
115     }
116 
117     /**
118      * Add a reference to a Path, FileSet, DirSet, or FileList defined
119      * elsewhere.
120      *
121      * @param r the reference to a path, fileset, dirset or filelist.
122      */
123     public void setRefid(Reference r) {
124         if (path != null) {
125             throw new BuildException("Nested elements are not allowed when using the refid attribute.");
126         }
127         refid = r;
128     }
129 
130     /**
131      * If this is a reference, this method will add the referenced resource
132      * collection to the collection of paths.
133      *
134      * @throws BuildException if the reference is not to a resource collection
135      */
136     private void dealWithReferences() throws BuildException {
137         if (isReference()) {
138             final Object o = refid.getReferencedObject(getProject());
139             if (!(o instanceof ResourceCollection)) {
140                 throw new BuildException("refid '" + refid.getRefId()
141                         + "' does not refer to a resource collection.");
142             }
143             getPath().add((ResourceCollection) o);
144         }
145     }
146     // END COPY from org.apache.tools.ant.taskdefs
147     /**
148      * The application name for the report.
149      */
150     private String applicationName = "Dependency-Check";
151 
152     /**
153      * Get the value of applicationName.
154      *
155      * @return the value of applicationName
156      */
157     public String getApplicationName() {
158         return applicationName;
159     }
160 
161     /**
162      * Set the value of applicationName.
163      *
164      * @param applicationName new value of applicationName
165      */
166     public void setApplicationName(String applicationName) {
167         this.applicationName = applicationName;
168     }
169     /**
170      * The location of the data directory that contains
171      */
172     private String dataDirectory = null;
173 
174     /**
175      * Get the value of dataDirectory.
176      *
177      * @return the value of dataDirectory
178      */
179     public String getDataDirectory() {
180         return dataDirectory;
181     }
182 
183     /**
184      * Set the value of dataDirectory.
185      *
186      * @param dataDirectory new value of dataDirectory
187      */
188     public void setDataDirectory(String dataDirectory) {
189         this.dataDirectory = dataDirectory;
190     }
191     /**
192      * Specifies the destination directory for the generated Dependency-Check
193      * report.
194      */
195     private String reportOutputDirectory = ".";
196 
197     /**
198      * Get the value of reportOutputDirectory.
199      *
200      * @return the value of reportOutputDirectory
201      */
202     public String getReportOutputDirectory() {
203         return reportOutputDirectory;
204     }
205 
206     /**
207      * Set the value of reportOutputDirectory.
208      *
209      * @param reportOutputDirectory new value of reportOutputDirectory
210      */
211     public void setReportOutputDirectory(String reportOutputDirectory) {
212         this.reportOutputDirectory = reportOutputDirectory;
213     }
214     /**
215      * Specifies if the build should be failed if a CVSS score above a specified
216      * level is identified. The default is 11 which means since the CVSS scores
217      * are 0-10, by default the build will never fail and the CVSS score is set
218      * to 11. The valid range for the fail build on CVSS is 0 to 11, where
219      * anything above 10 will not cause the build to fail.
220      */
221     private float failBuildOnCVSS = 11;
222 
223     /**
224      * Get the value of failBuildOnCVSS.
225      *
226      * @return the value of failBuildOnCVSS
227      */
228     public float getFailBuildOnCVSS() {
229         return failBuildOnCVSS;
230     }
231 
232     /**
233      * Set the value of failBuildOnCVSS.
234      *
235      * @param failBuildOnCVSS new value of failBuildOnCVSS
236      */
237     public void setFailBuildOnCVSS(float failBuildOnCVSS) {
238         this.failBuildOnCVSS = failBuildOnCVSS;
239     }
240     /**
241      * Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not
242      * recommended that this be turned to false. Default is true.
243      */
244     private boolean autoUpdate = true;
245 
246     /**
247      * Get the value of autoUpdate.
248      *
249      * @return the value of autoUpdate
250      */
251     public boolean isAutoUpdate() {
252         return autoUpdate;
253     }
254 
255     /**
256      * Set the value of autoUpdate.
257      *
258      * @param autoUpdate new value of autoUpdate
259      */
260     public void setAutoUpdate(boolean autoUpdate) {
261         this.autoUpdate = autoUpdate;
262     }
263     /**
264      * The report format to be generated (HTML, XML, VULN, ALL). This
265      * configuration option has no affect if using this within the Site plugin
266      * unless the externalReport is set to true. Default is HTML.
267      */
268     private String reportFormat = "HTML";
269 
270     /**
271      * Get the value of reportFormat.
272      *
273      * @return the value of reportFormat
274      */
275     public String getReportFormat() {
276         return reportFormat;
277     }
278 
279     /**
280      * Set the value of reportFormat.
281      *
282      * @param reportFormat new value of reportFormat
283      */
284     public void setReportFormat(ReportFormats reportFormat) {
285         this.reportFormat = reportFormat.getValue();
286     }
287     /**
288      * The Proxy URL.
289      */
290     private String proxyUrl;
291 
292     /**
293      * Get the value of proxyUrl.
294      *
295      * @return the value of proxyUrl
296      */
297     public String getProxyUrl() {
298         return proxyUrl;
299     }
300 
301     /**
302      * Set the value of proxyUrl.
303      *
304      * @param proxyUrl new value of proxyUrl
305      */
306     public void setProxyUrl(String proxyUrl) {
307         this.proxyUrl = proxyUrl;
308     }
309     /**
310      * The Proxy Port.
311      */
312     private String proxyPort;
313 
314     /**
315      * Get the value of proxyPort.
316      *
317      * @return the value of proxyPort
318      */
319     public String getProxyPort() {
320         return proxyPort;
321     }
322 
323     /**
324      * Set the value of proxyPort.
325      *
326      * @param proxyPort new value of proxyPort
327      */
328     public void setProxyPort(String proxyPort) {
329         this.proxyPort = proxyPort;
330     }
331     /**
332      * The Proxy username.
333      */
334     private String proxyUsername;
335 
336     /**
337      * Get the value of proxyUsername.
338      *
339      * @return the value of proxyUsername
340      */
341     public String getProxyUsername() {
342         return proxyUsername;
343     }
344 
345     /**
346      * Set the value of proxyUsername.
347      *
348      * @param proxyUsername new value of proxyUsername
349      */
350     public void setProxyUsername(String proxyUsername) {
351         this.proxyUsername = proxyUsername;
352     }
353     /**
354      * The Proxy password.
355      */
356     private String proxyPassword;
357 
358     /**
359      * Get the value of proxyPassword.
360      *
361      * @return the value of proxyPassword
362      */
363     public String getProxyPassword() {
364         return proxyPassword;
365     }
366 
367     /**
368      * Set the value of proxyPassword.
369      *
370      * @param proxyPassword new value of proxyPassword
371      */
372     public void setProxyPassword(String proxyPassword) {
373         this.proxyPassword = proxyPassword;
374     }
375     /**
376      * The Connection Timeout.
377      */
378     private String connectionTimeout;
379 
380     /**
381      * Get the value of connectionTimeout.
382      *
383      * @return the value of connectionTimeout
384      */
385     public String getConnectionTimeout() {
386         return connectionTimeout;
387     }
388 
389     /**
390      * Set the value of connectionTimeout.
391      *
392      * @param connectionTimeout new value of connectionTimeout
393      */
394     public void setConnectionTimeout(String connectionTimeout) {
395         this.connectionTimeout = connectionTimeout;
396     }
397     /**
398      * The file path used for verbose logging.
399      */
400     private String logFile = null;
401 
402     /**
403      * Get the value of logFile.
404      *
405      * @return the value of logFile
406      */
407     public String getLogFile() {
408         return logFile;
409     }
410 
411     /**
412      * Set the value of logFile.
413      *
414      * @param logFile new value of logFile
415      */
416     public void setLogFile(String logFile) {
417         this.logFile = logFile;
418     }
419     /**
420      * The path to the suppression file.
421      */
422     private String suppressionFile;
423 
424     /**
425      * Get the value of suppressionFile.
426      *
427      * @return the value of suppressionFile
428      */
429     public String getSuppressionFile() {
430         return suppressionFile;
431     }
432 
433     /**
434      * Set the value of suppressionFile.
435      *
436      * @param suppressionFile new value of suppressionFile
437      */
438     public void setSuppressionFile(String suppressionFile) {
439         this.suppressionFile = suppressionFile;
440     }
441     /**
442      * flag indicating whether or not to show a summary of findings.
443      */
444     private boolean showSummary = true;
445 
446     /**
447      * Get the value of showSummary.
448      *
449      * @return the value of showSummary
450      */
451     public boolean isShowSummary() {
452         return showSummary;
453     }
454 
455     /**
456      * Set the value of showSummary.
457      *
458      * @param showSummary new value of showSummary
459      */
460     public void setShowSummary(boolean showSummary) {
461         this.showSummary = showSummary;
462     }
463 
464     @Override
465     public void execute() throws BuildException {
466         final InputStream in = DependencyCheckTask.class.getClassLoader().getResourceAsStream(LOG_PROPERTIES_FILE);
467         LogUtils.prepareLogger(in, logFile);
468 
469         dealWithReferences();
470         validateConfiguration();
471         populateSettings();
472 
473         final Engine engine = new Engine();
474         for (Resource resource : path) {
475             final FileProvider provider = resource.as(FileProvider.class);
476             if (provider != null) {
477                 final File file = provider.getFile();
478                 if (file != null && file.exists()) {
479                     engine.scan(file);
480                 }
481             }
482         }
483         try {
484             engine.analyzeDependencies();
485             final ReportGenerator reporter = new ReportGenerator(applicationName, engine.getDependencies(), engine.getAnalyzers());
486             reporter.generateReports(reportOutputDirectory, reportFormat);
487 
488             if (this.failBuildOnCVSS <= 10) {
489                 checkForFailure(engine.getDependencies());
490             }
491             if (this.showSummary) {
492                 showSummary(engine.getDependencies());
493             }
494         } catch (IOException ex) {
495             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.FINE, "Unable to generate dependency-check report", ex);
496             throw new BuildException("Unable to generate dependency-check report", ex);
497         } catch (Exception ex) {
498             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.FINE, "An exception occurred; unable to continue task", ex);
499             throw new BuildException("An exception occurred; unable to continue task", ex);
500         }
501     }
502 
503     /**
504      * Validate the configuration to ensure the parameters have been properly
505      * configured/initialized.
506      *
507      * @throws BuildException if the task was not configured correctly.
508      */
509     private void validateConfiguration() throws BuildException {
510         if (path == null) {
511             throw new BuildException("No project dependencies have been defined to analyze.");
512         }
513         if (failBuildOnCVSS < 0 || failBuildOnCVSS > 11) {
514             throw new BuildException("Invalid configuration, failBuildOnCVSS must be between 0 and 11.");
515         }
516     }
517 
518     /**
519      * Takes the properties supplied and updates the dependency-check settings.
520      * Additionally, this sets the system properties required to change the
521      * proxy url, port, and connection timeout.
522      */
523     private void populateSettings() {
524         InputStream taskProperties = null;
525         try {
526             taskProperties = this.getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE);
527             Settings.mergeProperties(taskProperties);
528         } catch (IOException ex) {
529             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.WARNING, "Unable to load the dependency-check ant task.properties file.");
530             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.FINE, null, ex);
531         } finally {
532             if (taskProperties != null) {
533                 try {
534                     taskProperties.close();
535                 } catch (IOException ex) {
536                     Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.FINEST, null, ex);
537                 }
538             }
539         }
540         if (dataDirectory != null) {
541             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory);
542         } else {
543             final File jarPath = new File(DependencyCheckTask.class.getProtectionDomain().getCodeSource().getLocation().getPath());
544             final File base = jarPath.getParentFile();
545             final String sub = Settings.getString(Settings.KEYS.DATA_DIRECTORY);
546             final File dataDir = new File(base, sub);
547             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
548         }
549 
550         Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
551 
552         if (proxyUrl != null && !proxyUrl.isEmpty()) {
553             Settings.setString(Settings.KEYS.PROXY_URL, proxyUrl);
554         }
555         if (proxyPort != null && !proxyPort.isEmpty()) {
556             Settings.setString(Settings.KEYS.PROXY_PORT, proxyPort);
557         }
558         if (proxyUsername != null && !proxyUsername.isEmpty()) {
559             Settings.setString(Settings.KEYS.PROXY_USERNAME, proxyUsername);
560         }
561         if (proxyPassword != null && !proxyPassword.isEmpty()) {
562             Settings.setString(Settings.KEYS.PROXY_PASSWORD, proxyPassword);
563         }
564         if (connectionTimeout != null && !connectionTimeout.isEmpty()) {
565             Settings.setString(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout);
566         }
567         if (suppressionFile != null && !suppressionFile.isEmpty()) {
568             Settings.setString(Settings.KEYS.SUPPRESSION_FILE, suppressionFile);
569         }
570     }
571 
572     /**
573      * Checks to see if a vulnerability has been identified with a CVSS score
574      * that is above the threshold set in the configuration.
575      *
576      * @param dependencies the list of dependency objects
577      * @throws BuildException thrown if a CVSS score is found that is higher
578      * then the threshold set
579      */
580     private void checkForFailure(List<Dependency> dependencies) throws BuildException {
581         final StringBuilder ids = new StringBuilder();
582         for (Dependency d : dependencies) {
583             for (Vulnerability v : d.getVulnerabilities()) {
584                 if (v.getCvssScore() >= failBuildOnCVSS) {
585                     if (ids.length() == 0) {
586                         ids.append(v.getName());
587                     } else {
588                         ids.append(", ").append(v.getName());
589                     }
590                 }
591             }
592         }
593         if (ids.length() > 0) {
594             final String msg = String.format("%n%nDependency-Check Failure:%n"
595                     + "One or more dependencies were identified with vulnerabilities that have a CVSS score greater then '%.1f': %s%n"
596                     + "See the dependency-check report for more details.%n%n", failBuildOnCVSS, ids.toString());
597             throw new BuildException(msg);
598         }
599     }
600 
601     /**
602      * Generates a warning message listing a summary of dependencies and their
603      * associated CPE and CVE entries.
604      *
605      * @param dependencies a list of dependency objects
606      */
607     private void showSummary(List<Dependency> dependencies) {
608         final StringBuilder summary = new StringBuilder();
609         for (Dependency d : dependencies) {
610             boolean firstEntry = true;
611             final StringBuilder ids = new StringBuilder();
612             for (Vulnerability v : d.getVulnerabilities()) {
613                 if (firstEntry) {
614                     firstEntry = false;
615                 } else {
616                     ids.append(", ");
617                 }
618                 ids.append(v.getName());
619             }
620             if (ids.length() > 0) {
621                 summary.append(d.getFileName()).append(" (");
622                 firstEntry = true;
623                 for (Identifier id : d.getIdentifiers()) {
624                     if (firstEntry) {
625                         firstEntry = false;
626                     } else {
627                         summary.append(", ");
628                     }
629                     summary.append(id.getValue());
630                 }
631                 summary.append(") : ").append(ids).append(NEW_LINE);
632             }
633         }
634         if (summary.length() > 0) {
635             final String msg = String.format("%n%n"
636                     + "One or more dependencies were identified with known vulnerabilities:%n%n%s"
637                     + "%n%nSee the dependency-check report for more details.%n%n", summary.toString());
638             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.WARNING, msg);
639         }
640     }
641 
642     /**
643      * An enumeration of supported report formats: "ALL", "HTML", "XML", "VULN",
644      * etc..
645      */
646     public static class ReportFormats extends EnumeratedAttribute {
647 
648         /**
649          * Returns the list of values for the report format.
650          *
651          * @return the list of values for the report format
652          */
653         @Override
654         public String[] getValues() {
655             int i = 0;
656             final Format[] formats = Format.values();
657             final String[] values = new String[formats.length];
658             for (Format format : formats) {
659                 values[i++] = format.name();
660             }
661             return values;
662         }
663     }
664 }