CredentialProvider.php 32 KB

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