PluginProbe
Media Cloud Sync / 1.4.2
Media Cloud Sync v1.4.2
1.4.2 1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 All 36 releases
← All changes | includes/sdk/s3/Aws/Credentials/CredentialProvider.php +128 -42 1.1.1 → 1.4.2 View file →
@@ -48,11 +48,15 @@
48 48 const ENV_KEY = 'AWS_ACCESS_KEY_ID';
49 49 const ENV_PROFILE = 'AWS_PROFILE';
50 50 const ENV_ROLE_SESSION_NAME = 'AWS_ROLE_SESSION_NAME';
51 51 const ENV_SECRET = 'AWS_SECRET_ACCESS_KEY';
52 + const ENV_ACCOUNT_ID = 'AWS_ACCOUNT_ID';
52 53 const ENV_SESSION = 'AWS_SESSION_TOKEN';
53 54 const ENV_TOKEN_FILE = 'AWS_WEB_IDENTITY_TOKEN_FILE';
54 55 const ENV_SHARED_CREDENTIALS_FILE = 'AWS_SHARED_CREDENTIALS_FILE';
56 + public const ENV_REGION = 'AWS_REGION';
57 + public const FALLBACK_REGION = 'us-east-1';
58 + public const REFRESH_WINDOW = 60;
55 59 /**
56 60 * Create a default credential provider that
57 61 * first checks for environment variables,
58 62 * then checks for assumed role via web identity,
@@ -78,11 +82,11 @@
78 82 $cacheable = ['web_identity', 'sso', 'process_credentials', 'process_config', 'ecs', 'instance'];
79 83 $profileName = \getenv(self::ENV_PROFILE) ?: 'default';
80 84 $defaultChain = ['env' => self::env(), 'web_identity' => self::assumeRoleWithWebIdentityCredentialProvider($config)];
81 85 if (!isset($config['use_aws_shared_config_files']) || $config['use_aws_shared_config_files'] !== \false) {
82 - $defaultChain['sso'] = self::sso('profile ' . $profileName, self::getHomeDir() . '/.aws/config', $config);
86 + $defaultChain['sso'] = self::sso($profileName, self::getHomeDir() . '/.aws/config', $config);
83 87 $defaultChain['process_credentials'] = self::process();
84 - $defaultChain['ini'] = self::ini();
88 + $defaultChain['ini'] = self::ini(null, null, $config);
85 89 $defaultChain['process_config'] = self::process('profile ' . $profileName, self::getHomeDir() . '/.aws/config');
86 90 $defaultChain['ini_config'] = self::ini('profile ' . $profileName, self::getHomeDir() . '/.aws/config');
87 91 }
88 92 if (self::shouldUseEcs()) {
@@ -171,10 +175,12 @@
171 175 if (!$creds->getExpiration()) {
172 176 $isConstant = \true;
173 177 return $creds;
174 178 }
175 - // Refresh expired credentials.
176 - if (!$creds->isExpired()) {
179 + // Check if credentials are expired or will expire in 1 minute
180 + $needsRefresh = $creds->getExpiration() - \time() <= self::REFRESH_WINDOW;
181 + // Refresh if expired or expiring soon
182 + if (!$needsRefresh && !$creds->isExpired()) {
177 183 return $creds;
178 184 }
179 185 // Refresh the result and forward the promise.
180 186 return $result = $provider($creds);
@@ -221,10 +227,12 @@
221 227 return function () {
222 228 // Use credentials from environment variables, if available
223 229 $key = \getenv(self::ENV_KEY);
224 230 $secret = \getenv(self::ENV_SECRET);
231 + $accountId = \getenv(self::ENV_ACCOUNT_ID) ?: null;
232 + $token = \getenv(self::ENV_SESSION) ?: null;
225 233 if ($key && $secret) {
226 - return Promise\Create::promiseFor(new Credentials($key, $secret, \getenv(self::ENV_SESSION) ?: NULL));
234 + return Promise\Create::promiseFor(new Credentials($key, $secret, $token, null, $accountId, CredentialSources::ENVIRONMENT));
227 235 }
228 236 return self::reject('Could not find environment variable ' . 'credentials in ' . self::ENV_KEY . '/' . self::ENV_SECRET);
229 237 };
230 238 }
@@ -245,9 +253,9 @@
245 253 * Credential provider that retrieves cached SSO credentials from the CLI
246 254 *
247 255 * @return callable
248 256 */
249 - public static function sso($ssoProfileName, $filename = null, $config = [])
257 + public static function sso($ssoProfileName = 'default', $filename = null, $config = [])
250 258 {
251 259 $filename = $filename ?: self::getHomeDir() . '/.aws/config';
252 260 return function () use($ssoProfileName, $filename, $config) {
253 261 if (!@\is_readable($filename)) {
@@ -253,41 +261,21 @@
253 261 if (!@\is_readable($filename)) {
254 262 return self::reject("Cannot read credentials from {$filename}");
255 263 }
256 264 $profiles = self::loadProfiles($filename);
257 - if (!isset($profiles[$ssoProfileName])) {
265 + if (isset($profiles[$ssoProfileName])) {
266 + $ssoProfile = $profiles[$ssoProfileName];
267 + } elseif (isset($profiles['profile ' . $ssoProfileName])) {
268 + $ssoProfileName = 'profile ' . $ssoProfileName;
269 + $ssoProfile = $profiles[$ssoProfileName];
270 + } else {
258 271 return self::reject("Profile {$ssoProfileName} does not exist in {$filename}.");
259 272 }
260 - $ssoProfile = $profiles[$ssoProfileName];
261 - if (empty($ssoProfile['sso_start_url']) || empty($ssoProfile['sso_region']) || empty($ssoProfile['sso_account_id']) || empty($ssoProfile['sso_role_name'])) {
262 - return self::reject("Profile {$ssoProfileName} in {$filename} must contain the following keys: " . "sso_start_url, sso_region, sso_account_id, and sso_role_name.");
263 - }
264 - $tokenLocation = self::getHomeDir() . '/.aws/sso/cache/' . \utf8_encode(\sha1($ssoProfile['sso_start_url'])) . ".json";
265 - if (!@\is_readable($tokenLocation)) {
266 - return self::reject("Unable to read token file at {$tokenLocation}");
267 - }
268 - $tokenData = \json_decode(\file_get_contents($tokenLocation), \true);
269 - if (empty($tokenData['accessToken']) || empty($tokenData['expiresAt'])) {
270 - return self::reject("Token file at {$tokenLocation} must contain an access token and an expiration");
271 - }
272 - try {
273 - $expiration = (new DateTimeResult($tokenData['expiresAt']))->getTimestamp();
274 - } catch (\Exception $e) {
275 - return self::reject("Cached SSO credentials returned an invalid expiration");
276 - }
277 - $now = \time();
278 - if ($expiration < $now) {
279 - return self::reject("Cached SSO credentials returned expired credentials");
280 - }
281 - $ssoClient = null;
282 - if (empty($config['ssoClient'])) {
283 - $ssoClient = new Aws\SSO\SSOClient(['region' => $ssoProfile['sso_region'], 'version' => '2019-06-10', 'credentials' => \false]);
273 + if (!empty($ssoProfile['sso_session'])) {
274 + return CredentialProvider::getSsoCredentials($profiles, $ssoProfileName, $filename, $config);
284 275 } else {
285 - $ssoClient = $config['ssoClient'];
276 + return CredentialProvider::getSsoCredentialsLegacy($profiles, $ssoProfileName, $filename, $config);
286 277 }
287 - $ssoResponse = $ssoClient->getRoleCredentials(['accessToken' => $tokenData['accessToken'], 'accountId' => $ssoProfile['sso_account_id'], 'roleName' => $ssoProfile['sso_role_name']]);
288 - $ssoCredentials = $ssoResponse['roleCredentials'];
289 - return Promise\Create::promiseFor(new Credentials($ssoCredentials['accessKeyId'], $ssoCredentials['secretAccessKey'], $ssoCredentials['sessionToken'], $expiration));
290 278 };
291 279 }
292 280 /**
293 281 * Credential provider that creates credentials using
@@ -331,9 +319,9 @@
331 319 $stsClient = isset($config['stsClient']) ? $config['stsClient'] : null;
332 320 $region = isset($config['region']) ? $config['region'] : null;
333 321 if ($tokenFromEnv && $arnFromEnv) {
334 322 $sessionName = \getenv(self::ENV_ROLE_SESSION_NAME) ? \getenv(self::ENV_ROLE_SESSION_NAME) : null;
335 - $provider = new AssumeRoleWithWebIdentityCredentialProvider(['RoleArn' => $arnFromEnv, 'WebIdentityTokenFile' => $tokenFromEnv, 'SessionName' => $sessionName, 'client' => $stsClient, 'region' => $region]);
323 + $provider = new AssumeRoleWithWebIdentityCredentialProvider(['RoleArn' => $arnFromEnv, 'WebIdentityTokenFile' => $tokenFromEnv, 'SessionName' => $sessionName, 'client' => $stsClient, 'region' => $region, 'source' => CredentialSources::ENVIRONMENT_STS_WEB_ID_TOKEN]);
336 324 return $provider();
337 325 }
338 326 $profileName = \getenv(self::ENV_PROFILE) ?: 'default';
339 327 if (isset($config['filename'])) {
@@ -347,9 +335,9 @@
347 335 $region = $profile['region'];
348 336 }
349 337 if (isset($profile['web_identity_token_file']) && isset($profile['role_arn'])) {
350 338 $sessionName = isset($profile['role_session_name']) ? $profile['role_session_name'] : null;
351 - $provider = new AssumeRoleWithWebIdentityCredentialProvider(['RoleArn' => $profile['role_arn'], 'WebIdentityTokenFile' => $profile['web_identity_token_file'], 'SessionName' => $sessionName, 'client' => $stsClient, 'region' => $region]);
339 + $provider = new AssumeRoleWithWebIdentityCredentialProvider(['RoleArn' => $profile['role_arn'], 'WebIdentityTokenFile' => $profile['web_identity_token_file'], 'SessionName' => $sessionName, 'client' => $stsClient, 'region' => $region, 'source' => CredentialSources::PROFILE_STS_WEB_ID_TOKEN]);
352 340 return $provider();
353 341 }
354 342 } else {
355 343 return self::reject("Unknown profile: {$profileName}");
@@ -416,9 +404,9 @@
416 404 }
417 405 if (empty($data[$profile]['aws_session_token'])) {
418 406 $data[$profile]['aws_session_token'] = isset($data[$profile]['aws_security_token']) ? $data[$profile]['aws_security_token'] : null;
419 407 }
420 - return Promise\Create::promiseFor(new Credentials($data[$profile]['aws_access_key_id'], $data[$profile]['aws_secret_access_key'], $data[$profile]['aws_session_token']));
408 + return Promise\Create::promiseFor(new Credentials($data[$profile]['aws_access_key_id'], $data[$profile]['aws_secret_access_key'], $data[$profile]['aws_session_token'], null, $data[$profile]['aws_account_id'] ?? null, CredentialSources::PROFILE));
421 409 };
422 410 }
423 411 /**
424 412 * Credentials provider that creates credentials using a process configured in
@@ -477,9 +465,15 @@
477 465 }
478 466 if (empty($processData['SessionToken'])) {
479 467 $processData['SessionToken'] = null;
480 468 }
481 - return Promise\Create::promiseFor(new Credentials($processData['AccessKeyId'], $processData['SecretAccessKey'], $processData['SessionToken'], $expires));
469 + $accountId = null;
470 + if (!empty($processData['AccountId'])) {
471 + $accountId = $processData['AccountId'];
472 + } elseif (!empty($data[$profile]['aws_account_id'])) {
473 + $accountId = $data[$profile]['aws_account_id'];
474 + }
475 + return Promise\Create::promiseFor(new Credentials($processData['AccessKeyId'], $processData['SecretAccessKey'], $processData['SessionToken'], $expires, $accountId, CredentialSources::PROFILE_PROCESS));
482 476 };
483 477 }
484 478 /**
485 479 * Assumes role for profile that includes role_arn
@@ -509,9 +503,8 @@
509 503 return self::reject("A role_arn must be provided with credential_source in " . "file {$filename} under profile {$profileName} ");
510 504 }
511 505 }
512 506 if (empty($stsClient)) {
513 - $sourceRegion = isset($profiles[$sourceProfileName]['region']) ? $profiles[$sourceProfileName]['region'] : 'us-east-1';
514 507 $config['preferStaticCredentials'] = \true;
515 508 $sourceCredentials = null;
516 509 if (!empty($roleProfile['source_profile'])) {
517 510 $sourceCredentials = \call_user_func(CredentialProvider::ini($sourceProfileName, $filename, $config))->wait();
@@ -517,12 +510,13 @@
517 510 $sourceCredentials = \call_user_func(CredentialProvider::ini($sourceProfileName, $filename, $config))->wait();
518 511 } else {
519 512 $sourceCredentials = self::getCredentialsFromSource($profileName, $filename);
520 513 }
521 - $stsClient = new StsClient(['credentials' => $sourceCredentials, 'region' => $sourceRegion, 'version' => '2011-06-15']);
514 + $region = $profiles[$sourceProfileName]['region'] ?? $config['region'] ?? \getEnv(self::ENV_REGION) ?: null;
515 + $stsClient = self::createDefaultStsClient($sourceCredentials, $region);
522 516 }
523 517 $result = $stsClient->assumeRole(['RoleArn' => $roleArn, 'RoleSessionName' => $roleSessionName]);
524 - $credentials = $stsClient->createCredentials($result);
518 + $credentials = $stsClient->createCredentials($result, CredentialSources::STS_ASSUME_ROLE);
525 519 return Promise\Create::promiseFor($credentials);
526 520 }
527 521 /**
528 522 * Gets the environment's HOME directory if available.
@@ -633,6 +627,98 @@
633 627 {
634 628 //Check for relative uri. if not, then full uri.
635 629 //fall back to server for each as getenv is not thread-safe.
636 630 return !empty(\getenv(EcsCredentialProvider::ENV_URI)) || !empty($_SERVER[EcsCredentialProvider::ENV_URI]) || !empty(\getenv(EcsCredentialProvider::ENV_FULL_URI)) || !empty($_SERVER[EcsCredentialProvider::ENV_FULL_URI]);
631 + }
632 + /**
633 + * @param $profiles
634 + * @param $ssoProfileName
635 + * @param $filename
636 + * @param $config
637 + * @return Promise\PromiseInterface
638 + */
639 + private static function getSsoCredentials($profiles, $ssoProfileName, $filename, $config)
640 + {
641 + if (empty($config['ssoOidcClient'])) {
642 + $ssoProfile = $profiles[$ssoProfileName];
643 + $sessionName = $ssoProfile['sso_session'];
644 + if (empty($profiles['sso-session ' . $sessionName])) {
645 + return self::reject("Could not find sso-session {$sessionName} in {$filename}");
646 + }
647 + $ssoSession = $profiles['sso-session ' . $ssoProfile['sso_session']];
648 + $ssoOidcClient = new Aws\SSOOIDC\SSOOIDCClient(['region' => $ssoSession['sso_region'], 'version' => '2019-06-10', 'credentials' => \false]);
649 + } else {
650 + $ssoOidcClient = $config['ssoClient'];
651 + }
652 + $tokenPromise = new Aws\Token\SsoTokenProvider($ssoProfileName, $filename, $ssoOidcClient);
653 + $token = $tokenPromise()->wait();
654 + $ssoCredentials = CredentialProvider::getCredentialsFromSsoService($ssoProfile, $ssoSession['sso_region'], $token->getToken(), $config);
655 + //Expiration value is returned in epoch milliseconds. Conversion to seconds
656 + $expiration = \intdiv($ssoCredentials['expiration'], 1000);
657 + return Promise\Create::promiseFor(new Credentials($ssoCredentials['accessKeyId'], $ssoCredentials['secretAccessKey'], $ssoCredentials['sessionToken'], $expiration, $ssoProfile['sso_account_id'], CredentialSources::PROFILE_SSO));
658 + }
659 + /**
660 + * @param $profiles
661 + * @param $ssoProfileName
662 + * @param $filename
663 + * @param $config
664 + * @return Promise\PromiseInterface
665 + */
666 + private static function getSsoCredentialsLegacy($profiles, $ssoProfileName, $filename, $config)
667 + {
668 + $ssoProfile = $profiles[$ssoProfileName];
669 + if (empty($ssoProfile['sso_start_url']) || empty($ssoProfile['sso_region']) || empty($ssoProfile['sso_account_id']) || empty($ssoProfile['sso_role_name'])) {
670 + return self::reject("Profile {$ssoProfileName} in {$filename} must contain the following keys: " . "sso_start_url, sso_region, sso_account_id, and sso_role_name.");
671 + }
672 + $tokenLocation = self::getHomeDir() . '/.aws/sso/cache/' . \sha1($ssoProfile['sso_start_url']) . ".json";
673 + if (!@\is_readable($tokenLocation)) {
674 + return self::reject("Unable to read token file at {$tokenLocation}");
675 + }
676 + $tokenData = \json_decode(\file_get_contents($tokenLocation), \true);
677 + if (empty($tokenData['accessToken']) || empty($tokenData['expiresAt'])) {
678 + return self::reject("Token file at {$tokenLocation} must contain an access token and an expiration");
679 + }
680 + try {
681 + $expiration = (new DateTimeResult($tokenData['expiresAt']))->getTimestamp();
682 + } catch (\Exception $e) {
683 + return self::reject("Cached SSO credentials returned an invalid expiration");
684 + }
685 + $now = \time();
686 + if ($expiration < $now) {
687 + return self::reject("Cached SSO credentials returned expired credentials");
688 + }
689 + $ssoCredentials = CredentialProvider::getCredentialsFromSsoService($ssoProfile, $ssoProfile['sso_region'], $tokenData['accessToken'], $config);
690 + return Promise\Create::promiseFor(new Credentials($ssoCredentials['accessKeyId'], $ssoCredentials['secretAccessKey'], $ssoCredentials['sessionToken'], $expiration, $ssoProfile['sso_account_id'], CredentialSources::PROFILE_SSO_LEGACY));
691 + }
692 + /**
693 + * @param array $ssoProfile
694 + * @param string $clientRegion
695 + * @param string $accessToken
696 + * @param array $config
697 + * @return array|null
698 + */
699 + private static function getCredentialsFromSsoService($ssoProfile, $clientRegion, $accessToken, $config)
700 + {
701 + if (empty($config['ssoClient'])) {
702 + $ssoClient = new Aws\SSO\SSOClient(['region' => $clientRegion, 'version' => '2019-06-10', 'credentials' => \false]);
703 + } else {
704 + $ssoClient = $config['ssoClient'];
705 + }
706 + $ssoResponse = $ssoClient->getRoleCredentials(['accessToken' => $accessToken, 'accountId' => $ssoProfile['sso_account_id'], 'roleName' => $ssoProfile['sso_role_name']]);
707 + $ssoCredentials = $ssoResponse['roleCredentials'];
708 + return $ssoCredentials;
709 + }
710 + /**
711 + * @param CredentialsInterface $credentials
712 + * @param string|null $region
713 + *
714 + * @return StsClient
715 + */
716 + private static function createDefaultStsClient(CredentialsInterface|callable $credentials, ?string $region) : StsClient
717 + {
718 + if (empty($region)) {
719 + $region = self::FALLBACK_REGION;
720 + \trigger_error('NOTICE: STS client created without explicit `region` configuration.' . \PHP_EOL . "Defaulting to `{$region}`. This fallback behavior may be removed." . \PHP_EOL . 'To avoid potential disruptions, configure a `region` using one of the following methods:' . \PHP_EOL . '(1) Add `region` to your source profile in ~/.aws/credentials,' . \PHP_EOL . '(2) Pass `region` in the `$config` array when calling the provider,' . \PHP_EOL . '(3) Set the `AWS_REGION` environment variable.' . \PHP_EOL . 'See: https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_credentials_assume_role.html#assume-role-with-profile' . \PHP_EOL, \E_USER_NOTICE);
721 + }
722 + return new StsClient(['credentials' => $credentials, 'region' => $region]);
637 723 }
638 724 }