diff options
author | Andrew Dolgov <[email protected]> | 2022-11-23 21:14:33 +0300 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2022-11-23 21:14:33 +0300 |
commit | 0c8af4992cb0f7589dcafaad65ada12753c64594 (patch) | |
tree | 18e83d068c3e7dd2499331de977782b382279396 /vendor/aws/aws-sdk-php/src/Credentials/InstanceProfileProvider.php |
initial
Diffstat (limited to 'vendor/aws/aws-sdk-php/src/Credentials/InstanceProfileProvider.php')
-rw-r--r-- | vendor/aws/aws-sdk-php/src/Credentials/InstanceProfileProvider.php | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/vendor/aws/aws-sdk-php/src/Credentials/InstanceProfileProvider.php b/vendor/aws/aws-sdk-php/src/Credentials/InstanceProfileProvider.php new file mode 100644 index 0000000..0f1fcb1 --- /dev/null +++ b/vendor/aws/aws-sdk-php/src/Credentials/InstanceProfileProvider.php @@ -0,0 +1,299 @@ +<?php +namespace Aws\Credentials; + +use Aws\Exception\CredentialsException; +use Aws\Exception\InvalidJsonException; +use Aws\Sdk; +use GuzzleHttp\Exception\TransferException; +use GuzzleHttp\Promise; +use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Promise\PromiseInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Credential provider that provides credentials from the EC2 metadata service. + */ +class InstanceProfileProvider +{ + const SERVER_URI = 'http://169.254.169.254/latest/'; + const CRED_PATH = 'meta-data/iam/security-credentials/'; + const TOKEN_PATH = 'api/token'; + + const ENV_DISABLE = 'AWS_EC2_METADATA_DISABLED'; + const ENV_TIMEOUT = 'AWS_METADATA_SERVICE_TIMEOUT'; + const ENV_RETRIES = 'AWS_METADATA_SERVICE_NUM_ATTEMPTS'; + + /** @var string */ + private $profile; + + /** @var callable */ + private $client; + + /** @var int */ + private $retries; + + /** @var int */ + private $attempts; + + /** @var float|mixed */ + private $timeout; + + /** @var bool */ + private $secureMode = true; + + /** + * The constructor accepts the following options: + * + * - timeout: Connection timeout, in seconds. + * - profile: Optional EC2 profile name, if known. + * - retries: Optional number of retries to be attempted. + * + * @param array $config Configuration options. + */ + public function __construct(array $config = []) + { + $this->timeout = (float) getenv(self::ENV_TIMEOUT) ?: (isset($config['timeout']) ? $config['timeout'] : 1.0); + $this->profile = isset($config['profile']) ? $config['profile'] : null; + $this->retries = (int) getenv(self::ENV_RETRIES) ?: (isset($config['retries']) ? $config['retries'] : 3); + $this->client = isset($config['client']) + ? $config['client'] // internal use only + : \Aws\default_http_handler(); + } + + /** + * Loads instance profile credentials. + * + * @return PromiseInterface + */ + public function __invoke($previousCredentials = null) + { + $this->attempts = 0; + return Promise\Coroutine::of(function () use ($previousCredentials) { + + // Retrieve token or switch out of secure mode + $token = null; + while ($this->secureMode && is_null($token)) { + try { + $token = (yield $this->request( + self::TOKEN_PATH, + 'PUT', + [ + 'x-aws-ec2-metadata-token-ttl-seconds' => 21600 + ] + )); + } catch (TransferException $e) { + if ($this->getExceptionStatusCode($e) === 500 + && $previousCredentials instanceof Credentials + ) { + goto generateCredentials; + } + else if (!method_exists($e, 'getResponse') + || empty($e->getResponse()) + || !in_array( + $e->getResponse()->getStatusCode(), + [400, 500, 502, 503, 504] + ) + ) { + $this->secureMode = false; + } else { + $this->handleRetryableException( + $e, + [], + $this->createErrorMessage( + 'Error retrieving metadata token' + ) + ); + } + } + $this->attempts++; + } + + // Set token header only for secure mode + $headers = []; + if ($this->secureMode) { + $headers = [ + 'x-aws-ec2-metadata-token' => $token + ]; + } + + // Retrieve profile + while (!$this->profile) { + try { + $this->profile = (yield $this->request( + self::CRED_PATH, + 'GET', + $headers + )); + } catch (TransferException $e) { + // 401 indicates insecure flow not supported, switch to + // attempting secure mode for subsequent calls + if (!empty($this->getExceptionStatusCode($e)) + && $this->getExceptionStatusCode($e) === 401 + ) { + $this->secureMode = true; + } + $this->handleRetryableException( + $e, + [ 'blacklist' => [401, 403] ], + $this->createErrorMessage($e->getMessage()) + ); + } + + $this->attempts++; + } + + // Retrieve credentials + $result = null; + while ($result == null) { + try { + $json = (yield $this->request( + self::CRED_PATH . $this->profile, + 'GET', + $headers + )); + $result = $this->decodeResult($json); + } catch (InvalidJsonException $e) { + $this->handleRetryableException( + $e, + [ 'blacklist' => [401, 403] ], + $this->createErrorMessage( + 'Invalid JSON response, retries exhausted' + ) + ); + } catch (TransferException $e) { + // 401 indicates insecure flow not supported, switch to + // attempting secure mode for subsequent calls + if (($this->getExceptionStatusCode($e) === 500 + || strpos($e->getMessage(), "cURL error 28") !== false) + && $previousCredentials instanceof Credentials + ) { + goto generateCredentials; + } else if (!empty($this->getExceptionStatusCode($e)) + && $this->getExceptionStatusCode($e) === 401 + ) { + $this->secureMode = true; + } + $this->handleRetryableException( + $e, + [ 'blacklist' => [401, 403] ], + $this->createErrorMessage($e->getMessage()) + ); + } + $this->attempts++; + } + generateCredentials: + + if (!isset($result)) { + $credentials = $previousCredentials; + } else { + $credentials = new Credentials( + $result['AccessKeyId'], + $result['SecretAccessKey'], + $result['Token'], + strtotime($result['Expiration']) + ); + } + + if ($credentials->isExpired()) { + $credentials->extendExpiration(); + } + + yield $credentials; + }); + } + + /** + * @param string $url + * @param string $method + * @param array $headers + * @return PromiseInterface Returns a promise that is fulfilled with the + * body of the response as a string. + */ + private function request($url, $method = 'GET', $headers = []) + { + $disabled = getenv(self::ENV_DISABLE) ?: false; + if (strcasecmp($disabled, 'true') === 0) { + throw new CredentialsException( + $this->createErrorMessage('EC2 metadata service access disabled') + ); + } + + $fn = $this->client; + $request = new Request($method, self::SERVER_URI . $url); + $userAgent = 'aws-sdk-php/' . Sdk::VERSION; + if (defined('HHVM_VERSION')) { + $userAgent .= ' HHVM/' . HHVM_VERSION; + } + $userAgent .= ' ' . \Aws\default_user_agent(); + $request = $request->withHeader('User-Agent', $userAgent); + foreach ($headers as $key => $value) { + $request = $request->withHeader($key, $value); + } + + return $fn($request, ['timeout' => $this->timeout]) + ->then(function (ResponseInterface $response) { + return (string) $response->getBody(); + })->otherwise(function (array $reason) { + $reason = $reason['exception']; + if ($reason instanceof TransferException) { + throw $reason; + } + $msg = $reason->getMessage(); + throw new CredentialsException( + $this->createErrorMessage($msg) + ); + }); + } + + private function handleRetryableException( + \Exception $e, + $retryOptions, + $message + ) { + $isRetryable = true; + if (!empty($status = $this->getExceptionStatusCode($e)) + && isset($retryOptions['blacklist']) + && in_array($status, $retryOptions['blacklist']) + ) { + $isRetryable = false; + } + if ($isRetryable && $this->attempts < $this->retries) { + sleep((int) pow(1.2, $this->attempts)); + } else { + throw new CredentialsException($message); + } + } + + private function getExceptionStatusCode(\Exception $e) + { + if (method_exists($e, 'getResponse') + && !empty($e->getResponse()) + ) { + return $e->getResponse()->getStatusCode(); + } + return null; + } + + private function createErrorMessage($previous) + { + return "Error retrieving credentials from the instance profile " + . "metadata service. ({$previous})"; + } + + private function decodeResult($response) + { + $result = json_decode($response, true); + + if (json_last_error() > 0) { + throw new InvalidJsonException(); + } + + if ($result['Code'] !== 'Success') { + throw new CredentialsException('Unexpected instance profile ' + . 'response code: ' . $result['Code']); + } + + return $result; + } +} |