PageRenderTime 48ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/upload/system/storage/vendor/aws/aws-sdk-php/src/Credentials/InstanceProfileProvider.php

https://github.com/ocStore/ocStore
PHP | 299 lines | 232 code | 29 blank | 38 comment | 37 complexity | 259fb2a2b02e31c941bcd52a85064dcb MD5 | raw file
  1. <?php
  2. namespace Aws\Credentials;
  3. use Aws\Exception\CredentialsException;
  4. use Aws\Exception\InvalidJsonException;
  5. use Aws\Sdk;
  6. use GuzzleHttp\Exception\TransferException;
  7. use GuzzleHttp\Promise;
  8. use GuzzleHttp\Exception\RequestException;
  9. use GuzzleHttp\Psr7\Request;
  10. use GuzzleHttp\Promise\PromiseInterface;
  11. use Psr\Http\Message\ResponseInterface;
  12. /**
  13. * Credential provider that provides credentials from the EC2 metadata service.
  14. */
  15. class InstanceProfileProvider
  16. {
  17. const SERVER_URI = 'http://169.254.169.254/latest/';
  18. const CRED_PATH = 'meta-data/iam/security-credentials/';
  19. const TOKEN_PATH = 'api/token';
  20. const ENV_DISABLE = 'AWS_EC2_METADATA_DISABLED';
  21. const ENV_TIMEOUT = 'AWS_METADATA_SERVICE_TIMEOUT';
  22. const ENV_RETRIES = 'AWS_METADATA_SERVICE_NUM_ATTEMPTS';
  23. /** @var string */
  24. private $profile;
  25. /** @var callable */
  26. private $client;
  27. /** @var int */
  28. private $retries;
  29. /** @var int */
  30. private $attempts;
  31. /** @var float|mixed */
  32. private $timeout;
  33. /** @var bool */
  34. private $secureMode = true;
  35. /**
  36. * The constructor accepts the following options:
  37. *
  38. * - timeout: Connection timeout, in seconds.
  39. * - profile: Optional EC2 profile name, if known.
  40. * - retries: Optional number of retries to be attempted.
  41. *
  42. * @param array $config Configuration options.
  43. */
  44. public function __construct(array $config = [])
  45. {
  46. $this->timeout = (float) getenv(self::ENV_TIMEOUT) ?: (isset($config['timeout']) ? $config['timeout'] : 1.0);
  47. $this->profile = isset($config['profile']) ? $config['profile'] : null;
  48. $this->retries = (int) getenv(self::ENV_RETRIES) ?: (isset($config['retries']) ? $config['retries'] : 3);
  49. $this->attempts = 0;
  50. $this->client = isset($config['client'])
  51. ? $config['client'] // internal use only
  52. : \Aws\default_http_handler();
  53. }
  54. /**
  55. * Loads instance profile credentials.
  56. *
  57. * @return PromiseInterface
  58. */
  59. public function __invoke($previousCredentials = null)
  60. {
  61. return Promise\Coroutine::of(function () use ($previousCredentials) {
  62. // Retrieve token or switch out of secure mode
  63. $token = null;
  64. while ($this->secureMode && is_null($token)) {
  65. try {
  66. $token = (yield $this->request(
  67. self::TOKEN_PATH,
  68. 'PUT',
  69. [
  70. 'x-aws-ec2-metadata-token-ttl-seconds' => 21600
  71. ]
  72. ));
  73. } catch (TransferException $e) {
  74. if ($this->getExceptionStatusCode($e) === 500
  75. && $previousCredentials instanceof Credentials
  76. ) {
  77. goto generateCredentials;
  78. }
  79. else if (!method_exists($e, 'getResponse')
  80. || empty($e->getResponse())
  81. || !in_array(
  82. $e->getResponse()->getStatusCode(),
  83. [400, 500, 502, 503, 504]
  84. )
  85. ) {
  86. $this->secureMode = false;
  87. } else {
  88. $this->handleRetryableException(
  89. $e,
  90. [],
  91. $this->createErrorMessage(
  92. 'Error retrieving metadata token'
  93. )
  94. );
  95. }
  96. }
  97. $this->attempts++;
  98. }
  99. // Set token header only for secure mode
  100. $headers = [];
  101. if ($this->secureMode) {
  102. $headers = [
  103. 'x-aws-ec2-metadata-token' => $token
  104. ];
  105. }
  106. // Retrieve profile
  107. while (!$this->profile) {
  108. try {
  109. $this->profile = (yield $this->request(
  110. self::CRED_PATH,
  111. 'GET',
  112. $headers
  113. ));
  114. } catch (TransferException $e) {
  115. // 401 indicates insecure flow not supported, switch to
  116. // attempting secure mode for subsequent calls
  117. if (!empty($this->getExceptionStatusCode($e))
  118. && $this->getExceptionStatusCode($e) === 401
  119. ) {
  120. $this->secureMode = true;
  121. }
  122. $this->handleRetryableException(
  123. $e,
  124. [ 'blacklist' => [401, 403] ],
  125. $this->createErrorMessage($e->getMessage())
  126. );
  127. }
  128. $this->attempts++;
  129. }
  130. // Retrieve credentials
  131. $result = null;
  132. while ($result == null) {
  133. try {
  134. $json = (yield $this->request(
  135. self::CRED_PATH . $this->profile,
  136. 'GET',
  137. $headers
  138. ));
  139. $result = $this->decodeResult($json);
  140. } catch (InvalidJsonException $e) {
  141. $this->handleRetryableException(
  142. $e,
  143. [ 'blacklist' => [401, 403] ],
  144. $this->createErrorMessage(
  145. 'Invalid JSON response, retries exhausted'
  146. )
  147. );
  148. } catch (TransferException $e) {
  149. // 401 indicates insecure flow not supported, switch to
  150. // attempting secure mode for subsequent calls
  151. if (($this->getExceptionStatusCode($e) === 500
  152. || strpos($e->getMessage(), "cURL error 28") !== false)
  153. && $previousCredentials instanceof Credentials
  154. ) {
  155. goto generateCredentials;
  156. } else if (!empty($this->getExceptionStatusCode($e))
  157. && $this->getExceptionStatusCode($e) === 401
  158. ) {
  159. $this->secureMode = true;
  160. }
  161. $this->handleRetryableException(
  162. $e,
  163. [ 'blacklist' => [401, 403] ],
  164. $this->createErrorMessage($e->getMessage())
  165. );
  166. }
  167. $this->attempts++;
  168. }
  169. generateCredentials:
  170. if (!isset($result)) {
  171. $credentials = $previousCredentials;
  172. } else {
  173. $credentials = new Credentials(
  174. $result['AccessKeyId'],
  175. $result['SecretAccessKey'],
  176. $result['Token'],
  177. strtotime($result['Expiration'])
  178. );
  179. }
  180. if ($credentials->isExpired()) {
  181. $credentials->extendExpiration();
  182. }
  183. yield $credentials;
  184. });
  185. }
  186. /**
  187. * @param string $url
  188. * @param string $method
  189. * @param array $headers
  190. * @return PromiseInterface Returns a promise that is fulfilled with the
  191. * body of the response as a string.
  192. */
  193. private function request($url, $method = 'GET', $headers = [])
  194. {
  195. $disabled = getenv(self::ENV_DISABLE) ?: false;
  196. if (strcasecmp($disabled, 'true') === 0) {
  197. throw new CredentialsException(
  198. $this->createErrorMessage('EC2 metadata service access disabled')
  199. );
  200. }
  201. $fn = $this->client;
  202. $request = new Request($method, self::SERVER_URI . $url);
  203. $userAgent = 'aws-sdk-php/' . Sdk::VERSION;
  204. if (defined('HHVM_VERSION')) {
  205. $userAgent .= ' HHVM/' . HHVM_VERSION;
  206. }
  207. $userAgent .= ' ' . \Aws\default_user_agent();
  208. $request = $request->withHeader('User-Agent', $userAgent);
  209. foreach ($headers as $key => $value) {
  210. $request = $request->withHeader($key, $value);
  211. }
  212. return $fn($request, ['timeout' => $this->timeout])
  213. ->then(function (ResponseInterface $response) {
  214. return (string) $response->getBody();
  215. })->otherwise(function (array $reason) {
  216. $reason = $reason['exception'];
  217. if ($reason instanceof TransferException) {
  218. throw $reason;
  219. }
  220. $msg = $reason->getMessage();
  221. throw new CredentialsException(
  222. $this->createErrorMessage($msg)
  223. );
  224. });
  225. }
  226. private function handleRetryableException(
  227. \Exception $e,
  228. $retryOptions,
  229. $message
  230. ) {
  231. $isRetryable = true;
  232. if (!empty($status = $this->getExceptionStatusCode($e))
  233. && isset($retryOptions['blacklist'])
  234. && in_array($status, $retryOptions['blacklist'])
  235. ) {
  236. $isRetryable = false;
  237. }
  238. if ($isRetryable && $this->attempts < $this->retries) {
  239. sleep(pow(1.2, $this->attempts));
  240. } else {
  241. throw new CredentialsException($message);
  242. }
  243. }
  244. private function getExceptionStatusCode(\Exception $e)
  245. {
  246. if (method_exists($e, 'getResponse')
  247. && !empty($e->getResponse())
  248. ) {
  249. return $e->getResponse()->getStatusCode();
  250. }
  251. return null;
  252. }
  253. private function createErrorMessage($previous)
  254. {
  255. return "Error retrieving credentials from the instance profile "
  256. . "metadata service. ({$previous})";
  257. }
  258. private function decodeResult($response)
  259. {
  260. $result = json_decode($response, true);
  261. if (json_last_error() > 0) {
  262. throw new InvalidJsonException();
  263. }
  264. if ($result['Code'] !== 'Success') {
  265. throw new CredentialsException('Unexpected instance profile '
  266. . 'response code: ' . $result['Code']);
  267. }
  268. return $result;
  269. }
  270. }