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.LogManager;
27  import java.util.logging.Logger;
28  import org.apache.tools.ant.BuildException;
29  import org.apache.tools.ant.Task;
30  import org.apache.tools.ant.types.EnumeratedAttribute;
31  import org.apache.tools.ant.types.Reference;
32  import org.apache.tools.ant.types.Resource;
33  import org.apache.tools.ant.types.ResourceCollection;
34  import org.apache.tools.ant.types.resources.FileProvider;
35  import org.apache.tools.ant.types.resources.Resources;
36  import org.owasp.dependencycheck.Engine;
37  import org.owasp.dependencycheck.dependency.Dependency;
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.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 Connection Timeout.
328      */
329     private String connectionTimeout;
330 
331     /**
332      * Get the value of connectionTimeout.
333      *
334      * @return the value of connectionTimeout
335      */
336     public String getConnectionTimeout() {
337         return connectionTimeout;
338     }
339 
340     /**
341      * Set the value of connectionTimeout.
342      *
343      * @param connectionTimeout new value of connectionTimeout
344      */
345     public void setConnectionTimeout(String connectionTimeout) {
346         this.connectionTimeout = connectionTimeout;
347     }
348 
349     /**
350      * Configures the logger for use by the application.
351      */
352     private static void prepareLogger() {
353         InputStream in = null;
354         try {
355             in = DependencyCheckTask.class.getClassLoader().getResourceAsStream(LOG_PROPERTIES_FILE);
356             LogManager.getLogManager().reset();
357             LogManager.getLogManager().readConfiguration(in);
358             //TODO add code to disable fine grained log file.
359 //            Logger logger = LogManager.getLogManager().getLogger("");
360 //            for (Handler h : logger.getHandlers()) {
361 //                if (h.getFormatter(). h.toString());
362 //            }
363         } catch (IOException ex) {
364             System.err.println(ex.toString());
365             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.SEVERE, null, ex);
366         } catch (SecurityException ex) {
367             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.SEVERE, null, ex);
368         } finally {
369             if (in != null) {
370                 try {
371                     in.close();
372                 } catch (Exception ex) {
373                     //noinspection UnusedAssignment
374                     in = null;
375                 }
376             }
377         }
378     }
379 
380     @Override
381     public void execute() throws BuildException {
382         prepareLogger();
383 
384         dealWithReferences();
385         validateConfiguration();
386         populateSettings();
387 
388         final Engine engine = new Engine();
389         for (Resource resource : path) {
390             final FileProvider provider = resource.as(FileProvider.class);
391             if (provider != null) {
392                 final File file = provider.getFile();
393                 if (file != null && file.exists()) {
394                     engine.scan(file);
395                 }
396             }
397         }
398         try {
399             engine.analyzeDependencies();
400             final ReportGenerator reporter = new ReportGenerator(applicationName, engine.getDependencies(), engine.getAnalyzers());
401             reporter.generateReports(reportOutputDirectory, reportFormat);
402 
403             if (this.failBuildOnCVSS <= 10) {
404                 checkForFailure(engine.getDependencies());
405             }
406         } catch (IOException ex) {
407             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.FINE, null, ex);
408             throw new BuildException("Unable to generate dependency-check report", ex);
409         } catch (Exception ex) {
410             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.SEVERE, null, ex);
411             throw new BuildException("An exception occured; unable to continue task", ex);
412         }
413     }
414 
415     /**
416      * Validate the configuration to ensure the parameters have been properly
417      * configured/initialized.
418      *
419      * @throws BuildException if the task was not configured correctly.
420      */
421     private void validateConfiguration() throws BuildException {
422         if (path == null) {
423             throw new BuildException("No project dependencies have been defined to analyze.");
424         }
425         if (failBuildOnCVSS < 0 || failBuildOnCVSS > 11) {
426             throw new BuildException("Invalid configuration, failBuildOnCVSS must be between 0 and 11.");
427         }
428     }
429 
430     /**
431      * Takes the properties supplied and updates the dependency-check settings.
432      * Additionally, this sets the system properties required to change the
433      * proxy url, port, and connection timeout.
434      */
435     private void populateSettings() {
436         InputStream taskProperties = null;
437         try {
438             taskProperties = this.getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE);
439             Settings.mergeProperties(taskProperties);
440         } catch (IOException ex) {
441             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.WARNING, "Unable to load the dependency-check ant task.properties file.");
442             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.FINE, null, ex);
443         } finally {
444             if (taskProperties != null) {
445                 try {
446                     taskProperties.close();
447                 } catch (IOException ex) {
448                     Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.FINEST, null, ex);
449                 }
450             }
451         }
452         if (dataDirectory != null) {
453             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory);
454         } else {
455             final File jarPath = new File(DependencyCheckTask.class.getProtectionDomain().getCodeSource().getLocation().getPath());
456             final File base = jarPath.getParentFile();
457             final String sub = Settings.getString(Settings.KEYS.DATA_DIRECTORY);
458             final File dataDir = new File(base, sub);
459             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
460         }
461 
462         Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
463 
464         if (proxyUrl != null && !proxyUrl.isEmpty()) {
465             Settings.setString(Settings.KEYS.PROXY_URL, proxyUrl);
466         }
467         if (proxyPort != null && !proxyPort.isEmpty()) {
468             Settings.setString(Settings.KEYS.PROXY_PORT, proxyPort);
469         }
470         if (connectionTimeout != null && !connectionTimeout.isEmpty()) {
471             Settings.setString(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout);
472         }
473     }
474 
475     /**
476      * Checks to see if a vulnerability has been identified with a CVSS score
477      * that is above the threshold set in the configuration.
478      *
479      * @param dependencies the list of dependency objects
480      * @throws BuildException thrown if a CVSS score is found that is higher
481      * then the threshold set
482      */
483     private void checkForFailure(List<Dependency> dependencies) throws BuildException {
484         final StringBuilder ids = new StringBuilder();
485         for (Dependency d : dependencies) {
486             for (Vulnerability v : d.getVulnerabilities()) {
487                 if (v.getCvssScore() >= failBuildOnCVSS) {
488                     if (ids.length() == 0) {
489                         ids.append(v.getName());
490                     } else {
491                         ids.append(", ").append(v.getName());
492                     }
493                 }
494             }
495         }
496         if (ids.length() > 0) {
497             final String msg = String.format("%n%nDependency-Check Failure:%n"
498                     + "One or more dependencies were identified with vulnerabilities that have a CVSS score greater then '%.1f': %s%n"
499                     + "See the dependency-check report for more details.%n%n", failBuildOnCVSS, ids.toString());
500             throw new BuildException(msg);
501         }
502     }
503 
504     /**
505      * An enumeration of supported report formats: "ALL", "HTML", "XML", "VULN",
506      * etc..
507      */
508     public static class ReportFormats extends EnumeratedAttribute {
509 
510         /**
511          * Returns the list of values for the report format.
512          *
513          * @return the list of values for the report format
514          */
515         public String[] getValues() {
516             int i = 0;
517             final Format[] formats = Format.values();
518             final String[] values = new String[formats.length];
519             for (Format format : formats) {
520                 values[i++] = format.name();
521             }
522             return values;
523         }
524     }
525 }