diff options
author | Andrew Dolgov <[email protected]> | 2022-11-23 21:14:33 +0300 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2022-11-23 21:14:33 +0300 |
commit | 0c8af4992cb0f7589dcafaad65ada12753c64594 (patch) | |
tree | 18e83d068c3e7dd2499331de977782b382279396 /vendor/aws/aws-sdk-php/src/CloudTrail/LogFileIterator.php |
initial
Diffstat (limited to 'vendor/aws/aws-sdk-php/src/CloudTrail/LogFileIterator.php')
-rw-r--r-- | vendor/aws/aws-sdk-php/src/CloudTrail/LogFileIterator.php | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/vendor/aws/aws-sdk-php/src/CloudTrail/LogFileIterator.php b/vendor/aws/aws-sdk-php/src/CloudTrail/LogFileIterator.php new file mode 100644 index 0000000..4800433 --- /dev/null +++ b/vendor/aws/aws-sdk-php/src/CloudTrail/LogFileIterator.php @@ -0,0 +1,325 @@ +<?php +namespace Aws\CloudTrail; + +use Aws\S3\S3Client; +use Aws\CloudTrail\Exception\CloudTrailException; + +/** + * The `Aws\CloudTrail\LogFileIterator` provides an easy way to iterate over + * log file generated by AWS CloudTrail. + * + * CloudTrail log files contain data about your AWS API calls and are stored in + * Amazon S3 at a predictable path based on a bucket name, a key prefix, an + * account ID, a region, and date information. This class allows you to specify + * options, including a date range, and emits each log file that match the + * provided options. + * + * Yields an array containing the Amazon S3 bucket and key of the log file. + */ +class LogFileIterator extends \IteratorIterator +{ + // For internal use + const DEFAULT_TRAIL_NAME = 'Default'; + const PREFIX_TEMPLATE = 'prefix/AWSLogs/account/CloudTrail/region/date/'; + const PREFIX_WILDCARD = '*'; + + // Option names used internally or externally + const TRAIL_NAME = 'trail_name'; + const KEY_PREFIX = 'key_prefix'; + const START_DATE = 'start_date'; + const END_DATE = 'end_date'; + const ACCOUNT_ID = 'account_id'; + const LOG_REGION = 'log_region'; + + /** @var S3Client S3 client used to perform ListObjects operations */ + private $s3Client; + + /** @var string S3 bucket that contains the log files */ + private $s3BucketName; + + /** + * Constructs a LogRecordIterator. This factory method is used if the name + * of the S3 bucket containing your logs is not known. This factory method + * uses a CloudTrail client and the trail name (or "Default") to find the + * information about the trail necessary for constructing the + * LogRecordIterator. + * + * @param S3Client $s3Client + * @param CloudTrailClient $cloudTrailClient + * @param array $options + * + * @return LogRecordIterator + * @throws \InvalidArgumentException + * @see LogRecordIterator::__contruct + */ + public static function forTrail( + S3Client $s3Client, + CloudTrailClient $cloudTrailClient, + array $options = [] + ) { + $trailName = isset($options[self::TRAIL_NAME]) + ? $options[self::TRAIL_NAME] + : self::DEFAULT_TRAIL_NAME; + + $s3BucketName = null; + + // Use the CloudTrail client to get information about the trail, + // including the bucket name. + try { + $result = $cloudTrailClient->describeTrails([ + 'trailNameList' => [$trailName] + ]); + $s3BucketName = $result->search('trailList[0].S3BucketName'); + $options[self::KEY_PREFIX] = $result->search( + 'trailList[0].S3KeyPrefix' + ); + } catch (CloudTrailException $e) { + // There was an error describing the trail + } + + // If the bucket name is still unknown, then throw an exception + if (!$s3BucketName) { + $prev = isset($e) ? $e : null; + throw new \InvalidArgumentException('The bucket name could not ' + . 'be determined from the trail.', 0, $prev); + } + + return new self($s3Client, $s3BucketName, $options); + } + + /** + * Constructs a LogFileIterator using the specified options: + * + * - trail_name: The name of the trail that is generating our logs. If + * none is provided, then "Default" will be used, since that is the name + * of the trail created in the AWS Management Console. + * - key_prefix: The S3 key prefix of your log files. This value will be + * overwritten when using the `fromTrail()` method. However, if you are + * using the constructor, then this value will be used. + * - start_date: The timestamp of the beginning of date range of the log + * records you want to read. You can pass this in as a `DateTime` object, + * integer (unix timestamp), or a string compatible with `strtotime()`. + * - end_date: The timestamp of the end of date range of the log records + * you want to read. You can pass this in as a `DateTime` object, integer + * (unix timestamp), or a string compatible with `strtotime()`. + * - account_id: This is your AWS account ID, which is the 12-digit number + * found on the *Account Identifiers* section of the *AWS Security + * Credentials* page. See https://console.aws.amazon.com/iam/home?#security_credential + * - log_region: Region of the services of the log records you want to read. + * + * @param S3Client $s3Client + * @param string $s3BucketName + * @param array $options + */ + public function __construct( + S3Client $s3Client, + $s3BucketName, + array $options = [] + ) { + $this->s3Client = $s3Client; + $this->s3BucketName = $s3BucketName; + parent::__construct($this->buildListObjectsIterator($options)); + } + + /** + * An override of the typical current behavior of \IteratorIterator to + * format the output such that the bucket and key are returned in an array + * + * @return array|bool + */ + #[\ReturnTypeWillChange] + public function current() + { + if ($object = parent::current()) { + return [ + 'Bucket' => $this->s3BucketName, + 'Key' => $object['Key'] + ]; + } + + return false; + } + + /** + * Constructs an S3 ListObjects iterator, optionally decorated with + * FilterIterators, based on the provided options. + * + * @param array $options + * + * @return \Iterator + */ + private function buildListObjectsIterator(array $options) + { + // Extract and normalize the date values from the options + $startDate = isset($options[self::START_DATE]) + ? $this->normalizeDateValue($options[self::START_DATE]) + : null; + $endDate = isset($options[self::END_DATE]) + ? $this->normalizeDateValue($options[self::END_DATE]) + : null; + + // Determine the parts of the key prefix of the log files being read + $parts = [ + 'prefix' => isset($options[self::KEY_PREFIX]) + ? $options[self::KEY_PREFIX] + : null, + 'account' => isset($options[self::ACCOUNT_ID]) + ? $options[self::ACCOUNT_ID] + : self::PREFIX_WILDCARD, + 'region' => isset($options[self::LOG_REGION]) + ? $options[self::LOG_REGION] + : self::PREFIX_WILDCARD, + 'date' => $this->determineDateForPrefix($startDate, $endDate), + ]; + + // Determine the longest key prefix that can be used to retrieve all + // of the relevant log files. + $candidatePrefix = ltrim(strtr(self::PREFIX_TEMPLATE, $parts), '/'); + $logKeyPrefix = $candidatePrefix; + $index = strpos($candidatePrefix, self::PREFIX_WILDCARD); + + if ($index !== false) { + $logKeyPrefix = substr($candidatePrefix, 0, $index); + } + + // Create an iterator that will emit all of the objects matching the + // key prefix. + $objectsIterator = $this->s3Client->getIterator('ListObjects', [ + 'Bucket' => $this->s3BucketName, + 'Prefix' => $logKeyPrefix, + ]); + + // Apply regex and/or date filters to the objects iterator to emit only + // log files matching the options. + $objectsIterator = $this->applyRegexFilter( + $objectsIterator, + $logKeyPrefix, + $candidatePrefix + ); + + $objectsIterator = $this->applyDateFilter( + $objectsIterator, + $startDate, + $endDate + ); + + return $objectsIterator; + } + + /** + * Normalizes a date value to a unix timestamp + * + * @param int|string|\DateTimeInterface $date + * + * @return int + * @throws \InvalidArgumentException if the value cannot be converted to + * a timestamp + */ + private function normalizeDateValue($date) + { + if (is_string($date)) { + $date = strtotime($date); + } elseif ($date instanceof \DateTimeInterface) { + $date = $date->format('U'); + } elseif (!is_int($date)) { + throw new \InvalidArgumentException('Date values must be a ' + . 'string, an int, or a DateTime object.'); + } + + return $date; + } + + /** + * Uses the provided date values to determine the date portion of the prefix + */ + private function determineDateForPrefix($startDate, $endDate) + { + // The default date value should look like "*/*/*" after joining + $dateParts = array_fill_keys(['Y', 'm', 'd'], self::PREFIX_WILDCARD); + + // Narrow down the date by replacing the WILDCARDs with values if they + // are the same for the start and end date. + if ($startDate && $endDate) { + foreach ($dateParts as $key => &$value) { + $candidateValue = date($key, $startDate); + if ($candidateValue === date($key, $endDate)) { + $value = $candidateValue; + } else { + break; + } + } + } + + return join('/', $dateParts); + } + + /** + * Applies a regex iterator filter that limits the ListObjects result set + * based on the provided options. + * + * @param \Iterator $objectsIterator + * @param string $logKeyPrefix + * @param string $candidatePrefix + * + * @return \Iterator + */ + private function applyRegexFilter( + $objectsIterator, + $logKeyPrefix, + $candidatePrefix + ) { + // If the prefix and candidate prefix are not the same, then there were + // WILDCARDs. + if ($logKeyPrefix !== $candidatePrefix) { + // Turn the candidate prefix into a regex by trimming and + // converting WILDCARDs to regex notation. + $regex = rtrim($candidatePrefix, '/' . self::PREFIX_WILDCARD) . '/'; + $regex = strtr($regex, [self::PREFIX_WILDCARD => '[^/]+']); + + // After trimming WILDCARDs or the end, if the regex is the same as + // the prefix, then no regex is needed. + if ($logKeyPrefix !== $regex) { + // Apply a regex filter iterator to remove files that don't + // match the provided options. + $objectsIterator = new \CallbackFilterIterator( + $objectsIterator, + function ($object) use ($regex) { + return preg_match("#{$regex}#", $object['Key']); + } + ); + } + } + + return $objectsIterator; + } + + /** + * Applies an iterator filter to restrict the ListObjects result set to the + * specified date range. + * + * @param \Iterator $objectsIterator + * @param int $startDate + * @param int $endDate + * + * @return \Iterator + */ + private function applyDateFilter($objectsIterator, $startDate, $endDate) + { + // If either a start or end date was provided, filter out dates that + // don't match the date range. + if ($startDate || $endDate) { + $fn = function ($object) use ($startDate, $endDate) { + if (!preg_match('/[0-9]{8}T[0-9]{4}Z/', $object['Key'], $m)) { + return false; + } + $date = strtotime($m[0]); + + return (!$startDate || $date >= $startDate) + && (!$endDate || $date <= $endDate); + }; + $objectsIterator = new \CallbackFilterIterator($objectsIterator, $fn); + } + + return $objectsIterator; + } +} |