All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sourceclear.plugins.CaptureMavenMojo Maven / Gradle / Ivy

Go to download

The SRC:CLR Maven Plugin analyzes the dependencies of your project, both immediate and transitive, to see if you are including any known security vulnerabilities through third-party packages in your project.

The newest version!
/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * 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.
 */

package com.sourceclear.plugins;

import com.google.common.collect.ImmutableSet;
import com.sourceclear.api.client.Client;
import com.sourceclear.api.client.SourceClearClient;
import com.sourceclear.api.data.artifact.LibraryArtifactApiModel;
import com.sourceclear.api.data.artifact.LibraryMatchWithArtifactsApiModel;
import com.sourceclear.api.data.evidence.Evidence;
import com.sourceclear.api.data.evidence.LanguageType;
import com.sourceclear.api.data.match.MatchQuery;
import com.sourceclear.api.data.match.MatchResponse;
import com.sourceclear.api.data.methods.MethodCallChains;
import com.sourceclear.api.data.methods.VulnerableMethodUpload;
import com.sourceclear.engine.common.ClassFileVisitor;
import com.sourceclear.engine.common.DependencyGraph;
import com.sourceclear.engine.common.logging.NoopLogStream;
import com.sourceclear.engine.component.Utils;
import com.sourceclear.engine.methods.ClassMethodsEngine;
import com.sourceclear.engine.methods.VulnerableMethodsCollator;
import com.sourceclear.plugins.config.ConfigService;
import com.sourceclear.plugins.config.ConfigServiceImpl;
import com.sourceclear.plugins.config.ConsoleConfig;
import com.sourceclear.util.io.GitUtils;
import com.sourceclear.util.io.renderers.ComponentRenderer;
import com.sourceclear.util.io.renderers.ScanReport;
import com.sourceclear.util.io.renderers.SummaryRenderer;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import jdk.nashorn.internal.ir.annotations.Immutable;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.InstantiationStrategy;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
import org.apache.maven.shared.dependency.graph.DependencyNode;


/**
 * The scan goal runs the SRC:CLR scan on the project described by the Maven pom.xml and all submodules.
 * 

* Upon completion, a list of found vulnerable components will be reported on the console and, if shouldUpload is true, * will be uploaded to the SRC:CLR platform. */ @Mojo( name = "scan", requiresDependencyCollection = ResolutionScope.COMPILE, requiresProject = true, instantiationStrategy=InstantiationStrategy.SINGLETON, threadSafe = false) // I know this is the default, but since I'm doing a lazy init on a singleton, make sure people know it. public class CaptureMavenMojo extends AbstractMojo { @Parameter(defaultValue = "${session}", required = true, readonly = true) private MavenSession session; /** * This token is used to grant you authority to write to a repo owned by an organization on the SRC:CLR platform. *

* If not specified, any uploads will be done to your personal environment. */ @Parameter(property = "orgToken") protected String orgToken; /** * The name of your organization on the SRC:CLR platform */ @Parameter(property="orgName") private String orgName; /** * The name that you would like your project to be called on the SRC:CLR platform. *

* If not specified, and if a projectID is not provided, a name will be constructed from repo and filesystem information. */ @Parameter(property="projectName") private String projectName; /** * The URL you are using for the SRC:CLR api. *

* You shouldn't need to use this unless directed to by SRC:CLR support. */ @Parameter(property = "apiURL") protected URI apiURL; /** * The userToken property is used to provide your personal token to the SRC:CLR platform for authentication. *

* The scan will not finish successfully if this is not provided or if it is incorrect. */ @Parameter(property = "userToken") protected String userToken; /** * If you know which platform project you would like to associate this scan with, that can be specified by the projectID property. */ @Parameter(property = "projectID") private Long userProjectID; /** * Indicates whether the report from SRC:CLR should be uploaded to the platform. If false, the results will only be displayed on the console. */ @Parameter(property = "shouldUpload", defaultValue="true") private boolean shouldUpload; /** * By default, this plugin only shows components with vulnerabilities in console output. Setting verbose to true causes all components to be listed. */ @Parameter(property="verbose", defaultValue = "false") private boolean verbose; public static final URI DEFAULT_API_URL = URI.create("https://api.srcclr.com"); enum FailureLevel { COMPONENT, METHOD, NEVER } /** * Allows you to specify the strength of evidence of a vulnerability at which you want the SRC:CLR scan to fail with an exception *

* By default, the SRC:CLR scan will fail only if it can show a chain of methods calling a known-vulnerable method * (the METHOD failureThreshold). If you specify COMPONENT, the scan will fail the build on the discovery of any * vulnerable component, whether or not a call chain to it can be found. Specifying NEVER will make the scan never * fail. */ @Parameter(property="failureThreshold", defaultValue="METHOD") private FailureLevel failureThreshold; @org.apache.maven.plugins.annotations.Component(hint = "default") private DependencyGraphBuilder dependencyGraphBuilder; @Immutable // This class is stateless, hence why we have a singleton instance that is immutable static final ClassMethodsEngine METHODS_ENGINE = new ClassMethodsEngine(); // To time how long our scan takes. long scanStart; class NoTestArtifacts implements ArtifactFilter { @Override public boolean include(Artifact artifact) { return !artifact.getScope().equals("test"); } } ArrayList projectDependencyTrees = new ArrayList<>(); MavenProject lastProject = null; /** * Handles some configuration issues. Essentially, looks to see if we have certain maven parameters. If the * parameters aren't present, we dig around in some SRC:CLR config files and environment variables to attempt to fill * in those parameters. * @throws MojoFailureException when no userToken is specified, when the SRC:CLR platform URI is malformed, etc. */ protected void handleConfig() throws MojoFailureException { // If these are all non-null, no need to look in the config files. Maven properties take precedence. if(StringUtils.isNotBlank(userToken) && StringUtils.isNotBlank(orgToken) && (apiURL != null)) { return; } ConfigService configService = new ConfigServiceImpl(); if(StringUtils.isBlank(userToken)) { try { userToken = configService.getSourceClearClientToken(); } catch(Exception e) { getLog().debug("Error in retrieving client token", e); } if(StringUtils.isBlank(userToken)) { throw new MojoFailureException("The userToken parameter is required. Specify it with -DuserToken or by setting userToken in a SRC:CLR configuration file."); } } if(StringUtils.isBlank(orgToken)) { try { orgToken = configService.getSourceClearOrgToken(); } catch(Exception e) { getLog().debug("Error in retrieving org token", e); } if(StringUtils.isBlank(orgToken)) { orgToken = null; } } if(apiURL == null) { ConsoleConfig config = null; try { config = configService.getConfiguration(); } catch(Exception e) { getLog().debug("Error in retrieving URL string: ", e); } String apiURLString = null; if(config != null) { String configApiString = config.getApiUrl(); if(StringUtils.isNotBlank(configApiString)) { apiURLString = configApiString; } } if(StringUtils.isNotBlank(apiURLString)) { try { apiURL = new URI(apiURLString); } catch (Exception e) { throw new MojoFailureException("Failure in trying to use specified apiURL:", e); } } } if(apiURL == null) { apiURL = DEFAULT_API_URL; } } /** * Some setup that needs to be done once on this singleton Maven mojo. * @throws MojoFailureException Upon various configure errors, see the handleCOnfig documentation. */ private void initialSetup() throws MojoFailureException { // Safe so long as we keep things sequential, would have to change for parallel builds. if (lastProject == null) { scanStart = System.currentTimeMillis(); handleConfig(); List sortedProjects = session.getProjectDependencyGraph().getSortedProjects(); lastProject = sortedProjects.get(sortedProjects.size() - 1); } } /** * Gets the dependency graph for a single Maven project (top level or submodule) as a SRC:CLR DependencyGraph object. * @return The dependency graph for the current project * @throws MojoExecutionException if we run into an unexpected value for Maven's dependency graph (null) or we run * into a problem translating the dependency graph. */ private DependencyGraph getProjectDependencyGraph() throws MojoExecutionException { Path pathToTop = getPathToTop(); try { ProjectBuildingRequest buildRequest = session.getProjectBuildingRequest(); MavenProject currentProject = session.getCurrentProject(); buildRequest.setProject(currentProject); Path pathToCurrPom = currentProject.getFile().toPath(); Path relativePathToCurrPom = pathToTop.relativize(pathToCurrPom); DependencyGraphTranslator graphTranslator = new DependencyGraphTranslator(relativePathToCurrPom.toString()); DependencyNode mavenDependencyTree = dependencyGraphBuilder.buildDependencyGraph(buildRequest, new NoTestArtifacts()); return graphTranslator.getSrcclrDependencyGraph(mavenDependencyTree); } catch (DependencyGraphBuilderException e) { throw new MojoExecutionException("Encountered problem running the SRC:CLR maven plugin", e); } } /** * Grabs the path to the top-level Maven project's directory * @return the path to the top level Maven project's directory */ private Path getPathToTop() { return Paths.get(session.getTopLevelProject().getFile().getParent()); } /** * After collecting evidence describing the artifacts used in this Maven project, this function builds a MatchQuery * to send up to the SRC:CLR platform to find which ones contain vulnerabilities. * @param evidence The evidence collected from artifact dependency graphs * @param doVulnMethods Whether we are going to attempt vulnerable method analysis * @return A MatchQuery suitible for sending to the SRC:CLR platform to find vulnerabilities. */ private MatchQuery buildMatchQuery(ImmutableSet evidence, boolean doVulnMethods) { String gitBranch = null; String gitCommitHash = null; String repoUrl = null; File projectDir = session.getTopLevelProject().getBasedir(); Path pathToTop = getPathToTop(); try { gitBranch = GitUtils.getBranch(projectDir); gitCommitHash = GitUtils.getCommitHash(projectDir); repoUrl = GitUtils.getRepoUrl(projectDir); if(repoUrl == null) { repoUrl = pathToTop.toFile().getCanonicalPath(); } } catch (Exception e) { getLog().error("\n ** A problem was encountered in trying to find repo naming information. ", e); } MatchQuery.Builder matchQueryBuilder = new MatchQuery.Builder() .withProjectId(userProjectID) .withScanId(generateScanId()) .withEvidence(new ArrayList<>(evidence)) .withBranch(gitBranch) .withCommitHash(gitCommitHash) .withRepoUrl(repoUrl) .withProjectName(projectName) .withOrganization(orgName) .persist(shouldUpload) .requestVulnMethods(doVulnMethods); return matchQueryBuilder.build(); } /** * Builds a SourceClearClient for communicating with the SRC:CLR platform * @return a client object */ private Client buildClient() { SourceClearClient.Builder clientBuilder = new SourceClearClient.Builder() .withAuthToken(userToken) .withOrgToken(orgToken) .withBaseURI(apiURL); return clientBuilder.build(); } /** * Builds a ScanReport object, which indicates what vulnerabilities we found and where. This will be rendered to the * console in a few different rendering formats. * @param evidence Information about the artifacts found in the dependency trees * @param vulnMethods The list of vulnerable methods found * @param response The response from the SRC:CLR platform from the MatchQuery * @return A ScanReport indicating the results of this scan. */ private ScanReport buildReport( ImmutableSet evidence, Map> vulnMethods, MatchResponse response) { ScanReport.Builder reportBuilder = new ScanReport.Builder() .withScanPath(getPathToTop().toString()) .withDuration((System.currentTimeMillis() - scanStart) / 1000) .withEvidence(evidence) .withMatchResponse(response) .withUpload(shouldUpload); if (vulnMethods != null) { reportBuilder.withCallChains(vulnMethods); } return reportBuilder.build(); } /** * Print the results of the scan to the console in various formats. * @param report The ScanReport generated from this scan. */ private void renderReport(ScanReport report) { SummaryRenderer sumRenderer = new SummaryRenderer(); ComponentRenderer compRenderer= new ComponentRenderer(verbose); compRenderer.accept(report); sumRenderer.accept(report); } /** * After all subprojects have finished building their dependency graphs, this method consumes that information, scans * for vulnerabilities, and reports to the console and (depending on shouldUpload) the SRC:CLR platform * @throws MojoFailureException If vulnerabilities with a confidence level equal to or greater than than the * failureThreshold are found, we throw a MojoFailureException. * @throws MojoExecutionException If we experience unexpected network issues while talking to the SRC:CLR platform */ private void consumeAndReport() throws MojoFailureException, MojoExecutionException { ImmutableSet.Builder builder = new ImmutableSet.Builder<>(); builder.addAll( Utils.fromDependencyGraph( LanguageType.JAVA, projectDependencyTrees.toArray(new DependencyGraph[projectDependencyTrees.size()]))); ImmutableSet evidence = builder.build(); String pathToTopStr = getPathToTop().toString(); boolean doVulnMethods = isMethodsSupported(pathToTopStr); if(!doVulnMethods) { System.err.printf("Couldn't find any built class files in %s, skipping vulnerable methods check.\n", pathToTopStr); } Client client = buildClient(); long matchingStart = System.currentTimeMillis(); MatchResponse response; try { response = client.match(buildMatchQuery(evidence, doVulnMethods)); } catch (IOException e) { throw new MojoExecutionException("Encountered problem running the SRC:CLR maven plugin", e); } Long trueProjectID = (userProjectID == null) ? response.getProjectId() : userProjectID; if (doVulnMethods && (trueProjectID == null)) { System.err.println("No projectID found, skipping vulnerable method upload."); doVulnMethods = false; } System.out.println(); // flush from the print "API..." upstairs System.out.printf(" -> matched in %s seconds.%n%n", (System.currentTimeMillis() - matchingStart) / 1000); Map> vulnMethods = null; if(doVulnMethods) { vulnMethods = doVulnMethodsScan(evidence, response.getComponents(), client, trueProjectID); } else { System.out.println("Skipping vulnerable methods scan."); } ScanReport report = buildReport(evidence, vulnMethods, response); renderReport(report); maybeFailOnVulns(report.getVulnerableMethods(), report.getVulnerableComponents()); } /** * Perform the vulnerable method scan, and (depending on shouldUpload) send this to the SRC:CLR platform. * @param evidence Evidence harvested from the artifact dependency graphs * @param components Components created from the artifact dependency graphs * @param client The client used to communicate with the SRC:CLR platform * @param trueProjectID The projectID, either specified by the user or given to us in the response from the name. * @return A map describing vulnerable method callchains. */ private Map> doVulnMethodsScan( ImmutableSet evidence, List components, Client client, Long trueProjectID) { Map> vulnMethods = null; final VulnerableMethodsCollator collator = new VulnerableMethodsCollator(evidence, components, METHODS_ENGINE, new NoopLogStream()); Path scanPath = getPathToTop(); collator.scanPath(scanPath.toFile()); vulnMethods = collator.getMethodsMap(); for (List vulnMethodList : vulnMethods.values()) { final VulnerableMethodUpload vmUpload = new VulnerableMethodUpload(vulnMethodList); boolean result = false; if (shouldUpload) { try { result = client.uploadVulnerableMethodsForProject(trueProjectID, vmUpload); } catch (IOException e) { // Report an error in the finally, try to continue. getLog().error("Encountered error during vulnerable method upload.", e); } finally { if (!result) { System.err.println("Vulnerable methods upload failed."); } } } } return vulnMethods; } /** * Fails if vulnerabilities are found with confidence greater than or equal to the failureThreshold * @param optVulnMethodCount The number of vulnerable method callchains found * @param vulnComponentCount The number of vulnerable components found * @throws MojoFailureException If the vulnerability confidence meets or exceeds the threshold. */ protected void maybeFailOnVulns(Integer optVulnMethodCount, int vulnComponentCount) throws MojoFailureException { FailureLevel failureLevel = null; if((optVulnMethodCount != null) && (optVulnMethodCount > 0)) { failureLevel = FailureLevel.METHOD; } else if(vulnComponentCount > 0) { failureLevel = FailureLevel.COMPONENT; } if((failureLevel != null) && (failureLevel.compareTo(failureThreshold) >= 0)) { throw new MojoFailureException( "The current scan found vulnerable " + failureLevel.toString().toLowerCase() + "s, failing as failureThreshold is set to " + failureThreshold + "."); } } /** * The top-level plugin entry point. * @throws MojoExecutionException If an unexpected failure occurs * @throws MojoFailureException On user error or the discovery of a vulnerability of confidence level at or above the * failureThreshold */ @Override public void execute() throws MojoExecutionException, MojoFailureException { initialSetup(); projectDependencyTrees.add(getProjectDependencyGraph()); // This works as long as we're doing this sequentially, but would have to change for parallel builds. if (lastProject.equals(session.getCurrentProject())) { consumeAndReport(); } } /** * A unique identifier for the current scan. * @return The unique identifier. */ private String generateScanId() { return String.format("maven-plugin-%s", UUID.randomUUID()); } //FIXME: The following method is copy-pasted from com.sourceclear.engine.component.natives.parsing.CollectionResult. // They should share the same code. /** * If the project path contains class files (other than the default exclusions), * we can assume that vulnerable methods scanning is to be supported. */ private boolean isMethodsSupported(String projectRoot) { try { ClassFileVisitor visitor = new ClassFileVisitor(); Files.walkFileTree(Paths.get(projectRoot), visitor); return !visitor.getClassFiles().isEmpty(); } catch (Exception ex) { System.err.println("Unable to determine vulnerable methods support, skipping"); getLog().error("Couldn't scan for class files", ex); return false; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy