AssumeRoleWithWebIdentityCredentialProvider.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. <?php
  2. namespace Aws\Credentials;
  3. use Aws\Exception\AwsException;
  4. use Aws\Exception\CredentialsException;
  5. use Aws\Result;
  6. use Aws\Sts\StsClient;
  7. use GuzzleHttp\Promise;
  8. /**
  9. * Credential provider that provides credentials via assuming a role with a web identity
  10. * More Information, see: https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-sts-2011-06-15.html#assumerolewithwebidentity
  11. */
  12. class AssumeRoleWithWebIdentityCredentialProvider
  13. {
  14. const ERROR_MSG = "Missing required 'AssumeRoleWithWebIdentityCredentialProvider' configuration option: ";
  15. const ENV_RETRIES = 'AWS_METADATA_SERVICE_NUM_ATTEMPTS';
  16. /** @var string */
  17. private $tokenFile;
  18. /** @var string */
  19. private $arn;
  20. /** @var string */
  21. private $session;
  22. /** @var StsClient */
  23. private $client;
  24. /** @var integer */
  25. private $retries;
  26. /** @var integer */
  27. private $attempts;
  28. /**
  29. * The constructor attempts to load config from environment variables.
  30. * If not set, the following config options are used:
  31. * - WebIdentityTokenFile: full path of token filename
  32. * - RoleArn: arn of role to be assumed
  33. * - SessionName: (optional) set by SDK if not provided
  34. *
  35. * @param array $config Configuration options
  36. * @throws \InvalidArgumentException
  37. */
  38. public function __construct(array $config = [])
  39. {
  40. if (!isset($config['RoleArn'])) {
  41. throw new \InvalidArgumentException(self::ERROR_MSG . "'RoleArn'.");
  42. }
  43. $this->arn = $config['RoleArn'];
  44. if (!isset($config['WebIdentityTokenFile'])) {
  45. throw new \InvalidArgumentException(self::ERROR_MSG . "'WebIdentityTokenFile'.");
  46. }
  47. $this->tokenFile = $config['WebIdentityTokenFile'];
  48. if (!preg_match("/^\w\:|^\/|^\\\/", $this->tokenFile)) {
  49. throw new \InvalidArgumentException("'WebIdentityTokenFile' must be an absolute path.");
  50. }
  51. $this->retries = (int) getenv(self::ENV_RETRIES) ?: (isset($config['retries']) ? $config['retries'] : 3);
  52. $this->attempts = 0;
  53. $this->session = isset($config['SessionName'])
  54. ? $config['SessionName']
  55. : 'aws-sdk-php-' . round(microtime(true) * 1000);
  56. $region = isset($config['region'])
  57. ? $config['region']
  58. : 'us-east-1';
  59. if (isset($config['client'])) {
  60. $this->client = $config['client'];
  61. } else {
  62. $this->client = new StsClient([
  63. 'credentials' => false,
  64. 'region' => $region,
  65. 'version' => 'latest'
  66. ]);
  67. }
  68. }
  69. /**
  70. * Loads assume role with web identity credentials.
  71. *
  72. * @return Promise\PromiseInterface
  73. */
  74. public function __invoke()
  75. {
  76. return Promise\coroutine(function () {
  77. $client = $this->client;
  78. $result = null;
  79. while ($result == null) {
  80. try {
  81. $token = is_readable($this->tokenFile)
  82. ? file_get_contents($this->tokenFile)
  83. : false;
  84. if (false === $token) {
  85. clearstatcache(true, dirname($this->tokenFile) . "/" . readlink($this->tokenFile));
  86. clearstatcache(true, dirname($this->tokenFile) . "/" . dirname(readlink($this->tokenFile)));
  87. clearstatcache(true, $this->tokenFile);
  88. if (!is_readable($this->tokenFile)) {
  89. throw new CredentialsException(
  90. "Unreadable tokenfile at location {$this->tokenFile}"
  91. );
  92. }
  93. $token = file_get_contents($this->tokenFile);
  94. }
  95. } catch (\Exception $exception) {
  96. throw new CredentialsException(
  97. "Error reading WebIdentityTokenFile from " . $this->tokenFile,
  98. 0,
  99. $exception
  100. );
  101. }
  102. $assumeParams = [
  103. 'RoleArn' => $this->arn,
  104. 'RoleSessionName' => $this->session,
  105. 'WebIdentityToken' => $token
  106. ];
  107. try {
  108. $result = $client->assumeRoleWithWebIdentity($assumeParams);
  109. } catch (AwsException $e) {
  110. if ($e->getAwsErrorCode() == 'InvalidIdentityToken') {
  111. if ($this->attempts < $this->retries) {
  112. sleep(pow(1.2, $this->attempts));
  113. } else {
  114. throw new CredentialsException(
  115. "InvalidIdentityToken, retries exhausted"
  116. );
  117. }
  118. } else {
  119. throw new CredentialsException(
  120. "Error assuming role from web identity credentials",
  121. 0,
  122. $e
  123. );
  124. }
  125. } catch (\Exception $e) {
  126. throw new CredentialsException(
  127. "Error retrieving web identity credentials: " . $e->getMessage()
  128. . " (" . $e->getCode() . ")"
  129. );
  130. }
  131. $this->attempts++;
  132. }
  133. yield $this->client->createCredentials($result);
  134. });
  135. }
  136. }