com.redhat.victims.VictimsRule.java Source code

Java tutorial

Introduction

Here is the source code for com.redhat.victims.VictimsRule.java

Source

package com.redhat.victims;

/*
 * #%L
 * This file is part of victims-enforcer.
 * %%
 * Copyright (C) 2013 The Victims Project
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import com.redhat.victims.database.VictimsDB;
import com.redhat.victims.database.VictimsDBInterface;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.enforcer.rule.api.EnforcerRule;
import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
import org.apache.maven.plugin.logging.Log;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.*;

/**
 * This class is the main entry point for working with the Maven Enforcer
 * plug-in. It provides the logic to synchronize a local database with a remote
 * database of vulnerable Java artifacts. This database is used to check for any
 * dependencies used within the project that may have known vulnerabilities.
 *
 * @author gmurphy
 */
public class VictimsRule implements EnforcerRule {

    /*
     * Configuration options available in pom.xml
     */
    private String metadata = Settings.defaults.get(Settings.METADATA);
    private String fingerprint = Settings.defaults.get(Settings.FINGERPRINT);
    private String updates = Settings.defaults.get(Settings.UPDATE_DATABASE);
    private String baseUrl = null;
    private String entryPoint = null;
    private String jdbcDriver = null;
    private String jdbcUrl = null;
    private String jdbcUser = null;
    private String jdbcPass = null;

    /**
     * Main entry point for the enforcer rule.
     *
     * @param helper
     * @throws EnforcerRuleException
     */

    public void execute(EnforcerRuleHelper helper) throws EnforcerRuleException {

        HashSet<Artifact> artifacts = new HashSet<Artifact>();
        ArtifactCollector[] collectors = { new DependencyTreeCollector(), new ReactorCollector(),
                new BaseArtifactCollector() };

        for (ArtifactCollector collector : collectors) {
            try {
                artifacts.addAll(collector.with(helper).getArtifacts());
            } catch (Throwable e) {
                // Expect failures for API incompatabilities
                helper.getLog()
                        .debug("[victims-enforcer] Artifact Collector failed: " + collector.getClass().getName());
                helper.getLog().debug(e.toString());
            }
        }

        execute(setupContext(helper.getLog()), artifacts);

    }

    /**
     * Configure execution context based on sensible defaults and
     * overrides in the pom.xml configuration.
     * @param log
     * @return Configured execution context
     * @throws EnforcerRuleException
     */
    public ExecutionContext setupContext(Log log) throws EnforcerRuleException {

        ExecutionContext ctx = new ExecutionContext();
        ctx.setLog(log);
        ctx.setSettings(new Settings());
        ctx.getSettings().set(Settings.METADATA, metadata);
        ctx.getSettings().set(Settings.FINGERPRINT, fingerprint);
        ctx.getSettings().set(Settings.UPDATE_DATABASE, updates);

        // Only need to query using one hashing mechanism
        System.setProperty(VictimsConfig.Key.ALGORITHMS, "SHA512");

        if (baseUrl != null) {
            System.setProperty(VictimsConfig.Key.URI, baseUrl);
            ctx.getSettings().set(VictimsConfig.Key.URI, baseUrl);
        }
        if (entryPoint != null) {
            System.setProperty(VictimsConfig.Key.ENTRY, entryPoint);
            ctx.getSettings().set(VictimsConfig.Key.URI, baseUrl);
        }
        if (jdbcDriver != null) {
            System.setProperty(VictimsConfig.Key.DB_DRIVER, jdbcDriver);
            ctx.getSettings().set(VictimsConfig.Key.DB_DRIVER, jdbcDriver);
        }
        if (jdbcUrl != null) {
            System.setProperty(VictimsConfig.Key.DB_URL, jdbcUrl);
            ctx.getSettings().set(VictimsConfig.Key.DB_URL, jdbcUrl);
        }
        if (jdbcUser != null) {
            System.setProperty(VictimsConfig.Key.DB_USER, jdbcUser);
            ctx.getSettings().set(VictimsConfig.Key.DB_USER, jdbcUser);
        }
        if (jdbcPass != null) {
            System.setProperty(VictimsConfig.Key.DB_PASS, jdbcPass);
            ctx.getSettings().set(VictimsConfig.Key.DB_PASS, "(not shown)");
        }

        // Setup database
        try {
            ctx.setDatabase(VictimsDB.db());
        } catch (VictimsException e) {
            log.debug(e);
            throw new EnforcerRuleException(e.getMessage());
        }

        // Setup cache
        try {
            ctx.setCache(new VictimsResultCache());
        } catch (VictimsException e) {
            log.debug(e);
            throw new EnforcerRuleException(e.getMessage());
        }

        // Validate settings
        try {
            ctx.getSettings().validate();
            ctx.getSettings().show(ctx.getLog());

        } catch (VictimsException e) {
            log.debug(e);
            throw new EnforcerRuleException(e.getMessage());
        }

        return ctx;
    }

    /**
     * Updates the database according to the given configuration
     * @param ctx
     * @throws VictimsException
     */
    public void updateDatabase(ExecutionContext ctx) throws VictimsException {

        Log log = ctx.getLog();

        // Disable updates via command line -Dvictims.skip.update=true
        String override = System.getProperty(Settings.UPDATES_OVERRIDE);
        if (override != null && override.equalsIgnoreCase("true")) {
            log.warn("[victims-enforcer] Updates disabled via system property.");
            return;
        }

        VictimsDBInterface db = ctx.getDatabase();
        Date updated = db.lastUpdated();

        // update automatically every time
        if (ctx.updateAlways()) {
            log.info(TextUI.fmt(Resources.INFO_UPDATES, updated.toString(), VictimsConfig.uri()));
            db.synchronize();

            // update once per day
        } else if (ctx.updateDaily()) {

            Date today = new Date();
            SimpleDateFormat cmp = new SimpleDateFormat("yyyyMMdd");
            boolean updatedToday = cmp.format(today).equals(cmp.format(updated));

            if (!updatedToday) {
                log.info(TextUI.fmt(Resources.INFO_UPDATES, updated.toString(), VictimsConfig.uri()));
                db.synchronize();

            } else {
                log.debug("[victims-enforcer] database last synchronized: " + updated.toString());
            }
        } else if (ctx.updateWeekly()) {

            Date today = new Date();
            SimpleDateFormat cmp = new SimpleDateFormat("yyyyw");
            if (cmp.format(today).equals(cmp.format(updated))) {
                log.info(TextUI.fmt(Resources.INFO_UPDATES, updated.toString(), VictimsConfig.uri()));
                db.synchronize();
            } else {
                log.debug("[victims-enforcer] database last synchronized: " + updated.toString());
            }

            // updates disabled 
        } else {
            log.debug("[victims-enforcer] database synchronization disabled.");
        }

    }

    /**
     * This is a helper method that processes a single result that 
     * has been executed. 
     * 
     * If any exceptions were thrown during execution they are inspected
     * to see if they indicate a vulnerable artifact. The result is also 
     * added to the cache for the current execution context.  
     * 
     * 
     * @param ctx - The execution context the result was generated under
     * @param result - The result to examine
     * @throws VictimsException
     * @throws VulnerableArtifactException
     * @throws EnforcerRuleException
     */
    private void processResult(ExecutionContext ctx, Future<ArtifactStub> result)
            throws VictimsException, VulnerableArtifactException, EnforcerRuleException {

        VictimsResultCache cache = ctx.getCache();
        Log log = ctx.getLog();

        try {

            ArtifactStub checked = result.get();
            if (checked != null) {
                log.debug("[victims-enforcer] done: " + checked.getId());
                cache.add(checked.getId(), null);
            }

        } catch (InterruptedException e) {
            log.info(e.getMessage());

        } catch (ExecutionException e) {

            log.debug(e);
            Throwable cause = e.getCause();

            if (cause instanceof VulnerableArtifactException) {

                VulnerableArtifactException ve = (VulnerableArtifactException) cause;
                // cache vulnerable artifact
                cache.add(ve.getId(), ve.getVulnerabilites());

                // Log the error and rethrow in case a fatal error
                log.warn(ve.getLogMessage());
                throw ve;

            } else {
                throw new EnforcerRuleException(e.getCause().getMessage());
            }
        }
    }

    /**
     * Scan the supplied artifacts given the provided execution context. An
     * exception will be raised if a vulnerable artifact has been detected.
     * @param ctx
     * @param artifacts
     * @throws EnforcerRuleException
     */
    public void execute(ExecutionContext ctx, Set<Artifact> artifacts) throws EnforcerRuleException {

        VictimsResultCache cache = ctx.getCache();
        Log log = ctx.getLog();
        int cores = Runtime.getRuntime().availableProcessors();
        ExecutorService executor = null;
        ExecutorCompletionService<ArtifactStub> completionService = null;
        List<Future<ArtifactStub>> results = null;

        try {

            // Synchronize database with victims service
            try {
                updateDatabase(ctx);
            } catch (VictimsException e) {
                log.warn("Unable to update victims database! Your CVE records might be out of date.");
                log.debug(e.toString());
            }
            // Concurrently process each dependency 
            executor = Executors.newFixedThreadPool(cores);
            completionService = new ExecutorCompletionService<ArtifactStub>(executor);

            results = new ArrayList<Future<ArtifactStub>>();

            for (Artifact a : artifacts) {

                // Check if we've already inspected this dependency
                if (cache.exists(a.getId())) {

                    HashSet<String> cves = cache.get(a.getId());
                    log.debug("[victims-enforcer] cached: " + a.getId());
                    if (!cves.isEmpty()) {

                        VulnerableArtifactException err = new VulnerableArtifactException(a, Settings.FINGERPRINT,
                                cves);
                        log.warn(err.getLogMessage());

                        if (err.isFatal(ctx)) {
                            throw new EnforcerRuleException(err.getErrorMessage());
                        }
                    }
                    continue;
                }

                // Not in cache process artifact
                results.add(completionService.submit(new VictimsCommand(ctx, a)));

                // Poll completion service for completed tasks to short circuit
                // on failure conditions.
                Future<ArtifactStub> result = completionService.poll();
                if (result != null) {
                    try {
                        results.remove(result);
                        processResult(ctx, result);

                    } catch (VulnerableArtifactException e) {

                        if (e.isFatal(ctx)) {
                            // Cancel other jobs
                            for (Future<ArtifactStub> f : results) {
                                f.cancel(true);
                            }
                            throw new EnforcerRuleException(e.getErrorMessage(), e);
                        }
                    }
                }

            }
            executor.shutdown();

            // Process any remaining results. 
            for (Future<ArtifactStub> future : results) {
                processResult(ctx, future);
            }

        } catch (VulnerableArtifactException e) {
            // fatal exception
            if (e.isFatal(ctx)) {
                throw new EnforcerRuleException(e.getErrorMessage());
            }

        } catch (VictimsException e) {
            log.debug(e);
            throw new EnforcerRuleException(e.getMessage());

        } finally {

            if (executor != null) {
                executor.shutdownNow();

            }
        }
    }

    /**
     * The database is always synchronized with the Victims Server. The initial
     * import of entries will take some time but subsequent requests should be
     * relatively fast.
     *
     * @return Always will return false.
     */
    public boolean isCacheable() {
        return false;
    }

    /**
     * Feature not used by this rule.
     *
     * @param er
     * @return Always returns false
     */
    public boolean isResultValid(EnforcerRule er) {
        return false;
    }

    /**
     * Feature not used by this plug-in. Return a bogus value.
     *
     * @return Not used
     */
    public String getCacheId() {
        return " " + new java.util.Date().getTime();
    }

}