View Javadoc
1   /*
2    * This file is part of dependency-check-core.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2014 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.analyzer;
19  
20  import org.apache.commons.io.FileUtils;
21  import org.owasp.dependencycheck.Engine;
22  import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
23  import org.owasp.dependencycheck.data.nexus.MavenArtifact;
24  import org.owasp.dependencycheck.data.nexus.NexusSearch;
25  import org.owasp.dependencycheck.dependency.Confidence;
26  import org.owasp.dependencycheck.dependency.Dependency;
27  import org.owasp.dependencycheck.dependency.Evidence;
28  import org.owasp.dependencycheck.xml.pom.PomUtils;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  import java.io.File;
33  import java.io.FileFilter;
34  import java.io.FileNotFoundException;
35  import java.io.IOException;
36  import java.net.MalformedURLException;
37  import java.net.URL;
38  import org.owasp.dependencycheck.exception.InitializationException;
39  import org.owasp.dependencycheck.utils.DownloadFailedException;
40  import org.owasp.dependencycheck.utils.Downloader;
41  import org.owasp.dependencycheck.utils.FileFilterBuilder;
42  import org.owasp.dependencycheck.utils.InvalidSettingException;
43  import org.owasp.dependencycheck.utils.Settings;
44  
45  /**
46   * Analyzer which will attempt to locate a dependency on a Nexus service by
47   * SHA-1 digest of the dependency.
48   *
49   * There are two settings which govern this behavior:
50   *
51   * <ul>
52   * <li>{@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_NEXUS_ENABLED}
53   * determines whether this analyzer is even enabled. This can be overridden by
54   * setting the system property.</li>
55   * <li>{@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_NEXUS_URL}
56   * the URL to a Nexus service to search by SHA-1. There is an expected
57   * <code>%s</code> in this where the SHA-1 will get entered.</li>
58   * </ul>
59   *
60   * @author colezlaw
61   */
62  public class NexusAnalyzer extends AbstractFileTypeAnalyzer {
63  
64      /**
65       * The default URL - this will be used by the CentralAnalyzer to determine
66       * whether to enable this.
67       */
68      public static final String DEFAULT_URL = "https://repository.sonatype.org/service/local/";
69  
70      /**
71       * The logger.
72       */
73      private static final Logger LOGGER = LoggerFactory.getLogger(NexusAnalyzer.class);
74  
75      /**
76       * The name of the analyzer.
77       */
78      private static final String ANALYZER_NAME = "Nexus Analyzer";
79  
80      /**
81       * The phase in which the analyzer runs.
82       */
83      private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
84  
85      /**
86       * The types of files on which this will work.
87       */
88      private static final String SUPPORTED_EXTENSIONS = "jar";
89  
90      /**
91       * Whether or not the Nexus analyzer should use a proxy if configured.
92       */
93      private boolean useProxy;
94      /**
95       * The Nexus Search to be set up for this analyzer.
96       */
97      private NexusSearch searcher;
98  
99      /**
100      * Field indicating if the analyzer is enabled.
101      */
102     private final boolean enabled = checkEnabled();
103 
104     /**
105      * Determines if this analyzer is enabled
106      *
107      * @return <code>true</code> if the analyzer is enabled; otherwise
108      * <code>false</code>
109      */
110     private boolean checkEnabled() {
111         /* Enable this analyzer ONLY if the Nexus URL has been set to something
112          other than the default one (if it's the default one, we'll use the
113          central one) and it's enabled by the user.
114          */
115         boolean retval = false;
116         try {
117             if (!DEFAULT_URL.equals(Settings.getString(Settings.KEYS.ANALYZER_NEXUS_URL))
118                     && Settings.getBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED)) {
119                 LOGGER.info("Enabling Nexus analyzer");
120                 retval = true;
121             } else {
122                 LOGGER.debug("Nexus analyzer disabled, using Central instead");
123             }
124         } catch (InvalidSettingException ise) {
125             LOGGER.warn("Invalid setting. Disabling Nexus analyzer");
126         }
127 
128         return retval;
129     }
130 
131     /**
132      * Determine whether to enable this analyzer or not.
133      *
134      * @return whether the analyzer should be enabled
135      */
136     @Override
137     public boolean isEnabled() {
138         return enabled;
139     }
140 
141     /**
142      * Initializes the analyzer once before any analysis is performed.
143      *
144      * @throws InitializationException if there's an error during initialization
145      */
146     @Override
147     public void initializeFileTypeAnalyzer() throws InitializationException {
148         LOGGER.debug("Initializing Nexus Analyzer");
149         LOGGER.debug("Nexus Analyzer enabled: {}", isEnabled());
150         if (isEnabled()) {
151             useProxy = useProxy();
152             final String searchUrl = Settings.getString(Settings.KEYS.ANALYZER_NEXUS_URL);
153             LOGGER.debug("Nexus Analyzer URL: {}", searchUrl);
154             try {
155                 searcher = new NexusSearch(new URL(searchUrl), useProxy);
156                 if (!searcher.preflightRequest()) {
157                     setEnabled(false);
158                     throw new InitializationException("There was an issue getting Nexus status. Disabling analyzer.");
159                 }
160             } catch (MalformedURLException mue) {
161                 setEnabled(false);
162                 throw new InitializationException("Malformed URL to Nexus: " + searchUrl, mue);
163             }
164         }
165     }
166 
167     /**
168      * Returns the analyzer's name.
169      *
170      * @return the name of the analyzer
171      */
172     @Override
173     public String getName() {
174         return ANALYZER_NAME;
175     }
176 
177     /**
178      * Returns the key used in the properties file to reference the analyzer's
179      * enabled property.
180      *
181      * @return the analyzer's enabled property setting key
182      */
183     @Override
184     protected String getAnalyzerEnabledSettingKey() {
185         return Settings.KEYS.ANALYZER_NEXUS_ENABLED;
186     }
187 
188     /**
189      * Returns the analysis phase under which the analyzer runs.
190      *
191      * @return the phase under which this analyzer runs
192      */
193     @Override
194     public AnalysisPhase getAnalysisPhase() {
195         return ANALYSIS_PHASE;
196     }
197 
198     /**
199      * The file filter used to determine which files this analyzer supports.
200      */
201     private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(SUPPORTED_EXTENSIONS).build();
202 
203     /**
204      * Returns the FileFilter
205      *
206      * @return the FileFilter
207      */
208     @Override
209     protected FileFilter getFileFilter() {
210         return FILTER;
211     }
212 
213     /**
214      * Performs the analysis.
215      *
216      * @param dependency the dependency to analyze
217      * @param engine the engine
218      * @throws AnalysisException when there's an exception during analysis
219      */
220     @Override
221     public void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
222         if (!isEnabled()) {
223             return;
224         }
225         try {
226             final MavenArtifact ma = searcher.searchSha1(dependency.getSha1sum());
227             dependency.addAsEvidence("nexus", ma, Confidence.HIGH);
228             boolean pomAnalyzed = false;
229             LOGGER.debug("POM URL {}", ma.getPomUrl());
230             for (Evidence e : dependency.getVendorEvidence()) {
231                 if ("pom".equals(e.getSource())) {
232                     pomAnalyzed = true;
233                     break;
234                 }
235             }
236             if (!pomAnalyzed && ma.getPomUrl() != null) {
237                 File pomFile = null;
238                 try {
239                     final File baseDir = Settings.getTempDirectory();
240                     pomFile = File.createTempFile("pom", ".xml", baseDir);
241                     if (!pomFile.delete()) {
242                         LOGGER.warn("Unable to fetch pom.xml for {} from Nexus repository; "
243                                 + "this could result in undetected CPE/CVEs.", dependency.getFileName());
244                         LOGGER.debug("Unable to delete temp file");
245                     }
246                     LOGGER.debug("Downloading {}", ma.getPomUrl());
247                     Downloader.fetchFile(new URL(ma.getPomUrl()), pomFile);
248                     PomUtils.analyzePOM(dependency, pomFile);
249                 } catch (DownloadFailedException ex) {
250                     LOGGER.warn("Unable to download pom.xml for {} from Nexus repository; "
251                             + "this could result in undetected CPE/CVEs.", dependency.getFileName());
252                 } finally {
253                     if (pomFile != null && pomFile.exists() && !FileUtils.deleteQuietly(pomFile)) {
254                         LOGGER.debug("Failed to delete temporary pom file {}", pomFile.toString());
255                         pomFile.deleteOnExit();
256                     }
257                 }
258             }
259         } catch (IllegalArgumentException iae) {
260             //dependency.addAnalysisException(new AnalysisException("Invalid SHA-1"));
261             LOGGER.info("invalid sha-1 hash on {}", dependency.getFileName());
262         } catch (FileNotFoundException fnfe) {
263             //dependency.addAnalysisException(new AnalysisException("Artifact not found on repository"));
264             LOGGER.debug("Artifact not found in repository '{}'", dependency.getFileName());
265             LOGGER.debug(fnfe.getMessage(), fnfe);
266         } catch (IOException ioe) {
267             //dependency.addAnalysisException(new AnalysisException("Could not connect to repository", ioe));
268             LOGGER.debug("Could not connect to nexus repository", ioe);
269         }
270     }
271 
272     /**
273      * Determine if a proxy should be used.
274      *
275      * @return {@code true} if a proxy should be used
276      */
277     public static boolean useProxy() {
278         try {
279             return Settings.getString(Settings.KEYS.PROXY_SERVER) != null
280                     && Settings.getBoolean(Settings.KEYS.ANALYZER_NEXUS_USES_PROXY);
281         } catch (InvalidSettingException ise) {
282             LOGGER.warn("Failed to parse proxy settings.", ise);
283             return false;
284         }
285     }
286 }