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

com.redhat.victims.VictimsRule Maven / Gradle / Ivy

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 .
 * #L%
 */

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

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 com.redhat.victims.database.VictimsDB;
import com.redhat.victims.database.VictimsDBInterface;


/**
 * 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 artifacts = new HashSet();
      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 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 artifacts) throws EnforcerRuleException {
    
    VictimsResultCache cache = ctx.getCache();
    Log log = ctx.getLog();
    int cores = Runtime.getRuntime().availableProcessors();
    ExecutorService executor = null;
    ExecutorCompletionService completionService = null;
    List> 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(executor);
      
      results = new ArrayList>();
      
      for (Artifact a : artifacts){
           
        // Check if we've already inspected this dependency
        if (cache.exists(a.getId())){
          
          HashSet 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 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 f : results){
                f.cancel(true);
              }
              throw new EnforcerRuleException(e.getErrorMessage(), e);
            }
          }
        }
      
      }
      executor.shutdown();
      
      // Process any remaining results. 
      for (Future 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();
  }
  
  
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy