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.utils.DownloadFailedException;
39  import org.owasp.dependencycheck.utils.Downloader;
40  import org.owasp.dependencycheck.utils.FileFilterBuilder;
41  import org.owasp.dependencycheck.utils.InvalidSettingException;
42  import org.owasp.dependencycheck.utils.Settings;
43  
44  /**
45   * Analyzer which will attempt to locate a dependency on a Nexus service by SHA-1 digest of the dependency.
46   *
47   * There are two settings which govern this behavior:
48   *
49   * <ul>
50   * <li>{@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_NEXUS_ENABLED} determines whether this analyzer is even
51   * enabled. This can be overridden by setting the system property.</li>
52   * <li>{@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_NEXUS_URL} the URL to a Nexus service to search by SHA-1.
53   * There is an expected <code>%s</code> in this where the SHA-1 will get entered.</li>
54   * </ul>
55   *
56   * @author colezlaw
57   */
58  public class NexusAnalyzer extends AbstractFileTypeAnalyzer {
59  
60      /**
61       * The default URL - this will be used by the CentralAnalyzer to determine whether to enable this.
62       */
63      public static final String DEFAULT_URL = "https://repository.sonatype.org/service/local/";
64  
65      /**
66       * The logger.
67       */
68      private static final Logger LOGGER = LoggerFactory.getLogger(NexusAnalyzer.class);
69  
70      /**
71       * The name of the analyzer.
72       */
73      private static final String ANALYZER_NAME = "Nexus Analyzer";
74  
75      /**
76       * The phase in which the analyzer runs.
77       */
78      private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
79  
80      /**
81       * The types of files on which this will work.
82       */
83      private static final String SUPPORTED_EXTENSIONS = "jar";
84  
85      /**
86       * The Nexus Search to be set up for this analyzer.
87       */
88      private NexusSearch searcher;
89  
90      /**
91       * Field indicating if the analyzer is enabled.
92       */
93      private final boolean enabled = checkEnabled();
94  
95      /**
96       * Determines if this analyzer is enabled
97       *
98       * @return <code>true</code> if the analyzer is enabled; otherwise <code>false</code>
99       */
100     private boolean checkEnabled() {
101         /* Enable this analyzer ONLY if the Nexus URL has been set to something
102          other than the default one (if it's the default one, we'll use the
103          central one) and it's enabled by the user.
104          */
105         boolean retval = false;
106         try {
107             if (!DEFAULT_URL.equals(Settings.getString(Settings.KEYS.ANALYZER_NEXUS_URL))
108                     && Settings.getBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED)) {
109                 LOGGER.info("Enabling Nexus analyzer");
110                 retval = true;
111             } else {
112                 LOGGER.debug("Nexus analyzer disabled, using Central instead");
113             }
114         } catch (InvalidSettingException ise) {
115             LOGGER.warn("Invalid setting. Disabling Nexus analyzer");
116         }
117 
118         return retval;
119     }
120 
121     /**
122      * Determine whether to enable this analyzer or not.
123      *
124      * @return whether the analyzer should be enabled
125      */
126     @Override
127     public boolean isEnabled() {
128         return enabled;
129     }
130 
131     /**
132      * Initializes the analyzer once before any analysis is performed.
133      *
134      * @throws Exception if there's an error during initialization
135      */
136     @Override
137     public void initializeFileTypeAnalyzer() throws Exception {
138         LOGGER.debug("Initializing Nexus Analyzer");
139         LOGGER.debug("Nexus Analyzer enabled: {}", isEnabled());
140         if (isEnabled()) {
141             final String searchUrl = Settings.getString(Settings.KEYS.ANALYZER_NEXUS_URL);
142             LOGGER.debug("Nexus Analyzer URL: {}", searchUrl);
143             try {
144                 searcher = new NexusSearch(new URL(searchUrl));
145                 if (!searcher.preflightRequest()) {
146                     LOGGER.warn("There was an issue getting Nexus status. Disabling analyzer.");
147                     setEnabled(false);
148                 }
149             } catch (MalformedURLException mue) {
150                 // I know that initialize can throw an exception, but we'll
151                 // just disable the analyzer if the URL isn't valid
152                 LOGGER.warn("Property {} not a valid URL. Nexus Analyzer disabled", searchUrl);
153                 setEnabled(false);
154             }
155         }
156     }
157 
158     /**
159      * Returns the analyzer's name.
160      *
161      * @return the name of the analyzer
162      */
163     @Override
164     public String getName() {
165         return ANALYZER_NAME;
166     }
167 
168     /**
169      * Returns the key used in the properties file to reference the analyzer's enabled property.
170      *
171      * @return the analyzer's enabled property setting key
172      */
173     @Override
174     protected String getAnalyzerEnabledSettingKey() {
175         return Settings.KEYS.ANALYZER_NEXUS_ENABLED;
176     }
177 
178     /**
179      * Returns the analysis phase under which the analyzer runs.
180      *
181      * @return the phase under which this analyzer runs
182      */
183     @Override
184     public AnalysisPhase getAnalysisPhase() {
185         return ANALYSIS_PHASE;
186     }
187 
188     /**
189      * The file filter used to determine which files this analyzer supports.
190      */
191     private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(SUPPORTED_EXTENSIONS).build();
192 
193     /**
194      * Returns the FileFilter
195      *
196      * @return the FileFilter
197      */
198     @Override
199     protected FileFilter getFileFilter() {
200         return FILTER;
201     }
202 
203     /**
204      * Performs the analysis.
205      *
206      * @param dependency the dependency to analyze
207      * @param engine the engine
208      * @throws AnalysisException when there's an exception during analysis
209      */
210     @Override
211     public void analyzeFileType(Dependency dependency, Engine engine) throws AnalysisException {
212         if (!isEnabled()) {
213             return;
214         }
215         try {
216             final MavenArtifact ma = searcher.searchSha1(dependency.getSha1sum());
217             dependency.addAsEvidence("nexus", ma, Confidence.HIGH);
218             boolean pomAnalyzed = false;
219             LOGGER.debug("POM URL {}", ma.getPomUrl());
220             for (Evidence e : dependency.getVendorEvidence()) {
221                 if ("pom".equals(e.getSource())) {
222                     pomAnalyzed = true;
223                     break;
224                 }
225             }
226             if (!pomAnalyzed && ma.getPomUrl() != null) {
227                 File pomFile = null;
228                 try {
229                     final File baseDir = Settings.getTempDirectory();
230                     pomFile = File.createTempFile("pom", ".xml", baseDir);
231                     if (!pomFile.delete()) {
232                         LOGGER.warn("Unable to fetch pom.xml for {} from Nexus repository; "
233                                 + "this could result in undetected CPE/CVEs.", dependency.getFileName());
234                         LOGGER.debug("Unable to delete temp file");
235                     }
236                     LOGGER.debug("Downloading {}", ma.getPomUrl());
237                     Downloader.fetchFile(new URL(ma.getPomUrl()), pomFile);
238                     PomUtils.analyzePOM(dependency, pomFile);
239                 } catch (DownloadFailedException ex) {
240                     LOGGER.warn("Unable to download pom.xml for {} from Nexus repository; "
241                             + "this could result in undetected CPE/CVEs.", dependency.getFileName());
242                 } finally {
243                     if (pomFile != null && !FileUtils.deleteQuietly(pomFile)) {
244                         pomFile.deleteOnExit();
245                     }
246                 }
247             }
248         } catch (IllegalArgumentException iae) {
249             //dependency.addAnalysisException(new AnalysisException("Invalid SHA-1"));
250             LOGGER.info("invalid sha-1 hash on {}", dependency.getFileName());
251         } catch (FileNotFoundException fnfe) {
252             //dependency.addAnalysisException(new AnalysisException("Artifact not found on repository"));
253             LOGGER.debug("Artifact not found in repository '{}'", dependency.getFileName());
254             LOGGER.debug(fnfe.getMessage(), fnfe);
255         } catch (IOException ioe) {
256             //dependency.addAnalysisException(new AnalysisException("Could not connect to repository", ioe));
257             LOGGER.debug("Could not connect to nexus repository", ioe);
258         }
259     }
260 }