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