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