diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java new file mode 100644 index 000000000..25a6d520b --- /dev/null +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java @@ -0,0 +1,218 @@ +/* + * This file is part of dependency-check-maven. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2013 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.maven; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; +import org.owasp.dependencycheck.analyzer.DependencyBundlingAnalyzer; +import org.owasp.dependencycheck.analyzer.exception.AnalysisException; +import org.owasp.dependencycheck.data.nexus.MavenArtifact; +import org.owasp.dependencycheck.data.nvdcve.DatabaseException; +import org.owasp.dependencycheck.dependency.Confidence; +import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.utils.Settings; + +/** + * Maven Plugin that checks project dependencies and the dependencies of all child modules to see if they have any known + * published vulnerabilities. + * + * @author Jeremy Long + */ +@Mojo( + name = "aggregate", + defaultPhase = LifecyclePhase.SITE, + aggregator = true, + threadSafe = true, + requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, + requiresOnline = true +) +public class AggregateMojo extends BaseDependencyCheckMojo { + + /** + * Logger field reference. + */ + private static final Logger LOGGER = Logger.getLogger(AggregateMojo.class.getName()); + + @Override + public void runCheck() throws MojoExecutionException { + final Engine engine = generateDataFile(); + if (getProject() == getReactorProjects().get(getReactorProjects().size() - 1)) { + final Map> children = buildAggregateInfo(); + + for (MavenProject current : getReactorProjects()) { + List dependencies = readDataFile(current); + if (dependencies == null) { + dependencies = new ArrayList(); + } + List childProjects = getAllChildren(current, children); + //check for orchestration build - execution root with no children or dependencies + if (dependencies.isEmpty() && childProjects.isEmpty() && current.isExecutionRoot()) { + childProjects = new ArrayList(getReactorProjects().size()); + childProjects.addAll(getReactorProjects()); + } + + for (MavenProject reportOn : childProjects) { + List childDeps = readDataFile(reportOn); + if (childDeps != null && !childDeps.isEmpty()) { + dependencies.addAll(childDeps); + } + } + + engine.getDependencies().clear(); + engine.getDependencies().addAll(dependencies); + final DependencyBundlingAnalyzer bundler = new DependencyBundlingAnalyzer(); + try { + bundler.analyze(null, engine); + } catch (AnalysisException ex) { + LOGGER.log(Level.WARNING, "An error occured grouping the dependencies; duplicate entries may exist in the report", ex); + LOGGER.log(Level.FINE, "Bundling Exception", ex); + } + + File outputDir = getCorrectOutputDirectory(current); + + writeReports(engine, current, outputDir); + } + } + engine.cleanup(); + Settings.cleanup(); + } + + /** + * Returns a list containing all the recursive, non-pom children of the given project, never null. + * + * @param project the parent project to collect the child project references + * @param childMap a map of the parent-child relationships + * @return a list of child projects + */ + protected List getAllChildren(MavenProject project, Map> childMap) { + final Set children = childMap.get(project); + if (children == null) { + return Collections.emptyList(); + } + final List result = new ArrayList(); + for (MavenProject child : children) { + if (isMultiModule(child)) { + result.addAll(getAllChildren(child, childMap)); + } else { + result.add(child); + } + } + return result; + } + + /** + * Test if the project has pom packaging + * + * @param mavenProject Project to test + * @return true if it has a pom packaging; otherwise false + */ + protected boolean isMultiModule(MavenProject mavenProject) { + return "pom".equals(mavenProject.getPackaging()); + } + + /** + * Builds the parent-child map. + */ + private Map> buildAggregateInfo() { + Map> parentChildMap = new HashMap>(); + for (MavenProject proj : getReactorProjects()) { + Set depList = parentChildMap.get(proj.getParent()); + if (depList == null) { + depList = new HashSet(); + parentChildMap.put(proj.getParent(), depList); + } + depList.add(proj); + } + return parentChildMap; + } + + protected Engine generateDataFile() throws MojoExecutionException { + final Engine engine; + try { + engine = initializeEngine(); + } catch (DatabaseException ex) { + Logger.getLogger(CheckMojo.class.getName()).log(Level.SEVERE, null, ex); + throw new MojoExecutionException("An exception occured connecting to the local database. Please see the log file for more details.", ex); + } + final Set artifacts = getProject().getArtifacts(); + for (Artifact a : artifacts) { + if (excludeFromScan(a)) { + continue; + } + final List deps = engine.scan(a.getFile().getAbsoluteFile()); + if (deps != null) { + if (deps.size() == 1) { + final Dependency d = deps.get(0); + if (d != null) { + final MavenArtifact ma = new MavenArtifact(a.getGroupId(), a.getArtifactId(), a.getVersion()); + d.addAsEvidence("pom", ma, Confidence.HIGHEST); + } + } else { + final String msg = String.format("More then 1 dependency was identified in first pass scan of '%s:%s:%s'", + a.getGroupId(), a.getArtifactId(), a.getVersion()); + LOGGER.info(msg); + } + } + } + engine.analyzeDependencies(); + writeDataFile(engine.getDependencies()); + return engine; + } + + @Override + public boolean canGenerateReport() { + return true; //aggregate always returns true for now - we can look at a more complicated/acurate solution later + } + + /** + * Returns the report name. + * + * @param locale the location + * @return the report name + */ + public String getName(Locale locale) { + return "dependency-check:aggregate"; + } + + /** + * Gets the description of the Dependency-Check report to be displayed in the Maven Generated Reports page. + * + * @param locale The Locale to get the description for + * @return the description + */ + public String getDescription(Locale locale) { + return "Generates an aggregate report of all child Maven projects providing details on any " + + "published vulnerabilities within project dependencies. This report is a best " + + "effort and may contain false positives and false negatives."; + } +} diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java new file mode 100644 index 000000000..6bb22ea0e --- /dev/null +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java @@ -0,0 +1,902 @@ +/* + * This file is part of dependency-check-maven. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2014 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.maven; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.doxia.sink.Sink; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.apache.maven.reporting.MavenReport; +import org.apache.maven.reporting.MavenReportException; +import org.apache.maven.settings.Proxy; +import org.owasp.dependencycheck.data.nvdcve.CveDB; +import org.owasp.dependencycheck.data.nvdcve.DatabaseException; +import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties; +import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.dependency.Identifier; +import org.owasp.dependencycheck.dependency.Vulnerability; +import org.owasp.dependencycheck.reporting.ReportGenerator; +import org.owasp.dependencycheck.utils.LogUtils; +import org.owasp.dependencycheck.utils.Settings; + +/** + * + * @author Jeremy Long + */ +public abstract class BaseDependencyCheckMojo extends AbstractMojo implements MavenReport { + + // + /** + * Logger field reference. + */ + private static final Logger LOGGER = Logger.getLogger(BaseDependencyCheckMojo.class.getName()); + /** + * The properties file location. + */ + private static final String PROPERTIES_FILE = "mojo.properties"; + /** + * Name of the logging properties file. + */ + private static final String LOG_PROPERTIES_FILE = "log.properties"; + /** + * System specific new line character. + */ + private static final String NEW_LINE = System.getProperty("line.separator", "\n").intern(); + /** + * Sets whether or not the external report format should be used. + */ + @Parameter(property = "metaFileName", defaultValue = "dependency-check.ser", required = true) + private String dataFileName; + + // + // + /** + * The Maven Project Object. + */ + @Component + private MavenProject project; + /** + * List of Maven project of the current build + */ + @Parameter(readonly = true, required = true, property = "reactorProjects") + private List reactorProjects; + /** + * The path to the verbose log. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "logFile", defaultValue = "") + private String logFile = null; + /** + * The output directory. This generally maps to "target". + */ + @Parameter(defaultValue = "${project.build.directory}", required = true) + private File outputDirectory; + /** + * Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11 + * which means since the CVSS scores are 0-10, by default the build will never fail. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "failBuildOnCVSS", defaultValue = "11", required = true) + private float failBuildOnCVSS = 11; + /** + * Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to + * false. Default is true. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "autoupdate", defaultValue = "true", required = true) + private boolean autoUpdate = true; + /** + * Generate aggregate reports in multi-module projects. + * + * @deprecated use the aggregate goal instead + */ + @Parameter(property = "aggregate", defaultValue = "false") + @Deprecated + private boolean aggregate; + /** + * The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this + * within the Site plug-in unless the externalReport is set to true. Default is HTML. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "format", defaultValue = "HTML", required = true) + private String format = "HTML"; + /** + * The Maven settings. + */ + @Parameter(property = "mavenSettings", defaultValue = "${settings}", required = false) + private org.apache.maven.settings.Settings mavenSettings; + + /** + * The maven settings proxy id. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "mavenSettingsProxyId", required = false) + private String mavenSettingsProxyId; + + /** + * The Connection Timeout. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "connectionTimeout", defaultValue = "", required = false) + private String connectionTimeout = null; + /** + * The path to the suppression file. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "suppressionFile", defaultValue = "", required = false) + private String suppressionFile = null; + /** + * Flag indicating whether or not to show a summary in the output. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "showSummary", defaultValue = "true", required = false) + private boolean showSummary = true; + + /** + * Whether or not the Jar Analyzer is enabled. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "jarAnalyzerEnabled", defaultValue = "true", required = false) + private boolean jarAnalyzerEnabled = true; + + /** + * Whether or not the Archive Analyzer is enabled. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "archiveAnalyzerEnabled", defaultValue = "true", required = false) + private boolean archiveAnalyzerEnabled = true; + + /** + * Whether or not the .NET Assembly Analyzer is enabled. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "assemblyAnalyzerEnabled", defaultValue = "true", required = false) + private boolean assemblyAnalyzerEnabled = true; + + /** + * Whether or not the .NET Nuspec Analyzer is enabled. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "nuspecAnalyzerEnabled", defaultValue = "true", required = false) + private boolean nuspecAnalyzerEnabled = true; + + /** + * Whether or not the Nexus Analyzer is enabled. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "nexusAnalyzerEnabled", defaultValue = "true", required = false) + private boolean nexusAnalyzerEnabled = true; + /** + * Whether or not the Nexus Analyzer is enabled. + */ + @Parameter(property = "nexusUrl", defaultValue = "", required = false) + private String nexusUrl; + /** + * Whether or not the configured proxy is used to connect to Nexus. + */ + @Parameter(property = "nexusUsesProxy", defaultValue = "true", required = false) + private boolean nexusUsesProxy = true; + /** + * The database connection string. + */ + @Parameter(property = "connectionString", defaultValue = "", required = false) + private String connectionString; + /** + * The database driver name. An example would be org.h2.Driver. + */ + @Parameter(property = "databaseDriverName", defaultValue = "", required = false) + private String databaseDriverName; + /** + * The path to the database driver if it is not on the class path. + */ + @Parameter(property = "databaseDriverPath", defaultValue = "", required = false) + private String databaseDriverPath; + /** + * The database user name. + */ + @Parameter(property = "databaseUser", defaultValue = "", required = false) + private String databaseUser; + /** + * The password to use when connecting to the database. + */ + @Parameter(property = "databasePassword", defaultValue = "", required = false) + private String databasePassword; + /** + * A comma-separated list of file extensions to add to analysis next to jar, zip, .... + */ + @Parameter(property = "zipExtensions", required = false) + private String zipExtensions; + /** + * Skip Analysis for Test Scope Dependencies. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "skipTestScope", defaultValue = "true", required = false) + private boolean skipTestScope = true; + /** + * Skip Analysis for Runtime Scope Dependencies. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "skipRuntimeScope", defaultValue = "false", required = false) + private boolean skipRuntimeScope = false; + /** + * Skip Analysis for Provided Scope Dependencies. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "skipProvidedScope", defaultValue = "false", required = false) + private boolean skipProvidedScope = false; + /** + * The data directory, hold DC SQL DB. + */ + @Parameter(property = "dataDirectory", defaultValue = "", required = false) + private String dataDirectory; + /** + * Data Mirror URL for CVE 1.2. + */ + @Parameter(property = "cveUrl12Modified", defaultValue = "", required = false) + private String cveUrl12Modified; + /** + * Data Mirror URL for CVE 2.0. + */ + @Parameter(property = "cveUrl20Modified", defaultValue = "", required = false) + private String cveUrl20Modified; + /** + * Base Data Mirror URL for CVE 1.2. + */ + @Parameter(property = "cveUrl12Base", defaultValue = "", required = false) + private String cveUrl12Base; + /** + * Data Mirror URL for CVE 2.0. + */ + @Parameter(property = "cveUrl20Base", defaultValue = "", required = false) + private String cveUrl20Base; + + /** + * The path to mono for .NET Assembly analysis on non-windows systems. + */ + @Parameter(property = "pathToMono", defaultValue = "", required = false) + private String pathToMono; + + /** + * The Proxy URL. + * + * @deprecated Please use mavenSettings instead + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "proxyUrl", defaultValue = "", required = false) + @Deprecated + private String proxyUrl = null; + /** + * Sets whether or not the external report format should be used. + * + * @deprecated the internal report is no longer supported + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "externalReport") + @Deprecated + private String externalReport = null; + + /** + * Specifies the destination directory for the generated Dependency-Check report. This generally maps to + * "target/site". + */ + @Parameter(property = "reportOutputDirectory", defaultValue = "${project.reporting.outputDirectory}", required = true) + private File reportOutputDirectory; + // + // + + /** + * Executes dependency-check. + * + * @throws MojoExecutionException thrown if there is an exception executing the mojo + * @throws MojoFailureException thrown if dependency-check failed the build + */ + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + validateAggregate(); + project.setContextValue(getOutputDirectoryContextKey(), this.outputDirectory); + runCheck(); + } + + private void validateAggregate() throws MojoExecutionException { + if (aggregate == true) { + String msg = "Aggregate configuration detected - as of dependency-check 1.2.8 this no longer works and has " + + "been deprecated. Please use the aggregate goal instead."; + LOGGER.warning(msg); + throw new MojoExecutionException(msg); + } + } + + /** + * Generates the Dependency-Check Site Report. + * + * @param sink the sink to write the report to + * @param locale the locale to use when generating the report + * @throws MavenReportException if a maven report exception occurs + * @deprecated use {@link #generate(org.apache.maven.doxia.sink.Sink, java.util.Locale) instead. + */ + @Deprecated + public final void generate(@SuppressWarnings("deprecation") org.codehaus.doxia.sink.Sink sink, Locale locale) throws MavenReportException { + generate((Sink) sink, locale); + } + + /** + * Generates the Dependency-Check Site Report. + * + * @param sink the sink to write the report to + * @param locale the locale to use when generating the report + * @throws MavenReportException if a maven report exception occurs + */ + public void generate(Sink sink, Locale locale) throws MavenReportException { + try { + validateAggregate(); + } catch (MojoExecutionException ex) { + throw new MavenReportException(ex.getMessage()); + } + project.setContextValue(getOutputDirectoryContextKey(), getReportOutputDirectory()); + try { + runCheck(); + } catch (MojoExecutionException ex) { + throw new MavenReportException(ex.getMessage(), ex); + } + } + + /** + * Returns the correct output directory depending on if a site is being executed or not. + * + * @return the directory to write the report(s) + * @throws MojoExecutionException thrown if there is an error loading the file path + */ + protected File getCorrectOutputDirectory() throws MojoExecutionException { + return getCorrectOutputDirectory(this.project); + } + + /** + * Returns the correct output directory depending on if a site is being executed or not. + * + * @param current the Maven project to get the output directory from + * @return the directory to write the report(s) + * @throws MojoExecutionException thrown if there is an error loading the file path + */ + protected File getCorrectOutputDirectory(MavenProject current) throws MojoExecutionException { + Object obj = current.getContextValue(getOutputDirectoryContextKey()); + if (obj != null && obj instanceof File) { + return (File) obj; + } else { + throw new MojoExecutionException(String.format("Unable to determine output directory for '%s'", current.getName())); + } + } + + /** + * Executes the dependency-check scan and generates the necassary report. + * + * @throws MojoExecutionException thrown if there is an exception running the scan + */ + public abstract void runCheck() throws MojoExecutionException; + + /** + * Sets the Reporting output directory. + * + * @param directory the output directory + */ + @Override + public void setReportOutputDirectory(File directory) { + reportOutputDirectory = directory; + } + + /** + * Returns the report output directory. + * + * @return the report output directory + */ + @Override + public File getReportOutputDirectory() { + return reportOutputDirectory; + } + + /** + * Returns the output directory. + * + * @return the output directory + */ + public File getOutputDirectory() { + return outputDirectory; + } + + /** + * Returns whether this is an external report. This method always returns true. + * + * @return true + */ + @Override + public final boolean isExternalReport() { + return true; + } + + /** + * Returns the output name. + * + * @return the output name + */ + public String getOutputName() { + if ("HTML".equalsIgnoreCase(this.format) || "ALL".equalsIgnoreCase(this.format)) { + return "dependency-check-report"; + } else if ("XML".equalsIgnoreCase(this.format)) { + return "dependency-check-report.xml#"; + } else if ("VULN".equalsIgnoreCase(this.format)) { + return "dependency-check-vulnerability"; + } else { + LOGGER.log(Level.WARNING, "Unknown report format used during site generation."); + return "dependency-check-report"; + } + } + + /** + * Returns the category name. + * + * @return the category name + */ + public String getCategoryName() { + return MavenReport.CATEGORY_PROJECT_REPORTS; + } + // + + /** + * Initializes a new Engine that can be used for scanning. + * + * @return a newly instantiated Engine + * @throws DatabaseException thrown if there is a database exception + */ + protected Engine initializeEngine() throws DatabaseException { + final InputStream in = BaseDependencyCheckMojo.class.getClassLoader().getResourceAsStream(LOG_PROPERTIES_FILE); + LogUtils.prepareLogger(in, logFile); + populateSettings(); + return new Engine(this.project, this.reactorProjects); + } + + /** + * Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system + * properties required to change the proxy url, port, and connection timeout. + */ + private void populateSettings() { + Settings.initialize(); + InputStream mojoProperties = null; + try { + mojoProperties = this.getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE); + Settings.mergeProperties(mojoProperties); + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "Unable to load the dependency-check ant task.properties file."); + LOGGER.log(Level.FINE, null, ex); + } finally { + if (mojoProperties != null) { + try { + mojoProperties.close(); + } catch (IOException ex) { + LOGGER.log(Level.FINEST, null, ex); + } + } + } + + Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate); + if (externalReport != null) { + LOGGER.warning("The 'externalReport' option was set; this configuration option has been removed. " + + "Please update the dependency-check-maven plugin's configuration"); + } + + if (proxyUrl != null && !proxyUrl.isEmpty()) { + LOGGER.warning("Deprecated configuration detected, proxyUrl will be ignored; use the maven settings " + "to configure the proxy instead"); + } + final Proxy proxy = getMavenProxy(); + if (proxy != null) { + Settings.setString(Settings.KEYS.PROXY_SERVER, proxy.getHost()); + Settings.setString(Settings.KEYS.PROXY_PORT, Integer.toString(proxy.getPort())); + final String userName = proxy.getUsername(); + final String password = proxy.getPassword(); + if (userName != null) { + Settings.setString(Settings.KEYS.PROXY_USERNAME, userName); + } + if (password != null) { + Settings.setString(Settings.KEYS.PROXY_PASSWORD, password); + } + + } + + if (connectionTimeout != null && !connectionTimeout.isEmpty()) { + Settings.setString(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout); + } + if (suppressionFile != null && !suppressionFile.isEmpty()) { + Settings.setString(Settings.KEYS.SUPPRESSION_FILE, suppressionFile); + } + + //File Type Analyzer Settings + //JAR ANALYZER + Settings.setBoolean(Settings.KEYS.ANALYZER_JAR_ENABLED, jarAnalyzerEnabled); + //NUSPEC ANALYZER + Settings.setBoolean(Settings.KEYS.ANALYZER_NUSPEC_ENABLED, nuspecAnalyzerEnabled); + //NEXUS ANALYZER + Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, nexusAnalyzerEnabled); + if (nexusUrl != null && !nexusUrl.isEmpty()) { + Settings.setString(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl); + } + Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_PROXY, nexusUsesProxy); + //ARCHIVE ANALYZER + Settings.setBoolean(Settings.KEYS.ANALYZER_ARCHIVE_ENABLED, archiveAnalyzerEnabled); + if (zipExtensions != null && !zipExtensions.isEmpty()) { + Settings.setString(Settings.KEYS.ADDITIONAL_ZIP_EXTENSIONS, zipExtensions); + } + //ASSEMBLY ANALYZER + Settings.setBoolean(Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED, assemblyAnalyzerEnabled); + if (pathToMono != null && !pathToMono.isEmpty()) { + Settings.setString(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH, pathToMono); + } + + //Database configuration + if (databaseDriverName != null && !databaseDriverName.isEmpty()) { + Settings.setString(Settings.KEYS.DB_DRIVER_NAME, databaseDriverName); + } + if (databaseDriverPath != null && !databaseDriverPath.isEmpty()) { + Settings.setString(Settings.KEYS.DB_DRIVER_PATH, databaseDriverPath); + } + if (connectionString != null && !connectionString.isEmpty()) { + Settings.setString(Settings.KEYS.DB_CONNECTION_STRING, connectionString); + } + if (databaseUser != null && !databaseUser.isEmpty()) { + Settings.setString(Settings.KEYS.DB_USER, databaseUser); + } + if (databasePassword != null && !databasePassword.isEmpty()) { + Settings.setString(Settings.KEYS.DB_PASSWORD, databasePassword); + } + // Data Directory + if (dataDirectory != null && !dataDirectory.isEmpty()) { + Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory); + } + + // Scope Exclusion + Settings.setBoolean(Settings.KEYS.SKIP_TEST_SCOPE, skipTestScope); + Settings.setBoolean(Settings.KEYS.SKIP_RUNTIME_SCOPE, skipRuntimeScope); + Settings.setBoolean(Settings.KEYS.SKIP_PROVIDED_SCOPE, skipProvidedScope); + + // CVE Data Mirroring + if (cveUrl12Modified != null && !cveUrl12Modified.isEmpty()) { + Settings.setString(Settings.KEYS.CVE_MODIFIED_12_URL, cveUrl12Modified); + } + if (cveUrl20Modified != null && !cveUrl20Modified.isEmpty()) { + Settings.setString(Settings.KEYS.CVE_MODIFIED_20_URL, cveUrl20Modified); + } + if (cveUrl12Base != null && !cveUrl12Base.isEmpty()) { + Settings.setString(Settings.KEYS.CVE_SCHEMA_1_2, cveUrl12Base); + } + if (cveUrl20Base != null && !cveUrl20Base.isEmpty()) { + Settings.setString(Settings.KEYS.CVE_SCHEMA_2_0, cveUrl20Base); + } + } + + /** + * Returns the maven proxy. + * + * @return the maven proxy + */ + private Proxy getMavenProxy() { + if (mavenSettings != null) { + final List proxies = mavenSettings.getProxies(); + if (proxies != null && proxies.size() > 0) { + if (mavenSettingsProxyId != null) { + for (Proxy proxy : proxies) { + if (mavenSettingsProxyId.equalsIgnoreCase(proxy.getId())) { + return proxy; + } + } + } else if (proxies.size() == 1) { + return proxies.get(0); + } else { + LOGGER.warning("Multiple proxy defentiions exist in the Maven settings. In the dependency-check " + + "configuration set the maveSettingsProxyId so that the correct proxy will be used."); + throw new IllegalStateException("Ambiguous proxy definition"); + } + } + } + return null; + } + + /** + * Tests is the artifact should be included in the scan (i.e. is the dependency in a scope that is being scanned). + * + * @param a the Artifact to test + * @return true if the artifact is in an excluded scope; otherwise false + */ + protected boolean excludeFromScan(Artifact a) { + if (skipTestScope && Artifact.SCOPE_TEST.equals(a.getScope())) { + return true; + } + if (skipProvidedScope && Artifact.SCOPE_PROVIDED.equals(a.getScope())) { + return true; + } + if (skipRuntimeScope && !Artifact.SCOPE_RUNTIME.equals(a.getScope())) { + return true; + } + return false; + } + + /** + * Returns a reference to the current project. This method is used instead of auto-binding the project via component + * annotation in concrete implementations of this. If the child has a @Component MavenProject project; + * defined then the abstract class (i.e. this class) will not have access to the current project (just the way Maven + * works with the binding). + * + * @return returns a reference to the current project + */ + protected MavenProject getProject() { + return project; + } + + /** + * Returns the list of Maven Projects in this build. + * + * @return the list of Maven Projects in this build + */ + protected List getReactorProjects() { + return reactorProjects; + } + + /** + * Returns the report format. + * + * @return the report format + */ + protected String getFormat() { + return format; + } + + /** + * Generates the reports for a given dependency-check engine. + * + * @param engine a dependency-check engine + * @param p the maven project + * @param outputDir the directory path to write the report(s). + */ + protected void writeReports(Engine engine, MavenProject p, File outputDir) { + DatabaseProperties prop = null; + CveDB cve = null; + try { + cve = new CveDB(); + cve.open(); + prop = cve.getDatabaseProperties(); + } catch (DatabaseException ex) { + LOGGER.log(Level.FINE, "Unable to retrieve DB Properties", ex); + } finally { + if (cve != null) { + cve.close(); + } + } + final ReportGenerator r = new ReportGenerator(p.getName(), engine.getDependencies(), engine.getAnalyzers(), prop); + try { + r.generateReports(outputDir.getAbsolutePath(), format); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, + "Unexpected exception occurred during analysis; please see the verbose error log for more details."); + LOGGER.log(Level.FINE, null, ex); + } catch (Throwable ex) { + LOGGER.log(Level.SEVERE, + "Unexpected exception occurred during analysis; please see the verbose error log for more details."); + LOGGER.log(Level.FINE, null, ex); + } + } + + // + /** + * Checks to see if a vulnerability has been identified with a CVSS score that is above the threshold set in the + * configuration. + * + * @param dependencies the list of dependency objects + * @throws MojoFailureException thrown if a CVSS score is found that is higher then the threshold set + */ + protected void checkForFailure(List dependencies) throws MojoFailureException { + final StringBuilder ids = new StringBuilder(); + for (Dependency d : dependencies) { + boolean addName = true; + for (Vulnerability v : d.getVulnerabilities()) { + if (v.getCvssScore() >= failBuildOnCVSS) { + if (addName) { + addName = false; + ids.append(NEW_LINE).append(d.getFileName()).append(": "); + ids.append(v.getName()); + } else { + ids.append(", ").append(v.getName()); + } + } + } + } + if (ids.length() > 0) { + final String msg = String.format("%n%nDependency-Check Failure:%n" + + "One or more dependencies were identified with vulnerabilities that have a CVSS score greater then '%.1f': %s%n" + + "See the dependency-check report for more details.%n%n", failBuildOnCVSS, ids.toString()); + throw new MojoFailureException(msg); + } + } + + /** + * Generates a warning message listing a summary of dependencies and their associated CPE and CVE entries. + * + * @param dependencies a list of dependency objects + */ + protected void showSummary(List dependencies) { + final StringBuilder summary = new StringBuilder(); + for (Dependency d : dependencies) { + boolean firstEntry = true; + final StringBuilder ids = new StringBuilder(); + for (Vulnerability v : d.getVulnerabilities()) { + if (firstEntry) { + firstEntry = false; + } else { + ids.append(", "); + } + ids.append(v.getName()); + } + if (ids.length() > 0) { + summary.append(d.getFileName()).append(" ("); + firstEntry = true; + for (Identifier id : d.getIdentifiers()) { + if (firstEntry) { + firstEntry = false; + } else { + summary.append(", "); + } + summary.append(id.getValue()); + } + summary.append(") : ").append(ids).append(NEW_LINE); + } + } + if (summary.length() > 0) { + final String msg = String.format("%n%n" + "One or more dependencies were identified with known vulnerabilities:%n%n%s" + + "%n%nSee the dependency-check report for more details.%n%n", summary.toString()); + LOGGER.log(Level.WARNING, msg); + } + } + + // + // + /** + * Returns the key used to store the path to the data file that is saved by writeDataFile(). This key + * is used in the MavenProject.(set|get)ContextValue. + * + * @return the key used to store the path to the data file + */ + protected String getDataFileContextKey() { + return "dependency-check-path-" + dataFileName; + } + + /** + * Returns the key used to store the path to the output directory. When generating the report in the + * executeAggregateReport() the output directory should be obtained by using this key. + * + * @return the key used to store the path to the output directory + */ + protected String getOutputDirectoryContextKey() { + return "dependency-output-dir-" + dataFileName; + } + + /** + * Writes the scan data to disk. This is used to serialize the scan data between the "check" and "aggregate" phase. + * + * @param dependencies the list of dependencies to serialize + */ + protected void writeDataFile(List dependencies) { + File file = null; + if (dependencies != null && project.getContextValue(this.getDataFileContextKey()) == null) { + file = new File(project.getBuild().getDirectory(), dataFileName); + OutputStream os = null; + OutputStream bos = null; + ObjectOutputStream out = null; + try { + os = new FileOutputStream(file); + bos = new BufferedOutputStream(os); + out = new ObjectOutputStream(bos); + out.writeObject(dependencies); + out.flush(); + + //call reset to prevent resource leaks per + //https://www.securecoding.cert.org/confluence/display/java/SER10-J.+Avoid+memory+and+resource+leaks+during+serialization + out.reset(); + project.setContextValue(this.getDataFileContextKey(), file.getAbsolutePath()); + LOGGER.fine(String.format("Serialized data file written to '%s'", file.getAbsolutePath())); + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "Unable to create data file used for report aggregation; " + + "if report aggregation is being used the results may be incomplete."); + LOGGER.log(Level.FINE, ex.getMessage(), ex); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException ex) { + LOGGER.log(Level.FINEST, "ignore", ex); + } + } + if (bos != null) { + try { + bos.close(); + } catch (IOException ex) { + LOGGER.log(Level.FINEST, "ignore", ex); + } + } + if (os != null) { + try { + os.close(); + } catch (IOException ex) { + LOGGER.log(Level.FINEST, "ignore", ex); + } + } + } + } + } + + /** + * Reads the serialized scan data from disk. This is used to serialize the scan data between the "check" and + * "aggregate" phase. + * + * @param project the Maven project to read the data file from + * @return a Engine object populated with dependencies if the serialized data file exists; otherwise + * null is returned + */ + protected List readDataFile(MavenProject project) { + final Object oPath = project.getContextValue(this.getDataFileContextKey()); + if (oPath == null) { + return null; + } + List ret = null; + final String path = (String) oPath; + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(new FileInputStream(path)); + ret = (List) ois.readObject(); + } catch (FileNotFoundException ex) { + //TODO fix logging + LOGGER.log(Level.SEVERE, null, ex); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, null, ex); + } catch (ClassNotFoundException ex) { + LOGGER.log(Level.SEVERE, null, ex); + } finally { + if (ois != null) { + try { + ois.close(); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, null, ex); + } + } + } + return ret; + } + // +} diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java new file mode 100644 index 000000000..c1fb6d145 --- /dev/null +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java @@ -0,0 +1,133 @@ +/* + * This file is part of dependency-check-maven. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2013 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.maven; + +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.owasp.dependencycheck.data.nexus.MavenArtifact; +import org.owasp.dependencycheck.data.nvdcve.DatabaseException; +import org.owasp.dependencycheck.dependency.Confidence; +import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.utils.Settings; + +/** + * Maven Plugin that checks the project dependencies to see if they have any known published vulnerabilities. + * + * @author Jeremy Long + */ +@Mojo( + name = "check", + defaultPhase = LifecyclePhase.COMPILE, + threadSafe = true, + requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, + requiresOnline = true +) +public class CheckMojo extends BaseDependencyCheckMojo { + + /** + * Logger field reference. + */ + private static final Logger LOGGER = Logger.getLogger(CheckMojo.class.getName()); + + /** + * Returns whether or not a the report can be generated. + * + * @return true if the report can be generated; otherwise false + */ + @Override + public boolean canGenerateReport() { + boolean isCapable = false; + for (Artifact a : getProject().getArtifacts()) { + if (!excludeFromScan(a)) { + isCapable = true; + break; + } + } + return isCapable; + } + + public void runCheck() throws MojoExecutionException { + final Engine engine; + try { + engine = initializeEngine(); + } catch (DatabaseException ex) { + Logger.getLogger(CheckMojo.class.getName()).log(Level.SEVERE, null, ex); + throw new MojoExecutionException("An exception occured connecting to the local database. Please see the log file for more details.", ex); + } + + final Set artifacts = getProject().getArtifacts(); + for (Artifact a : artifacts) { + if (excludeFromScan(a)) { + continue; + } + final List deps = engine.scan(a.getFile().getAbsoluteFile()); + if (deps != null) { + if (deps.size() == 1) { + final Dependency d = deps.get(0); + if (d != null) { + final MavenArtifact ma = new MavenArtifact(a.getGroupId(), a.getArtifactId(), a.getVersion()); + d.addAsEvidence("pom", ma, Confidence.HIGHEST); + } + } else { + final String msg = String.format("More then 1 dependency was identified in first pass scan of '%s:%s:%s'", + a.getGroupId(), a.getArtifactId(), a.getVersion()); + LOGGER.info(msg); + } + } + } + if (engine.getDependencies().isEmpty()) { + LOGGER.info("No dependencies were identified that could be analyzed by dependency-check"); + } else { + engine.analyzeDependencies(); + writeReports(engine, getProject(), getCorrectOutputDirectory()); + writeDataFile(engine.getDependencies()); + } + engine.cleanup(); + Settings.cleanup(); + } + + /** + * Returns the report name. + * + * @param locale the location + * @return the report name + */ + public String getName(Locale locale) { + return "dependency-check"; + } + + /** + * Gets the description of the Dependency-Check report to be displayed in the Maven Generated Reports page. + * + * @param locale The Locale to get the description for + * @return the description + */ + public String getDescription(Locale locale) { + return "Generates a report providing details on any published vulnerabilities within project dependencies. " + + "This report is a best effort and may contain false positives and false negatives."; + } + +}