PluginProbe
Media Cloud Sync / 1.2.10
Media Cloud Sync v1.2.10
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 1.2.9 All 35 releases
media-cloud-sync / includes / sdk / s3 / Aws / Credentials / InstanceProfileProvider.php

InstanceProfileProvider.php in Media Cloud Sync 1.2.10, at includes/sdk/s3/Aws/Credentials/InstanceProfileProvider.php

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