Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
namespace Aws\CloudTrail;
3
 
4
use Aws\S3\S3Client;
5
use Aws\CloudTrail\Exception\CloudTrailException;
6
 
7
/**
8
 * The `Aws\CloudTrail\LogFileIterator` provides an easy way to iterate over
9
 * log file generated by AWS CloudTrail.
10
 *
11
 * CloudTrail log files contain data about your AWS API calls and are stored in
12
 * Amazon S3 at a predictable path based on a bucket name, a key prefix, an
13
 * account ID, a region, and date information. This class allows you to specify
14
 * options, including a date range, and emits each log file that match the
15
 * provided options.
16
 *
17
 * Yields an array containing the Amazon S3 bucket and key of the log file.
18
 */
19
class LogFileIterator extends \IteratorIterator
20
{
21
    // For internal use
22
    const DEFAULT_TRAIL_NAME = 'Default';
23
    const PREFIX_TEMPLATE = 'prefix/AWSLogs/account/CloudTrail/region/date/';
24
    const PREFIX_WILDCARD = '*';
25
 
26
    // Option names used internally or externally
27
    const TRAIL_NAME = 'trail_name';
28
    const KEY_PREFIX = 'key_prefix';
29
    const START_DATE = 'start_date';
30
    const END_DATE = 'end_date';
31
    const ACCOUNT_ID = 'account_id';
32
    const LOG_REGION = 'log_region';
33
 
34
    /** @var S3Client S3 client used to perform ListObjects operations */
35
    private $s3Client;
36
 
37
    /** @var string S3 bucket that contains the log files */
38
    private $s3BucketName;
39
 
40
    /**
41
     * Constructs a LogRecordIterator. This factory method is used if the name
42
     * of the S3 bucket containing your logs is not known. This factory method
43
     * uses a CloudTrail client and the trail name (or "Default") to find the
44
     * information about the trail necessary for constructing the
45
     * LogRecordIterator.
46
     *
47
     * @param S3Client         $s3Client
48
     * @param CloudTrailClient $cloudTrailClient
49
     * @param array            $options
50
     *
51
     * @return LogRecordIterator
52
     * @throws \InvalidArgumentException
53
     * @see LogRecordIterator::__contruct
54
     */
55
    public static function forTrail(
56
        S3Client $s3Client,
57
        CloudTrailClient $cloudTrailClient,
58
        array $options = []
59
    ) {
60
        $trailName = isset($options[self::TRAIL_NAME])
61
            ? $options[self::TRAIL_NAME]
62
            : self::DEFAULT_TRAIL_NAME;
63
 
64
        $s3BucketName = null;
65
 
66
        // Use the CloudTrail client to get information about the trail,
67
        // including the bucket name.
68
        try {
69
            $result = $cloudTrailClient->describeTrails([
70
                'trailNameList' => [$trailName]
71
            ]);
72
            $s3BucketName = $result->search('trailList[0].S3BucketName');
73
            $options[self::KEY_PREFIX] = $result->search(
74
                'trailList[0].S3KeyPrefix'
75
            );
76
        } catch (CloudTrailException $e) {
77
            // There was an error describing the trail
78
        }
79
 
80
        // If the bucket name is still unknown, then throw an exception
81
        if (!$s3BucketName) {
82
            $prev = isset($e) ? $e : null;
83
            throw new \InvalidArgumentException('The bucket name could not '
84
                . 'be determined from the trail.', 0, $prev);
85
        }
86
 
87
        return new self($s3Client, $s3BucketName, $options);
88
    }
89
 
90
    /**
91
     * Constructs a LogFileIterator using the specified options:
92
     *
93
     * - trail_name: The name of the trail that is generating our logs. If
94
     *   none is provided, then "Default" will be used, since that is the name
95
     *   of the trail created in the AWS Management Console.
96
     * - key_prefix: The S3 key prefix of your log files. This value will be
97
     *   overwritten when using the `fromTrail()` method. However, if you are
98
     *   using the constructor, then this value will be used.
99
     * - start_date: The timestamp of the beginning of date range of the log
100
     *   records you want to read. You can pass this in as a `DateTime` object,
101
     *   integer (unix timestamp), or a string compatible with `strtotime()`.
102
     * - end_date: The timestamp of the end of date range of the log records
103
     *   you want to read. You can pass this in as a `DateTime` object, integer
104
     *   (unix timestamp), or a string compatible with `strtotime()`.
105
     * - account_id: This is your AWS account ID, which is the 12-digit number
106
     *   found on the *Account Identifiers* section of the *AWS Security
107
     *   Credentials* page. See https://console.aws.amazon.com/iam/home?#security_credential
108
     * - log_region: Region of the services of the log records you want to read.
109
     *
110
     * @param S3Client $s3Client
111
     * @param string   $s3BucketName
112
     * @param array    $options
113
     */
114
    public function __construct(
115
        S3Client $s3Client,
116
        $s3BucketName,
117
        array $options = []
118
    ) {
119
        $this->s3Client = $s3Client;
120
        $this->s3BucketName = $s3BucketName;
121
        parent::__construct($this->buildListObjectsIterator($options));
122
    }
123
 
124
    /**
125
     * An override of the typical current behavior of \IteratorIterator to
126
     * format the output such that the bucket and key are returned in an array
127
     *
128
     * @return array|bool
129
     */
130
    #[\ReturnTypeWillChange]
131
    public function current()
132
    {
133
        if ($object = parent::current()) {
134
            return [
135
                'Bucket' => $this->s3BucketName,
136
                'Key'    => $object['Key']
137
            ];
138
        }
139
 
140
        return false;
141
    }
142
 
143
    /**
144
     * Constructs an S3 ListObjects iterator, optionally decorated with
145
     * FilterIterators, based on the provided options.
146
     *
147
     * @param array $options
148
     *
149
     * @return \Iterator
150
     */
151
    private function buildListObjectsIterator(array $options)
152
    {
153
        // Extract and normalize the date values from the options
154
        $startDate = isset($options[self::START_DATE])
155
            ? $this->normalizeDateValue($options[self::START_DATE])
156
            : null;
157
        $endDate = isset($options[self::END_DATE])
158
            ? $this->normalizeDateValue($options[self::END_DATE])
159
            : null;
160
 
161
        // Determine the parts of the key prefix of the log files being read
162
        $parts = [
163
            'prefix' => isset($options[self::KEY_PREFIX])
164
                    ? $options[self::KEY_PREFIX]
165
                    : null,
166
            'account' => isset($options[self::ACCOUNT_ID])
167
                    ? $options[self::ACCOUNT_ID]
168
                    : self::PREFIX_WILDCARD,
169
            'region' => isset($options[self::LOG_REGION])
170
                    ? $options[self::LOG_REGION]
171
                    : self::PREFIX_WILDCARD,
172
            'date' => $this->determineDateForPrefix($startDate, $endDate),
173
        ];
174
 
175
        // Determine the longest key prefix that can be used to retrieve all
176
        // of the relevant log files.
177
        $candidatePrefix = ltrim(strtr(self::PREFIX_TEMPLATE, $parts), '/');
178
        $logKeyPrefix = $candidatePrefix;
179
        $index = strpos($candidatePrefix, self::PREFIX_WILDCARD);
180
 
181
        if ($index !== false) {
182
            $logKeyPrefix = substr($candidatePrefix, 0, $index);
183
        }
184
 
185
        // Create an iterator that will emit all of the objects matching the
186
        // key prefix.
187
        $objectsIterator = $this->s3Client->getIterator('ListObjects', [
188
            'Bucket' => $this->s3BucketName,
189
            'Prefix' => $logKeyPrefix,
190
        ]);
191
 
192
        // Apply regex and/or date filters to the objects iterator to emit only
193
        // log files matching the options.
194
        $objectsIterator = $this->applyRegexFilter(
195
            $objectsIterator,
196
            $logKeyPrefix,
197
            $candidatePrefix
198
        );
199
 
200
        $objectsIterator = $this->applyDateFilter(
201
            $objectsIterator,
202
            $startDate,
203
            $endDate
204
        );
205
 
206
        return $objectsIterator;
207
    }
208
 
209
    /**
210
     * Normalizes a date value to a unix timestamp
211
     *
212
     * @param int|string|\DateTimeInterface $date
213
     *
214
     * @return int
215
     * @throws \InvalidArgumentException if the value cannot be converted to
216
     *     a timestamp
217
     */
218
    private function normalizeDateValue($date)
219
    {
220
        if (is_string($date)) {
221
            $date = strtotime($date);
222
        } elseif ($date instanceof \DateTimeInterface) {
223
            $date = $date->format('U');
224
        } elseif (!is_int($date)) {
225
            throw new \InvalidArgumentException('Date values must be a '
226
                . 'string, an int, or a DateTime object.');
227
        }
228
 
229
        return $date;
230
    }
231
 
232
    /**
233
     * Uses the provided date values to determine the date portion of the prefix
234
     */
235
    private function determineDateForPrefix($startDate, $endDate)
236
    {
237
        // The default date value should look like "*/*/*" after joining
238
        $dateParts = array_fill_keys(['Y', 'm', 'd'], self::PREFIX_WILDCARD);
239
 
240
        // Narrow down the date by replacing the WILDCARDs with values if they
241
        // are the same for the start and end date.
242
        if ($startDate && $endDate) {
243
            foreach ($dateParts as $key => &$value) {
244
                $candidateValue = date($key, $startDate);
245
                if ($candidateValue === date($key, $endDate)) {
246
                    $value = $candidateValue;
247
                } else {
248
                    break;
249
                }
250
            }
251
        }
252
 
253
        return join('/', $dateParts);
254
    }
255
 
256
    /**
257
     * Applies a regex iterator filter that limits the ListObjects result set
258
     * based on the provided options.
259
     *
260
     * @param \Iterator $objectsIterator
261
     * @param string    $logKeyPrefix
262
     * @param string    $candidatePrefix
263
     *
264
     * @return \Iterator
265
     */
266
    private function applyRegexFilter(
267
        $objectsIterator,
268
        $logKeyPrefix,
269
        $candidatePrefix
270
    ) {
271
        // If the prefix and candidate prefix are not the same, then there were
272
        // WILDCARDs.
273
        if ($logKeyPrefix !== $candidatePrefix) {
274
            // Turn the candidate prefix into a regex by trimming and
275
            // converting WILDCARDs to regex notation.
276
            $regex = rtrim($candidatePrefix, '/' . self::PREFIX_WILDCARD) . '/';
277
            $regex = strtr($regex, [self::PREFIX_WILDCARD => '[^/]+']);
278
 
279
            // After trimming WILDCARDs or the end, if the regex is the same as
280
            // the prefix, then no regex is needed.
281
            if ($logKeyPrefix !== $regex) {
282
                // Apply a regex filter iterator to remove files that don't
283
                // match the provided options.
284
                $objectsIterator = new \CallbackFilterIterator(
285
                    $objectsIterator,
286
                    function ($object) use ($regex) {
287
                        return preg_match("#{$regex}#", $object['Key']);
288
                    }
289
                );
290
            }
291
        }
292
 
293
        return $objectsIterator;
294
    }
295
 
296
    /**
297
     * Applies an iterator filter to restrict the ListObjects result set to the
298
     * specified date range.
299
     *
300
     * @param \Iterator $objectsIterator
301
     * @param int       $startDate
302
     * @param int       $endDate
303
     *
304
     * @return \Iterator
305
     */
306
    private function applyDateFilter($objectsIterator, $startDate, $endDate)
307
    {
308
        // If either a start or end date was provided, filter out dates that
309
        // don't match the date range.
310
        if ($startDate || $endDate) {
311
            $fn = function ($object) use ($startDate, $endDate) {
312
                if (!preg_match('/[0-9]{8}T[0-9]{4}Z/', $object['Key'], $m)) {
313
                    return false;
314
                }
315
                $date = strtotime($m[0]);
316
 
317
                return (!$startDate || $date >= $startDate)
318
                    && (!$endDate || $date <= $endDate);
319
            };
320
            $objectsIterator = new \CallbackFilterIterator($objectsIterator, $fn);
321
        }
322
 
323
        return $objectsIterator;
324
    }
325
}