Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
namespace Aws\Credentials;
3
 
4
use Aws;
5
use Aws\Api\DateTimeResult;
6
use Aws\CacheInterface;
7
use Aws\Exception\CredentialsException;
8
use Aws\Sts\StsClient;
9
use GuzzleHttp\Promise;
10
/**
11
 * Credential providers are functions that accept no arguments and return a
12
 * promise that is fulfilled with an {@see \Aws\Credentials\CredentialsInterface}
13
 * or rejected with an {@see \Aws\Exception\CredentialsException}.
14
 *
15
 * <code>
16
 * use Aws\Credentials\CredentialProvider;
17
 * $provider = CredentialProvider::defaultProvider();
18
 * // Returns a CredentialsInterface or throws.
19
 * $creds = $provider()->wait();
20
 * </code>
21
 *
22
 * Credential providers can be composed to create credentials using conditional
23
 * logic that can create different credentials in different environments. You
24
 * can compose multiple providers into a single provider using
25
 * {@see Aws\Credentials\CredentialProvider::chain}. This function accepts
26
 * providers as variadic arguments and returns a new function that will invoke
27
 * each provider until a successful set of credentials is returned.
28
 *
29
 * <code>
30
 * // First try an INI file at this location.
31
 * $a = CredentialProvider::ini(null, '/path/to/file.ini');
32
 * // Then try an INI file at this location.
33
 * $b = CredentialProvider::ini(null, '/path/to/other-file.ini');
34
 * // Then try loading from environment variables.
35
 * $c = CredentialProvider::env();
36
 * // Combine the three providers together.
37
 * $composed = CredentialProvider::chain($a, $b, $c);
38
 * // Returns a promise that is fulfilled with credentials or throws.
39
 * $promise = $composed();
40
 * // Wait on the credentials to resolve.
41
 * $creds = $promise->wait();
42
 * </code>
43
 */
44
class CredentialProvider
45
{
46
    const ENV_ARN = 'AWS_ROLE_ARN';
47
    const ENV_KEY = 'AWS_ACCESS_KEY_ID';
48
    const ENV_PROFILE = 'AWS_PROFILE';
49
    const ENV_ROLE_SESSION_NAME = 'AWS_ROLE_SESSION_NAME';
50
    const ENV_SECRET = 'AWS_SECRET_ACCESS_KEY';
51
    const ENV_SESSION = 'AWS_SESSION_TOKEN';
52
    const ENV_TOKEN_FILE = 'AWS_WEB_IDENTITY_TOKEN_FILE';
53
    const ENV_SHARED_CREDENTIALS_FILE = 'AWS_SHARED_CREDENTIALS_FILE';
54
 
55
    /**
56
     * Create a default credential provider that
57
     * first checks for environment variables,
58
     * then checks for assumed role via web identity,
59
     * then checks for cached SSO credentials from the CLI,
60
     * then check for credential_process in the "default" profile in ~/.aws/credentials,
61
     * then checks for the "default" profile in ~/.aws/credentials,
62
     * then for credential_process in the "default profile" profile in ~/.aws/config,
63
     * then checks for "profile default" profile in ~/.aws/config (which is
64
     * the default profile of AWS CLI),
65
     * then tries to make a GET Request to fetch credentials if ECS environment variable is presented,
66
     * finally checks for EC2 instance profile credentials.
67
     *
68
     * This provider is automatically wrapped in a memoize function that caches
69
     * previously provided credentials.
70
     *
71
     * @param array $config Optional array of ecs/instance profile credentials
72
     *                      provider options.
73
     *
74
     * @return callable
75
     */
76
    public static function defaultProvider(array $config = [])
77
    {
78
        $cacheable = [
79
            'web_identity',
80
            'sso',
81
            'process_credentials',
82
            'process_config',
83
            'ecs',
84
            'instance'
85
        ];
86
 
87
        $profileName = getenv(self::ENV_PROFILE) ?: 'default';
88
 
89
        $defaultChain = [
90
            'env' => self::env(),
91
            'web_identity' => self::assumeRoleWithWebIdentityCredentialProvider($config),
92
        ];
93
        if (
94
            !isset($config['use_aws_shared_config_files'])
95
            || $config['use_aws_shared_config_files'] !== false
96
        ) {
97
            $defaultChain['sso'] = self::sso(
98
                $profileName,
99
                self::getHomeDir() . '/.aws/config',
100
                $config
101
            );
102
            $defaultChain['process_credentials'] = self::process();
103
            $defaultChain['ini'] = self::ini();
104
            $defaultChain['process_config'] = self::process(
105
                'profile ' . $profileName,
106
                self::getHomeDir() . '/.aws/config'
107
            );
108
            $defaultChain['ini_config'] = self::ini(
109
                'profile '. $profileName,
110
                self::getHomeDir() . '/.aws/config'
111
            );
112
        }
113
 
114
        if (self::shouldUseEcs()) {
115
            $defaultChain['ecs'] = self::ecsCredentials($config);
116
        } else {
117
            $defaultChain['instance'] = self::instanceProfile($config);
118
        }
119
 
120
        if (isset($config['credentials'])
121
            && $config['credentials'] instanceof CacheInterface
122
        ) {
123
            foreach ($cacheable as $provider) {
124
                if (isset($defaultChain[$provider])) {
125
                    $defaultChain[$provider] = self::cache(
126
                        $defaultChain[$provider],
127
                        $config['credentials'],
128
                        'aws_cached_' . $provider . '_credentials'
129
                    );
130
                }
131
            }
132
        }
133
 
134
        return self::memoize(
135
            call_user_func_array(
136
                [CredentialProvider::class, 'chain'],
137
                array_values($defaultChain)
138
            )
139
        );
140
    }
141
 
142
    /**
143
     * Create a credential provider function from a set of static credentials.
144
     *
145
     * @param CredentialsInterface $creds
146
     *
147
     * @return callable
148
     */
149
    public static function fromCredentials(CredentialsInterface $creds)
150
    {
151
        $promise = Promise\Create::promiseFor($creds);
152
 
153
        return function () use ($promise) {
154
            return $promise;
155
        };
156
    }
157
 
158
    /**
159
     * Creates an aggregate credentials provider that invokes the provided
160
     * variadic providers one after the other until a provider returns
161
     * credentials.
162
     *
163
     * @return callable
164
     */
165
    public static function chain()
166
    {
167
        $links = func_get_args();
168
        if (empty($links)) {
169
            throw new \InvalidArgumentException('No providers in chain');
170
        }
171
 
172
        return function ($previousCreds = null) use ($links) {
173
            /** @var callable $parent */
174
            $parent = array_shift($links);
175
            $promise = $parent();
176
            while ($next = array_shift($links)) {
177
                if ($next instanceof InstanceProfileProvider
178
                    && $previousCreds instanceof Credentials
179
                ) {
180
                    $promise = $promise->otherwise(
181
                        function () use ($next, $previousCreds) {return $next($previousCreds);}
182
                    );
183
                } else {
184
                    $promise = $promise->otherwise($next);
185
                }
186
            }
187
            return $promise;
188
        };
189
    }
190
 
191
    /**
192
     * Wraps a credential provider and caches previously provided credentials.
193
     *
194
     * Ensures that cached credentials are refreshed when they expire.
195
     *
196
     * @param callable $provider Credentials provider function to wrap.
197
     *
198
     * @return callable
199
     */
200
    public static function memoize(callable $provider)
201
    {
202
        return function () use ($provider) {
203
            static $result;
204
            static $isConstant;
205
 
206
            // Constant credentials will be returned constantly.
207
            if ($isConstant) {
208
                return $result;
209
            }
210
 
211
            // Create the initial promise that will be used as the cached value
212
            // until it expires.
213
            if (null === $result) {
214
                $result = $provider();
215
            }
216
 
217
            // Return credentials that could expire and refresh when needed.
218
            return $result
219
                ->then(function (CredentialsInterface $creds) use ($provider, &$isConstant, &$result) {
220
                    // Determine if these are constant credentials.
221
                    if (!$creds->getExpiration()) {
222
                        $isConstant = true;
223
                        return $creds;
224
                    }
225
 
226
                    // Refresh expired credentials.
227
                    if (!$creds->isExpired()) {
228
                        return $creds;
229
                    }
230
                    // Refresh the result and forward the promise.
231
                    return $result = $provider($creds);
232
                })
233
                ->otherwise(function($reason) use (&$result) {
234
                    // Cleanup rejected promise.
235
                    $result = null;
236
                    return new Promise\RejectedPromise($reason);
237
                });
238
        };
239
    }
240
 
241
    /**
242
     * Wraps a credential provider and saves provided credentials in an
243
     * instance of Aws\CacheInterface. Forwards calls when no credentials found
244
     * in cache and updates cache with the results.
245
     *
246
     * @param callable $provider Credentials provider function to wrap
247
     * @param CacheInterface $cache Cache to store credentials
248
     * @param string|null $cacheKey (optional) Cache key to use
249
     *
250
     * @return callable
251
     */
252
    public static function cache(
253
        callable $provider,
254
        CacheInterface $cache,
255
        $cacheKey = null
256
    ) {
257
        $cacheKey = $cacheKey ?: 'aws_cached_credentials';
258
 
259
        return function () use ($provider, $cache, $cacheKey) {
260
            $found = $cache->get($cacheKey);
261
            if ($found instanceof CredentialsInterface && !$found->isExpired()) {
262
                return Promise\Create::promiseFor($found);
263
            }
264
 
265
            return $provider()
266
                ->then(function (CredentialsInterface $creds) use (
267
                    $cache,
268
                    $cacheKey
269
                ) {
270
                    $cache->set(
271
                        $cacheKey,
272
                        $creds,
273
                        null === $creds->getExpiration() ?
274
 
275
                    );
276
 
277
                    return $creds;
278
                });
279
        };
280
    }
281
 
282
    /**
283
     * Provider that creates credentials from environment variables
284
     * AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN.
285
     *
286
     * @return callable
287
     */
288
    public static function env()
289
    {
290
        return function () {
291
            // Use credentials from environment variables, if available
292
            $key = getenv(self::ENV_KEY);
293
            $secret = getenv(self::ENV_SECRET);
294
            if ($key && $secret) {
295
                return Promise\Create::promiseFor(
296
                    new Credentials($key, $secret, getenv(self::ENV_SESSION) ?: NULL)
297
                );
298
            }
299
 
300
            return self::reject('Could not find environment variable '
301
                . 'credentials in ' . self::ENV_KEY . '/' . self::ENV_SECRET);
302
        };
303
    }
304
 
305
    /**
306
     * Credential provider that creates credentials using instance profile
307
     * credentials.
308
     *
309
     * @param array $config Array of configuration data.
310
     *
311
     * @return InstanceProfileProvider
312
     * @see Aws\Credentials\InstanceProfileProvider for $config details.
313
     */
314
    public static function instanceProfile(array $config = [])
315
    {
316
        return new InstanceProfileProvider($config);
317
    }
318
 
319
    /**
320
     * Credential provider that retrieves cached SSO credentials from the CLI
321
     *
322
     * @return callable
323
     */
324
    public static function sso($ssoProfileName = 'default',
325
                               $filename = null,
326
                               $config = []
327
    ) {
328
        $filename = $filename ?: (self::getHomeDir() . '/.aws/config');
329
 
330
        return function () use ($ssoProfileName, $filename, $config) {
331
            if (!@is_readable($filename)) {
332
                return self::reject("Cannot read credentials from $filename");
333
            }
334
            $profiles = self::loadProfiles($filename);
335
 
336
            if (isset($profiles[$ssoProfileName])) {
337
                $ssoProfile = $profiles[$ssoProfileName];
338
            } elseif (isset($profiles['profile ' . $ssoProfileName])) {
339
                $ssoProfileName = 'profile ' . $ssoProfileName;
340
                $ssoProfile = $profiles[$ssoProfileName];
341
            } else {
342
                return self::reject("Profile {$ssoProfileName} does not exist in {$filename}.");
343
            }
344
 
345
            if (!empty($ssoProfile['sso_session'])) {
346
                return CredentialProvider::getSsoCredentials($profiles, $ssoProfileName, $filename, $config);
347
            } else {
348
                return CredentialProvider::getSsoCredentialsLegacy($profiles, $ssoProfileName, $filename, $config);
349
            }
350
        };
351
    }
352
 
353
    /**
354
     * Credential provider that creates credentials using
355
     * ecs credentials by a GET request, whose uri is specified
356
     * by environment variable
357
     *
358
     * @param array $config Array of configuration data.
359
     *
360
     * @return EcsCredentialProvider
361
     * @see Aws\Credentials\EcsCredentialProvider for $config details.
362
     */
363
    public static function ecsCredentials(array $config = [])
364
    {
365
        return new EcsCredentialProvider($config);
366
    }
367
 
368
    /**
369
     * Credential provider that creates credentials using assume role
370
     *
371
     * @param array $config Array of configuration data
372
     * @return callable
373
     * @see Aws\Credentials\AssumeRoleCredentialProvider for $config details.
374
     */
375
    public static function assumeRole(array $config=[])
376
    {
377
        return new AssumeRoleCredentialProvider($config);
378
    }
379
 
380
    /**
381
     * Credential provider that creates credentials by assuming role from a
382
     * Web Identity Token
383
     *
384
     * @param array $config Array of configuration data
385
     * @return callable
386
     * @see Aws\Credentials\AssumeRoleWithWebIdentityCredentialProvider for
387
     * $config details.
388
     */
389
    public static function assumeRoleWithWebIdentityCredentialProvider(array $config = [])
390
    {
391
        return function () use ($config) {
392
            $arnFromEnv = getenv(self::ENV_ARN);
393
            $tokenFromEnv = getenv(self::ENV_TOKEN_FILE);
394
            $stsClient = isset($config['stsClient'])
395
                ? $config['stsClient']
396
                : null;
397
            $region = isset($config['region'])
398
                ? $config['region']
399
                : null;
400
 
401
            if ($tokenFromEnv && $arnFromEnv) {
402
                $sessionName = getenv(self::ENV_ROLE_SESSION_NAME)
403
                    ? getenv(self::ENV_ROLE_SESSION_NAME)
404
                    : null;
405
                $provider = new AssumeRoleWithWebIdentityCredentialProvider([
406
                    'RoleArn' => $arnFromEnv,
407
                    'WebIdentityTokenFile' => $tokenFromEnv,
408
                    'SessionName' => $sessionName,
409
                    'client' => $stsClient,
410
                    'region' => $region
411
                ]);
412
 
413
                return $provider();
414
            }
415
 
416
            $profileName = getenv(self::ENV_PROFILE) ?: 'default';
417
            if (isset($config['filename'])) {
418
                $profiles = self::loadProfiles($config['filename']);
419
            } else {
420
                $profiles = self::loadDefaultProfiles();
421
            }
422
 
423
            if (isset($profiles[$profileName])) {
424
                $profile = $profiles[$profileName];
425
                if (isset($profile['region'])) {
426
                    $region = $profile['region'];
427
                }
428
                if (isset($profile['web_identity_token_file'])
429
                    && isset($profile['role_arn'])
430
                ) {
431
                    $sessionName = isset($profile['role_session_name'])
432
                        ? $profile['role_session_name']
433
                        : null;
434
                    $provider = new AssumeRoleWithWebIdentityCredentialProvider([
435
                        'RoleArn' => $profile['role_arn'],
436
                        'WebIdentityTokenFile' => $profile['web_identity_token_file'],
437
                        'SessionName' => $sessionName,
438
                        'client' => $stsClient,
439
                        'region' => $region
440
                    ]);
441
 
442
                    return $provider();
443
                }
444
            } else {
445
                return self::reject("Unknown profile: $profileName");
446
            }
447
            return self::reject("No RoleArn or WebIdentityTokenFile specified");
448
        };
449
    }
450
 
451
    /**
452
     * Credentials provider that creates credentials using an ini file stored
453
     * in the current user's home directory.  A source can be provided
454
     * in this file for assuming a role using the credential_source config option.
455
     *
456
     * @param string|null $profile  Profile to use. If not specified will use
457
     *                              the "default" profile in "~/.aws/credentials".
458
     * @param string|null $filename If provided, uses a custom filename rather
459
     *                              than looking in the home directory.
460
     * @param array|null $config If provided, may contain the following:
461
     *                           preferStaticCredentials: If true, prefer static
462
     *                           credentials to role_arn if both are present
463
     *                           disableAssumeRole: If true, disable support for
464
     *                           roles that assume an IAM role. If true and role profile
465
     *                           is selected, an error is raised.
466
     *                           stsClient: StsClient used to assume role specified in profile
467
     *
468
     * @return callable
469
     */
470
    public static function ini($profile = null, $filename = null, array $config = [])
471
    {
472
        $filename = self::getFileName($filename);
473
        $profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');
474
 
475
        return function () use ($profile, $filename, $config) {
476
            $preferStaticCredentials = isset($config['preferStaticCredentials'])
477
                ? $config['preferStaticCredentials']
478
                : false;
479
            $disableAssumeRole = isset($config['disableAssumeRole'])
480
                ? $config['disableAssumeRole']
481
                : false;
482
            $stsClient = isset($config['stsClient']) ? $config['stsClient'] : null;
483
 
484
            if (!@is_readable($filename)) {
485
                return self::reject("Cannot read credentials from $filename");
486
            }
487
            $data = self::loadProfiles($filename);
488
            if ($data === false) {
489
                return self::reject("Invalid credentials file: $filename");
490
            }
491
            if (!isset($data[$profile])) {
492
                return self::reject("'$profile' not found in credentials file");
493
            }
494
 
495
            /*
496
            In the CLI, the presence of both a role_arn and static credentials have
497
            different meanings depending on how many profiles have been visited. For
498
            the first profile processed, role_arn takes precedence over any static
499
            credentials, but for all subsequent profiles, static credentials are
500
            used if present, and only in their absence will the profile's
501
            source_profile and role_arn keys be used to load another set of
502
            credentials. This bool is intended to yield compatible behaviour in this
503
            sdk.
504
            */
505
            $preferStaticCredentialsToRoleArn = ($preferStaticCredentials
506
                && isset($data[$profile]['aws_access_key_id'])
507
                && isset($data[$profile]['aws_secret_access_key']));
508
 
509
            if (isset($data[$profile]['role_arn'])
510
                && !$preferStaticCredentialsToRoleArn
511
            ) {
512
                if ($disableAssumeRole) {
513
                    return self::reject(
514
                        "Role assumption profiles are disabled. "
515
                        . "Failed to load profile " . $profile);
516
                }
517
                return self::loadRoleProfile(
518
                    $data,
519
                    $profile,
520
                    $filename,
521
                    $stsClient,
522
                    $config
523
                );
524
            }
525
 
526
            if (!isset($data[$profile]['aws_access_key_id'])
527
                || !isset($data[$profile]['aws_secret_access_key'])
528
            ) {
529
                return self::reject("No credentials present in INI profile "
530
                    . "'$profile' ($filename)");
531
            }
532
 
533
            if (empty($data[$profile]['aws_session_token'])) {
534
                $data[$profile]['aws_session_token']
535
                    = isset($data[$profile]['aws_security_token'])
536
                    ? $data[$profile]['aws_security_token']
537
                    : null;
538
            }
539
 
540
            return Promise\Create::promiseFor(
541
                new Credentials(
542
                    $data[$profile]['aws_access_key_id'],
543
                    $data[$profile]['aws_secret_access_key'],
544
                    $data[$profile]['aws_session_token']
545
                )
546
            );
547
        };
548
    }
549
 
550
    /**
551
     * Credentials provider that creates credentials using a process configured in
552
     * ini file stored in the current user's home directory.
553
     *
554
     * @param string|null $profile  Profile to use. If not specified will use
555
     *                              the "default" profile in "~/.aws/credentials".
556
     * @param string|null $filename If provided, uses a custom filename rather
557
     *                              than looking in the home directory.
558
     *
559
     * @return callable
560
     */
561
    public static function process($profile = null, $filename = null)
562
    {
563
        $filename = self::getFileName($filename);
564
        $profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');
565
 
566
        return function () use ($profile, $filename) {
567
            if (!@is_readable($filename)) {
568
                return self::reject("Cannot read process credentials from $filename");
569
            }
570
            $data = \Aws\parse_ini_file($filename, true, INI_SCANNER_RAW);
571
            if ($data === false) {
572
                return self::reject("Invalid credentials file: $filename");
573
            }
574
            if (!isset($data[$profile])) {
575
                return self::reject("'$profile' not found in credentials file");
576
            }
577
            if (!isset($data[$profile]['credential_process'])) {
578
                return self::reject("No credential_process present in INI profile "
579
                    . "'$profile' ($filename)");
580
            }
581
 
582
            $credentialProcess = $data[$profile]['credential_process'];
583
            $json = shell_exec($credentialProcess);
584
 
585
            $processData = json_decode($json, true);
586
 
587
            // Only support version 1
588
            if (isset($processData['Version'])) {
589
                if ($processData['Version'] !== 1) {
590
                    return self::reject("credential_process does not return Version == 1");
591
                }
592
            }
593
 
594
            if (!isset($processData['AccessKeyId'])
595
                || !isset($processData['SecretAccessKey']))
596
            {
597
                return self::reject("credential_process does not return valid credentials");
598
            }
599
 
600
            if (isset($processData['Expiration'])) {
601
                try {
602
                    $expiration = new DateTimeResult($processData['Expiration']);
603
                } catch (\Exception $e) {
604
                    return self::reject("credential_process returned invalid expiration");
605
                }
606
                $now = new DateTimeResult();
607
                if ($expiration < $now) {
608
                    return self::reject("credential_process returned expired credentials");
609
                }
610
                $expires = $expiration->getTimestamp();
611
            } else {
612
                $expires = null;
613
            }
614
 
615
            if (empty($processData['SessionToken'])) {
616
                $processData['SessionToken'] = null;
617
            }
618
 
619
            return Promise\Create::promiseFor(
620
                new Credentials(
621
                    $processData['AccessKeyId'],
622
                    $processData['SecretAccessKey'],
623
                    $processData['SessionToken'],
624
                    $expires
625
                )
626
            );
627
        };
628
    }
629
 
630
    /**
631
     * Assumes role for profile that includes role_arn
632
     *
633
     * @return callable
634
     */
635
    private static function loadRoleProfile(
636
        $profiles,
637
        $profileName,
638
        $filename,
639
        $stsClient,
640
        $config = []
641
    ) {
642
        $roleProfile = $profiles[$profileName];
643
        $roleArn = isset($roleProfile['role_arn']) ? $roleProfile['role_arn'] : '';
644
        $roleSessionName = isset($roleProfile['role_session_name'])
645
            ? $roleProfile['role_session_name']
646
            : 'aws-sdk-php-' . round(microtime(true) * 1000);
647
 
648
        if (
649
            empty($roleProfile['source_profile'])
650
            == empty($roleProfile['credential_source'])
651
        ) {
652
            return self::reject("Either source_profile or credential_source must be set " .
653
                "using profile " . $profileName . ", but not both."
654
            );
655
        }
656
 
657
        $sourceProfileName = "";
658
        if (!empty($roleProfile['source_profile'])) {
659
            $sourceProfileName = $roleProfile['source_profile'];
660
            if (!isset($profiles[$sourceProfileName])) {
661
                return self::reject("source_profile " . $sourceProfileName
662
                    . " using profile " . $profileName . " does not exist"
663
                );
664
            }
665
            if (isset($config['visited_profiles']) &&
666
                in_array($roleProfile['source_profile'], $config['visited_profiles'])
667
            ) {
668
                return self::reject("Circular source_profile reference found.");
669
            }
670
            $config['visited_profiles'] [] = $roleProfile['source_profile'];
671
        } else {
672
            if (empty($roleArn)) {
673
                return self::reject(
674
                    "A role_arn must be provided with credential_source in " .
675
                    "file {$filename} under profile {$profileName} "
676
                );
677
            }
678
        }
679
 
680
        if (empty($stsClient)) {
681
            $sourceRegion = isset($profiles[$sourceProfileName]['region'])
682
                ? $profiles[$sourceProfileName]['region']
683
                : 'us-east-1';
684
            $config['preferStaticCredentials'] = true;
685
            $sourceCredentials = null;
686
            if (!empty($roleProfile['source_profile'])){
687
                $sourceCredentials = call_user_func(
688
                    CredentialProvider::ini($sourceProfileName, $filename, $config)
689
                )->wait();
690
            } else {
691
                $sourceCredentials = self::getCredentialsFromSource(
692
                    $profileName,
693
                    $filename
694
                );
695
            }
696
            $stsClient = new StsClient([
697
                'credentials' => $sourceCredentials,
698
                'region' => $sourceRegion,
699
                'version' => '2011-06-15',
700
            ]);
701
        }
702
 
703
        $result = $stsClient->assumeRole([
704
            'RoleArn' => $roleArn,
705
            'RoleSessionName' => $roleSessionName
706
        ]);
707
 
708
        $credentials = $stsClient->createCredentials($result);
709
        return Promise\Create::promiseFor($credentials);
710
    }
711
 
712
    /**
713
     * Gets the environment's HOME directory if available.
714
     *
715
     * @return null|string
716
     */
717
    private static function getHomeDir()
718
    {
719
        // On Linux/Unix-like systems, use the HOME environment variable
720
        if ($homeDir = getenv('HOME')) {
721
            return $homeDir;
722
        }
723
 
724
        // Get the HOMEDRIVE and HOMEPATH values for Windows hosts
725
        $homeDrive = getenv('HOMEDRIVE');
726
        $homePath = getenv('HOMEPATH');
727
 
728
        return ($homeDrive && $homePath) ? $homeDrive . $homePath : null;
729
    }
730
 
731
    /**
732
     * Gets profiles from specified $filename, or default ini files.
733
     */
734
    private static function loadProfiles($filename)
735
    {
736
        $profileData = \Aws\parse_ini_file($filename, true, INI_SCANNER_RAW);
737
 
738
        // If loading .aws/credentials, also load .aws/config when AWS_SDK_LOAD_NONDEFAULT_CONFIG is set
739
        if ($filename === self::getHomeDir() . '/.aws/credentials'
740
            && getenv('AWS_SDK_LOAD_NONDEFAULT_CONFIG')
741
        ) {
742
            $configFilename = self::getHomeDir() . '/.aws/config';
743
            $configProfileData = \Aws\parse_ini_file($configFilename, true, INI_SCANNER_RAW);
744
            foreach ($configProfileData as $name => $profile) {
745
                // standardize config profile names
746
                $name = str_replace('profile ', '', $name);
747
                if (!isset($profileData[$name])) {
748
                    $profileData[$name] = $profile;
749
                }
750
            }
751
        }
752
 
753
        return $profileData;
754
    }
755
 
756
    /**
757
     * Gets profiles from ~/.aws/credentials and ~/.aws/config ini files
758
     */
759
    private static function loadDefaultProfiles() {
760
        $profiles = [];
761
        $credFile = self::getHomeDir() . '/.aws/credentials';
762
        $configFile = self::getHomeDir() . '/.aws/config';
763
        if (file_exists($credFile)) {
764
            $profiles = \Aws\parse_ini_file($credFile, true, INI_SCANNER_RAW);
765
        }
766
 
767
        if (file_exists($configFile)) {
768
            $configProfileData = \Aws\parse_ini_file($configFile, true, INI_SCANNER_RAW);
769
            foreach ($configProfileData as $name => $profile) {
770
                // standardize config profile names
771
                $name = str_replace('profile ', '', $name);
772
                if (!isset($profiles[$name])) {
773
                    $profiles[$name] = $profile;
774
                }
775
            }
776
        }
777
 
778
        return $profiles;
779
    }
780
 
781
    public static function getCredentialsFromSource(
782
        $profileName = '',
783
        $filename = '',
784
        $config = []
785
    ) {
786
        $data = self::loadProfiles($filename);
787
        $credentialSource = !empty($data[$profileName]['credential_source'])
788
            ? $data[$profileName]['credential_source']
789
            : null;
790
        $credentialsPromise = null;
791
 
792
        switch ($credentialSource) {
793
            case 'Environment':
794
                $credentialsPromise = self::env();
795
                break;
796
            case 'Ec2InstanceMetadata':
797
                $credentialsPromise = self::instanceProfile($config);
798
                break;
799
            case 'EcsContainer':
800
                $credentialsPromise = self::ecsCredentials($config);
801
                break;
802
            default:
803
                throw new CredentialsException(
804
                    "Invalid credential_source found in config file: {$credentialSource}. Valid inputs "
805
                    . "include Environment, Ec2InstanceMetadata, and EcsContainer."
806
                );
807
        }
808
 
809
        $credentialsResult = null;
810
        try {
811
            $credentialsResult = $credentialsPromise()->wait();
812
        } catch (\Exception $reason) {
813
            return self::reject(
814
                "Unable to successfully retrieve credentials from the source specified in the"
815
                . " credentials file: {$credentialSource}; failure message was: "
816
                . $reason->getMessage()
817
            );
818
        }
819
        return function () use ($credentialsResult) {
820
            return Promise\Create::promiseFor($credentialsResult);
821
        };
822
    }
823
 
824
    private static function reject($msg)
825
    {
826
        return new Promise\RejectedPromise(new CredentialsException($msg));
827
    }
828
 
829
    /**
830
     * @param $filename
831
     * @return string
832
     */
833
    private static function getFileName($filename)
834
    {
835
        if (!isset($filename)) {
836
            $filename = getenv(self::ENV_SHARED_CREDENTIALS_FILE) ?:
837
                (self::getHomeDir() . '/.aws/credentials');
838
        }
839
        return $filename;
840
    }
841
 
842
    /**
843
     * @return boolean
844
     */
845
    public static function shouldUseEcs()
846
    {
847
        //Check for relative uri. if not, then full uri.
848
        //fall back to server for each as getenv is not thread-safe.
849
        return !empty(getenv(EcsCredentialProvider::ENV_URI))
850
            || !empty($_SERVER[EcsCredentialProvider::ENV_URI])
851
            || !empty(getenv(EcsCredentialProvider::ENV_FULL_URI))
852
            || !empty($_SERVER[EcsCredentialProvider::ENV_FULL_URI]);
853
    }
854
 
855
    /**
856
     * @param $profiles
857
     * @param $ssoProfileName
858
     * @param $filename
859
     * @param $config
860
     * @return Promise\PromiseInterface
861
     */
862
    private static function getSsoCredentials($profiles, $ssoProfileName, $filename, $config)
863
    {
864
        if (empty($config['ssoOidcClient'])) {
865
            $ssoProfile = $profiles[$ssoProfileName];
866
            $sessionName = $ssoProfile['sso_session'];
867
            if (empty($profiles['sso-session ' . $sessionName])) {
868
                return self::reject(
869
                    "Could not find sso-session {$sessionName} in {$filename}"
870
                );
871
            }
872
            $ssoSession = $profiles['sso-session ' . $ssoProfile['sso_session']];
873
            $ssoOidcClient = new Aws\SSOOIDC\SSOOIDCClient([
874
                'region' => $ssoSession['sso_region'],
875
                'version' => '2019-06-10',
876
                'credentials' => false
877
            ]);
878
        } else {
879
            $ssoOidcClient = $config['ssoClient'];
880
        }
881
 
882
        $tokenPromise = new Aws\Token\SsoTokenProvider(
883
            $ssoProfileName,
884
            $filename,
885
            $ssoOidcClient
886
        );
887
        $token = $tokenPromise()->wait();
888
        $ssoCredentials = CredentialProvider::getCredentialsFromSsoService(
889
            $ssoProfile,
890
            $ssoSession['sso_region'],
891
            $token->getToken(),
892
            $config
893
        );
894
        $expiration = $ssoCredentials['expiration'];
895
        return Promise\Create::promiseFor(
896
            new Credentials(
897
                $ssoCredentials['accessKeyId'],
898
                $ssoCredentials['secretAccessKey'],
899
                $ssoCredentials['sessionToken'],
900
                $expiration
901
            )
902
        );
903
    }
904
 
905
    /**
906
     * @param $profiles
907
     * @param $ssoProfileName
908
     * @param $filename
909
     * @param $config
910
     * @return Promise\PromiseInterface
911
     */
912
    private static function getSsoCredentialsLegacy($profiles, $ssoProfileName, $filename, $config)
913
    {
914
        $ssoProfile = $profiles[$ssoProfileName];
915
        if (empty($ssoProfile['sso_start_url'])
916
            || empty($ssoProfile['sso_region'])
917
            || empty($ssoProfile['sso_account_id'])
918
            || empty($ssoProfile['sso_role_name'])
919
        ) {
920
            return self::reject(
921
                "Profile {$ssoProfileName} in {$filename} must contain the following keys: "
922
                . "sso_start_url, sso_region, sso_account_id, and sso_role_name."
923
            );
924
        }
925
        $tokenLocation = self::getHomeDir()
926
            . '/.aws/sso/cache/'
927
            . sha1($ssoProfile['sso_start_url'])
928
            . ".json";
929
 
930
        if (!@is_readable($tokenLocation)) {
931
            return self::reject("Unable to read token file at $tokenLocation");
932
        }
933
        $tokenData = json_decode(file_get_contents($tokenLocation), true);
934
        if (empty($tokenData['accessToken']) || empty($tokenData['expiresAt'])) {
935
            return self::reject(
936
                "Token file at {$tokenLocation} must contain an access token and an expiration"
937
            );
938
        }
939
        try {
940
            $expiration = (new DateTimeResult($tokenData['expiresAt']))->getTimestamp();
941
        } catch (\Exception $e) {
942
            return self::reject("Cached SSO credentials returned an invalid expiration");
943
        }
944
        $now = time();
945
        if ($expiration < $now) {
946
            return self::reject("Cached SSO credentials returned expired credentials");
947
        }
948
        $ssoCredentials = CredentialProvider::getCredentialsFromSsoService(
949
            $ssoProfile,
950
            $ssoProfile['sso_region'],
951
            $tokenData['accessToken'],
952
            $config
953
        );
954
        return Promise\Create::promiseFor(
955
            new Credentials(
956
                $ssoCredentials['accessKeyId'],
957
                $ssoCredentials['secretAccessKey'],
958
                $ssoCredentials['sessionToken'],
959
                $expiration
960
            )
961
        );
962
    }
963
    /**
964
     * @param array $ssoProfile
965
     * @param string $clientRegion
966
     * @param string $accessToken
967
     * @param array $config
968
     * @return array|null
969
     */
970
    private static function getCredentialsFromSsoService($ssoProfile, $clientRegion, $accessToken, $config)
971
    {
972
        if (empty($config['ssoClient'])) {
973
            $ssoClient = new Aws\SSO\SSOClient([
974
                'region' => $clientRegion,
975
                'version' => '2019-06-10',
976
                'credentials' => false
977
            ]);
978
        } else {
979
            $ssoClient = $config['ssoClient'];
980
        }
981
        $ssoResponse = $ssoClient->getRoleCredentials([
982
            'accessToken' => $accessToken,
983
            'accountId' => $ssoProfile['sso_account_id'],
984
            'roleName' => $ssoProfile['sso_role_name']
985
        ]);
986
 
987
        $ssoCredentials = $ssoResponse['roleCredentials'];
988
        return $ssoCredentials;
989
    }
990
}
991