Skip to content

Acquia

AcsfDeployWorkspaceCleaned

Ensure the ACSF deploy workspace is clear. This can bloat ephemeral storage.

Class: Drutiny\Acquia\Audit\AcsfDeployWorkspaceCleaned
Extends: Drutiny\Audit
Package: drutiny/acquia

This class can remediate failed audits.

Policies

These are the policies that use this class:

Name Title
Acquia:CheckSiteFactoryWorkspace ACSF Deployment Workspace
Source
  public function audit(Sandbox $sandbox) {

    $output = $sandbox->exec('find /mnt/tmp/repo_* -maxdepth 1 -mindepth 1 -ctime +1 -type d');
    $lines = array_filter(explode(PHP_EOL, $output));

    $sandbox->setParameter('directories', $lines);

    return empty($lines);
  }

AppInfo

Ensure an environment has custom domains set.

Class: Drutiny\Acquia\Audit\AppInfo
Extends: Drutiny\Audit
Package: drutiny/acquia

Policies

These are the policies that use this class:

Name Title
Acquia:AppInfo Acquia Application Information
Source
  public function audit(Sandbox $sandbox) {

    $target = $sandbox->getTarget();
    if ($target instanceof AcquiaTargetInterface) {
      $env = $target->getEnvironment();
      $app = CloudApiV2::get('applications/' . $env['application']['uuid']);
    }
    else {
      $options = $target->getOptions();
      $app = CloudApiDrushAdaptor::findApplication($options['ac-realm'], $options['ac-site']);
    }

    $sandbox->setParameter('app', $app);

    return Audit::NOTICE;
  }

CustomDomains

Ensure an environment has custom domains set.

Class: Drutiny\Acquia\Audit\CustomDomains
Extends: Drutiny\Audit
Package: drutiny/acquia

Policies

These are the policies that use this class:

Name Title
Acquia:CustomDomains Custom Domains Registered
Source
  public function audit(Sandbox $sandbox) {

    $environment = CloudApiDrushAdaptor::getEnvironment($sandbox->getTarget());

    $domains = array_filter($environment['domains'], function ($domain) {
      // Do not include ELB domains or Acquia default domains.
      return !(strpos($domain, 'acquia-sites.com') || strpos($domain, 'elb.amazonaws.com') || strpos($domain, 'acsitefactory.com'));
    });

    if (empty($domains)) {
      return FALSE;
    }

    $sandbox->setParameter('domains', array_values($domains));

    return TRUE;
  }

EnvironmentAnalysis

Check to ensure Production Mode is enabled on Acquia Cloud.

Class: Drutiny\Acquia\Audit\EnvironmentAnalysis
Extends: Drutiny\Audit\AbstractAnalysis
Package: drutiny/acquia

Policies

These are the policies that use this class:

Name Title
Acquia:ProductionMode Acquia Production Mode
Source
  public function gather(Sandbox $sandbox) {

    $environment = CloudApiDrushAdaptor::getEnvironment($sandbox->getTarget());
    $app = CloudApiV2::get('applications/' . $environment['application']['uuid']);

    $sandbox->setParameter('environment', $environment);
    $sandbox->setParameter('app', $app);
  }
  final public function audit(Sandbox $sandbox)
  {
    $this->gather($sandbox);

    $expressionLanguage = new ExpressionLanguage($sandbox);

    $variables  = $sandbox->getParameterTokens();
    $sandbox->logger()->debug(__CLASS__ . ': ' . Yaml::dump($variables));

    $expression = $sandbox->getParameter('not_applicable', 'false');
    $sandbox->logger()->debug(__CLASS__ . ': ' . $expression);
    if (@$expressionLanguage->evaluate($expression, $variables)) {
      return self::NOT_APPLICABLE;
    }

    $expression = $sandbox->getParameter('expression', 'true');
    $sandbox->logger()->info(__CLASS__ . ': ' . $expression);
    return @$expressionLanguage->evaluate($expression, $variables);
  }

FilesPerDirectory

Class: Drutiny\Acquia\Audit\FilesPerDirectory
Extends: Drutiny\Audit
Package: drutiny/acquia

Policies

These are the policies that use this class:

Name Title
Acquia:FilesPerDirectory Less than 2,500 files per directory

Parameters

Name Type Description Default
limit integer The limit of files per directory. 2500

Tokens

Name Type Description Default
limit integer The limit of files per directory. 2500
directories array A multidimensional array containing directory data breaching the limit. { }
Source
  public function audit(Sandbox $sandbox) {
    // Build a count of files in every directory in the public filesystem.
    $stat = $sandbox->drush(['format' => 'json'])->status();
    $files = implode('/', [$stat['root'], $stat['files']]);
    $limit = $sandbox->getParameter('limit');

    // Note the trailing slash at the end of $files to ensure find works over
    // symlinks.
    $command = "find $files/ -type f -exec dirname {} \; | uniq -c | awk '\$1 > $limit'";

    // Execute and clean the output into usable data.
    $output = $sandbox->exec($command);
    $lines = array_filter(explode(PHP_EOL, $output));
    $directories = [];
    foreach ($lines as $line) {
      list($count, $directory) = explode(' ', trim($line));
      $directories[] = [
        'directory' => str_replace($stat['root'] . '/', '', $directory),
        'file_count' => $count,
      ];
    }

    // No limits breached, return as pass.
    if (empty($directories)) {
      return TRUE;
    }

    $sandbox->setParameter('directories', $directories);

    return FALSE;
  }

SearchDB

Ensure there is no use of search indexes in the database.

Class: Drutiny\Acquia\Audit\SearchDB
Extends: Drutiny\Plugin\Drupal7\Audit\ModuleDisabled
Package: drutiny/acquia

This class can remediate failed audits.

Policies

These are the policies that use this class:

Name Title
Acquia:Drupal7:NoDbSearch No Database Search Indexes for Drupal 7

Parameters

Name Type Description Default
module string The module to check is enabled. search_api_db

Tokens

Name Type Description Default
module string The module to check is enabled. search_api_db
indexes array An array of index names found in the database. Only available on failure. null
items integer The number of indexed items in the database. Only available on failure. null
Source
  public function audit(Sandbox $sandbox) {

    $sandbox->setParameter('module', 'search_api_db');
    if (parent::audit($sandbox)) {
      return TRUE;
    }

    // Find out if there are active indexes using the db service class.
    $output = $sandbox->drush()->sqlq("SELECT COUNT(i.machine_name) as count FROM {search_api_index} i LEFT JOIN {search_api_server} s ON i.server = s.machine_name WHERE i.status > 0 AND s.class = 'search_api_db_service';");
    if (empty($output)) {
      return TRUE;
    }
    elseif (count($output) == 1) {
      $number_of_db_indexes = (int) $output[0];
    }
    else {
      $number_of_db_indexes = (int) $output[1];
    }

    $sandbox->setParameter('indexes', $number_of_db_indexes);

    $output = $sandbox->drush()->sqlq('SELECT COUNT(item_id) FROM {search_api_db_default_node_index};');

    $sandbox->setParameter('items', $output);

    return FALSE;
  }

SecureDomains

Ensure an environment has custom domains set.

Class: Drutiny\Acquia\Audit\SecureDomains
Extends: Drutiny\Audit
Package: drutiny/acquia

Policies

These are the policies that use this class:

Name Title
Acquia:SecureDomains Secured Domains
Source
  public function audit(Sandbox $sandbox) {

    $environment = CloudApiDrushAdaptor::getEnvironment($sandbox->getTarget());

    // Check each IP associated with the "front" of the environment.
    $ssl_domains = $environment['ips'];

    // Default domain will point at either the Acquia balancers or the ELB.
    $ssl_domains[] = $environment['default_domain'];

    // Build a list of domains that "should" be secured by SSL certed hosted in
    // $ssl_domains with the exception of domains hosted at the edge.
    $domains = array_filter($environment['domains'], function ($domain) {
      // Do not include ELB domains or Acquia default domains.
      return !(strpos($domain, 'acquia-sites.com') || strpos($domain, 'elb.amazonaws.com') || strpos($domain, 'acsitefactory.com'));
    });

    $domain_details = [];
    foreach ($domains as $domain) {
      $domain_details[$domain] = [
        'name' => $domain,
        'secure' => FALSE,
        'certificate' => FALSE,
        'host' => FALSE,
      ];
    }
    $checked_certs = [];

    foreach ($ssl_domains as $host) {
      foreach ($domains as $sni_domain) {
        // No need to try any further certs for SNI domain if already confirmed
        // as secure.
        if ($domain_details[$sni_domain]['secure']) {
          continue;
        }

        try {
          $cert = $this->getCertificate($host, $sni_domain);
        }
        catch (\Exception $e) {
          $sandbox->logger()->warning($e->getMessage());
          continue;
        }

        // No need to check the same cert if we've picked it up from somewhere else.
        if (in_array($cert['extensions']['subjectKeyIdentifier'], $checked_certs)) {
          continue;
        }
        $checked_certs[] = $cert['extensions']['subjectKeyIdentifier'];

        // SANs are the only places where we can check for the domain.
        if (!isset($cert['extensions']['subjectAltName'])) {
          continue;
        }

        $san = $cert['extensions']['subjectAltName'];

        $secured_domains = array_filter($domains, function ($domain) use ($san) {
          return preg_match("/DNS:$domain(,|$)/", $san);
        });

        foreach ($secured_domains as $domain) {
          $domain_details[$domain]['secure'] = TRUE;
          $domain_details[$domain]['certificate'] = strtr('CN - O', $cert['subject']);
          $domain_details[$domain]['host'] = $host;
        }
      }
    }

    $insecured = array_filter($domain_details, function ($domain) {
      return !$domain['secure'];
    });

    $sandbox->setParameter('domains', array_values($domain_details));

    if (count($insecured) == count($domain_details)) {
      return FALSE;
    }

    return count($insecured) ? Audit::WARNING : TRUE;
  }

StackMetrics

Ensure an environment has custom domains set.

Class: Drutiny\Acquia\Audit\StackMetrics
Extends: Drutiny\Audit\AbstractAnalysis
Package: drutiny/acquia

Parameters

Name Type Description Default
metrics array one of apache-requests, bal-cpu, bal-memory, cron-memory, db-cpu, db-disk-size, db-disk-usage, db-memory, file-disk-size, file-cpu, file-disk-usage, file-memory, http-2xx, http-3xx, http-4xx, http-5xx, mysql-slow-query-count, nginx-requests, out-of-memory, php-proc-max-reached-site, php-proc-max-reached-total, php-proc-site, php-proc-total, varnish-cache-hit-rate, varnish-requests, web-cpu, web-memory - web-cpu
- web-memory

Tokens

Name Type Description Default
metrics array one of apache-requests, bal-cpu, bal-memory, cron-memory, db-cpu, db-disk-size, db-disk-usage, db-memory, file-disk-size, file-cpu, file-disk-usage, file-memory, http-2xx, http-3xx, http-4xx, http-5xx, mysql-slow-query-count, nginx-requests, out-of-memory, php-proc-max-reached-site, php-proc-max-reached-total, php-proc-site, php-proc-total, varnish-cache-hit-rate, varnish-requests, web-cpu, web-memory - web-cpu
- web-memory
Source
  public function gather(Sandbox $sandbox) {

    $target = $sandbox->getTarget();
    $env = ($target instanceof AcquiaTargetInterface) ? $target->getEnvironment() : CloudApiDrushAdaptor::getEnvironment($target);

    $metrics = $sandbox->getParameter('metrics');

    if (!is_array($metrics)) {
      throw new AuditValidationException("Metrics parameter must be an array. " . ucwords(gettype($metrics)) . ' given.');
    }

    $response = CloudApiV2::get('environments/' . $env['id'] . '/metrics/stackmetrics/data', [
      'filter' => implode(',', array_map(function ($metric) {
        return 'metric:' . $metric;
      }, $metrics)),
      'from' => $sandbox->getReportingPeriodStart()->format(\DateTime::ISO8601),
      'to' => $sandbox->getReportingPeriodEnd()->format(\DateTime::ISO8601),
    ]);

    $table_headers = ['Date'];
    $table_rows = [];

    foreach ($response['_embedded']['items'] as $item) {
      if (!empty($item['host'])) {
        list($item['name'],) = explode('.', $item['host'], 2);
      }
      if (!isset($item['name'])) {
        $item['name'] = $item['metric'];
      }
      elseif (count($metrics) > 1) {
        $item['name'] .= ':' . $item['metric'];
      }
      $table_headers[] = $item['name'];

      $idx = array_search($item['name'], $table_headers);
      foreach ($item['datapoints'] as $plot) {
        // $y == value
        // $x == epoch
        list($y, $x) = $plot;

        // Convert unix timestamp plot point to readable datetime.
        if (!isset($table_rows[$x])) {
          $table_rows[$x] = [ date('Y-m-d H:i:s', $x) ];
        }

        $table_rows[$x][$idx] = $y;
      }
    }

    // Sort the table columns by index.
    array_walk($table_rows, 'ksort');

    $sandbox->setParameter('result', $response);
    $sandbox->setParameter('env', $env);
    $sandbox->setParameter('table_headers', $table_headers);
    $sandbox->setParameter('table_rows', array_values($table_rows));

    // graph

    $graph = [
      'type' => 'line',
      'labels' => 'tr td:first-child',
      'hide-table' => TRUE,
      'height' => 250,
      'width' => 400,
      'stacked' => FALSE,
      'title' => $sandbox->getPolicy()->get('title'),
      'y-axis' => 'Percentage',
      'series' => [],
      'series-labels' => [],
      'legend' => 'bottom',
    ];

    foreach ($table_headers as $idx => $name) {
      if ($name == 'Date') {
        continue;
      }
      $nth = $idx + 1;
      $graph['series'][] = 'tr td:nth-child(' . $nth . ')';
      $graph['series-labels'][] = 'tr th:nth-child(' . $nth . ')';
    }
    $graph['series'] = implode(',', $graph['series']);
    $graph['series-labels'] = implode(',', $graph['series-labels']);

    $element = [];
    foreach ($graph as $key => $value) {
      $element[] = $key . '="' . $value . '"';
    }
    $element = '[[[' . implode(' ', $element) . ']]]';
    $sandbox->setParameter('graph', $element);
  }
  final public function audit(Sandbox $sandbox)
  {
    $this->gather($sandbox);

    $expressionLanguage = new ExpressionLanguage($sandbox);

    $variables  = $sandbox->getParameterTokens();
    $sandbox->logger()->debug(__CLASS__ . ': ' . Yaml::dump($variables));

    $expression = $sandbox->getParameter('not_applicable', 'false');
    $sandbox->logger()->debug(__CLASS__ . ': ' . $expression);
    if (@$expressionLanguage->evaluate($expression, $variables)) {
      return self::NOT_APPLICABLE;
    }

    $expression = $sandbox->getParameter('expression', 'true');
    $sandbox->logger()->info(__CLASS__ . ': ' . $expression);
    return @$expressionLanguage->evaluate($expression, $variables);
  }