Proyectos de Subversion Moodle

Rev

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