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

com.sourceclear.plugins.ScanMavenMojo 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.

There is a newer version: 3.1.25
Show newest version
/*
 * © Copyright 2015 -  SourceClear Inc
 */

package com.sourceclear.plugins;

import com.google.common.annotations.VisibleForTesting;
import com.sourceclear.util.config.EnvironmentProvider;
import com.sourceclear.util.config.EnvironmentProviderImpl;
import com.sourceclear.util.config.FailureLevel;
import com.srcclr.sdk.Directives;
import com.srcclr.sdk.LibraryGraph;
import com.srcclr.sdk.LibraryGraphContainer;
import com.srcclr.sdk.build.MavenComponentGraphBuilder;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
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.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugins.annotations.Component;
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.settings.Settings;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
import org.apache.maven.shared.dependency.graph.DependencyNode;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;


/**
 * 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 upload is true, * will be uploaded to the SRC:CLR platform. */ @Mojo( name = "scan", requiresDependencyCollection = ResolutionScope.TEST, // Because the maven test scope is the superset of all scopes instantiationStrategy = InstantiationStrategy.SINGLETON) public class ScanMavenMojo extends AbstractMojo { private static final String SCOPE_ENV_VAR = "SRCCLR_SCAN_SCOPE"; private static final String DIRECTIVES_FILE_NAME = "srcclr.yml"; private static final Path DIRECTIVES_FILE = Paths.get(DIRECTIVES_FILE_NAME); private static final String DIRECTIVES_SCOPE_KEY = "scope"; private static final String DEFAULT_SCOPE = Artifact.SCOPE_COMPILE; @Parameter(defaultValue = "${session}", required = true, readonly = true) private MavenSession session; /** * The settings, from some settings.xml file. We use this to grab proxy information. */ @Parameter(defaultValue = "${settings}", readonly = true, required = true) public Settings settings; /** * 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 apiToken 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 = "apiToken") protected String apiToken; /** * 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 = "upload", defaultValue = "true") private boolean upload; /** * 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; /** * If this is set to true, the plugin will not grab configuration information from environment variables or the * SRC:CLR directory, relying only on Maven parameters. Used to isolate the Maven plugin from global configuration * settings. */ @Parameter(property = "useOnlyMavenParams", defaultValue = "false") private boolean useOnlyMavenParams; @Parameter(property = "srcclr.maven.skip", defaultValue = "false") private boolean skip; /** * If set to true, we will not attempt to contact Platform and acquire licensing information. */ @Parameter(property = "srcclr.license.provided", defaultValue = "false") private boolean licenseProvided; @Parameter(property = "headlessOutputFile") private File headlessOutputFile; /** * The workspaceSlug if this is an org-level agent. */ @Parameter(property = "workspaceSlug") protected String workspaceSlug; /** * 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") private FailureLevel failureThreshold; /** * Allows users to specify the scope of the scan when building the dependency graph. Possible values are: * - compile * - test * - import * - runtime * - system * - provided */ @Parameter(property = "scope") private String scopeParameter; @Component(hint = "default") private DependencyGraphBuilder dependencyGraphBuilder; EnvironmentProvider environmentProvider = new EnvironmentProviderImpl(); LibraryGraphContainer.Builder projectDependencyTreesBuilder = new LibraryGraphContainer.Builder(); /** * The final Project the current run will visit, which is obviously the current one for single-module projects, * but will vary wildly in the case of multi-module ones. */ MavenProject lastProject; private String pluginVersion; protected File getPathToTop() { if (session != null) { return session.getTopLevelProject().getFile().getParentFile(); } else { getLog().warn("Couldn't figure out the top level of the Maven project."); return new File("."); } } /** * Some setup that needs to be done once on this singleton Maven mojo. * * @throws MojoFailureException Upon various configure errors, see the handleCOnfig documentation. */ @VisibleForTesting void initialSetup() throws MojoFailureException { pluginVersion = ((PluginDescriptor) getPluginContext().get("pluginDescriptor")).getVersion(); // Safe so long as we keep things sequential, would have to change for parallel builds. if (lastProject == null) { 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 LibraryGraph getProjectDependencyGraph() throws MojoExecutionException, MojoFailureException { try { MavenProject currentProject = session.getCurrentProject(); Path pathToCurrPom = currentProject.getFile().toPath(); String relativePathToCurrPom = (Paths.get(getPathToTop().toString())).relativize(pathToCurrPom).toString(); MavenComponentGraphBuilder graphTranslator = new MavenComponentGraphBuilder(); DependencyNode mavenDependencyTree = dependencyGraphBuilder.buildDependencyGraph(currentProject, new ScopeArtifactFilter(getScope())); return graphTranslator.buildGraph(mavenDependencyTree, relativePathToCurrPom); } catch (DependencyGraphBuilderException e) { throw new MojoExecutionException("Encountered problem running the SRC:CLR maven plugin: " + e.getMessage()); } } /** * 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 { if (skip) { getLog().info("Skipping the SRC:CLR Maven Plugin because srcclr.maven.skip is set"); return; } initialSetup(); final Config config = Config.builder() .withPluginVersion(pluginVersion) .withApiToken(apiToken) .withApiURL(apiURL) .withFailureThreshold(failureThreshold) .withLogger(getLog()) .withLicenseProvided(licenseProvided) .withProjectName(projectName) .withPathToTop(getPathToTop()) .withSettings(settings) .withUpload(upload) .withUseOnlyMavenParams(useOnlyMavenParams) .withUserProjectID(userProjectID) .withHeadlessOutputFile(headlessOutputFile) .withVerbose(verbose) .withWorkspaceSlug(workspaceSlug) .build(); final Lifecycle lifecycle = determineLifecycle(config); lifecycle.configure(environmentProvider); projectDependencyTreesBuilder.withGraph(getProjectDependencyGraph()); // This works as long as we're doing this sequentially, but would have to change for parallel builds. if (lastProject.equals(session.getCurrentProject())) { LibraryGraphContainer depTrees = projectDependencyTreesBuilder.build(); lifecycle.execute(depTrees); } } static Lifecycle determineLifecycle(Config config) { if (config.getHeadlessOutputFile().isPresent()) { return new HeadlessLifecycle(config.getHeadlessOutputFile().get()); } else { return new DefaultLifecycle(config); } } /** * Returns the scope that is set by the user. There are three possible ways to set the scope: * * - the SRCCLR_SCAN_SCOPE environment variable * - the maven "scope" parameter * - the scope key in the srcclr.yml directives file * * The order that is presented above is also the order of precedence that determines the scope value. That is, if the * user supplies the scope with multiple ways, like for example, it's supplied by both the environment variable and * the maven parameter, the value read from the environment variable would be chosen and the parameter will be * ignored. * * If no scope is set, it defaults to {@code Artifact.SCOPE_COMPILE}. * @return the scope that is set by the user */ private String getScope() throws MojoFailureException { final String scope; final String envVarScope = environmentProvider.getenv(SCOPE_ENV_VAR); if (envVarScope != null) { scope = envVarScope; } else if (this.scopeParameter != null) { scope = this.scopeParameter; } else if (Files.exists(DIRECTIVES_FILE)) { scope = getScopeFromDirectivesFile(DIRECTIVES_FILE); } else { scope = DEFAULT_SCOPE; } return toArtifactScopeValue(scope); } /** * Returns the scope that is declared in the directivesFile. If it is not specified, it returns the default scope. * * @param directivesFile the file to read the scope from * @return the scope in the directivesFile */ static String getScopeFromDirectivesFile(Path directivesFile) throws MojoFailureException { final String scope; try (InputStream is = Files.newInputStream(directivesFile)) { final Map directives = Directives.parseDirectives(is); String scopeValue = (String) directives.get(DIRECTIVES_SCOPE_KEY); if (scopeValue != null) { scope = scopeValue; } else { scope = DEFAULT_SCOPE; } } catch (IOException e) { throw new MojoFailureException(e.getMessage()); } return scope; } /** * Returns the default scope if scope is not one of the values declared in {@code Artifact}. This ensures that we * always pass the right scope value to {@code ArtifactFilter}. * * @param scope the scope parameter * @return one of the scopes declared in {@code Artifact} */ static String toArtifactScopeValue(String scope) { switch (scope) { case Artifact.SCOPE_COMPILE: case Artifact.SCOPE_TEST: case Artifact.SCOPE_IMPORT: case Artifact.SCOPE_RUNTIME: case Artifact.SCOPE_PROVIDED: case Artifact.SCOPE_SYSTEM: return scope; default: return DEFAULT_SCOPE; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy