PluginProbe
Media Cloud Sync / 1.4.2
Media Cloud Sync v1.4.2
1.4.2 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 All 36 releases
← All changes | includes/sdk/s3/Aws/Credentials/InstanceProfileProvider.php +140 -18 1.2.11 → 1.4.2 View file →
@@ -1,14 +1,14 @@
1 1 <?php
2 2
3 3 namespace Dudlewebs\WPMCS\s3\Aws\Credentials;
4 4
5 +use Dudlewebs\WPMCS\s3\Aws\Configuration\ConfigurationResolver;
5 6 use Dudlewebs\WPMCS\s3\Aws\Exception\CredentialsException;
6 7 use Dudlewebs\WPMCS\s3\Aws\Exception\InvalidJsonException;
7 8 use Dudlewebs\WPMCS\s3\Aws\Sdk;
8 9 use Dudlewebs\WPMCS\s3\GuzzleHttp\Exception\TransferException;
9 10 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise;
10 -use Dudlewebs\WPMCS\s3\GuzzleHttp\Exception\RequestException;
11 11 use Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7\Request;
12 12 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\PromiseInterface;
13 13 use Dudlewebs\WPMCS\s3\Psr\Http\Message\ResponseInterface;
14 14 /**
@@ -15,14 +15,24 @@
15 15 * Credential provider that provides credentials from the EC2 metadata service.
16 16 */
17 17 class InstanceProfileProvider
18 18 {
19 - const SERVER_URI = 'http://169.254.169.254/latest/';
20 19 const CRED_PATH = 'meta-data/iam/security-credentials/';
21 20 const TOKEN_PATH = 'api/token';
22 21 const ENV_DISABLE = 'AWS_EC2_METADATA_DISABLED';
23 22 const ENV_TIMEOUT = 'AWS_METADATA_SERVICE_TIMEOUT';
24 23 const ENV_RETRIES = 'AWS_METADATA_SERVICE_NUM_ATTEMPTS';
24 + const CFG_EC2_METADATA_V1_DISABLED = 'ec2_metadata_v1_disabled';
25 + const CFG_EC2_METADATA_SERVICE_ENDPOINT = 'ec2_metadata_service_endpoint';
26 + const CFG_EC2_METADATA_SERVICE_ENDPOINT_MODE = 'ec2_metadata_service_endpoint_mode';
27 + const DEFAULT_TIMEOUT = 1.0;
28 + const DEFAULT_RETRIES = 3;
29 + const DEFAULT_TOKEN_TTL_SECONDS = 21600;
30 + const DEFAULT_AWS_EC2_METADATA_V1_DISABLED = \false;
31 + const ENDPOINT_MODE_IPv4 = 'IPv4';
32 + const ENDPOINT_MODE_IPv6 = 'IPv6';
33 + const DEFAULT_METADATA_SERVICE_IPv4_ENDPOINT = 'http://169.254.169.254';
34 + const DEFAULT_METADATA_SERVICE_IPv6_ENDPOINT = 'http://[fd00:ec2::254]';
25 35 /** @var string */
26 36 private $profile;
27 37 /** @var callable */
28 38 private $client;
@@ -33,8 +43,16 @@
33 43 /** @var float|mixed */
34 44 private $timeout;
35 45 /** @var bool */
36 46 private $secureMode = \true;
47 + /** @var bool|null */
48 + private $ec2MetadataV1Disabled;
49 + /** @var string */
50 + private $endpoint;
51 + /** @var string */
52 + private $endpointMode;
53 + /** @var array */
54 + private $config;
37 55 /**
38 56 * The constructor accepts the following options:
39 57 *
40 58 * - timeout: Connection timeout, in seconds.
@@ -39,17 +57,32 @@
39 57 *
40 58 * - timeout: Connection timeout, in seconds.
41 59 * - profile: Optional EC2 profile name, if known.
42 60 * - retries: Optional number of retries to be attempted.
61 + * - ec2_metadata_v1_disabled: Optional for disabling the fallback to IMDSv1.
62 + * - endpoint: Optional for overriding the default endpoint to be used for fetching credentials.
63 + * The value must contain a valid URI scheme. If the URI scheme is not https, it must
64 + * resolve to a loopback address.
65 + * - endpoint_mode: Optional for overriding the default endpoint mode (IPv4|IPv6) to be used for
66 + * resolving the default endpoint.
67 + * - use_aws_shared_config_files: Decides whether the shared config file should be considered when
68 + * using the ConfigurationResolver::resolve method.
43 69 *
44 70 * @param array $config Configuration options.
45 71 */
46 72 public function __construct(array $config = [])
47 73 {
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();
74 + $this->timeout = (float) \getenv(self::ENV_TIMEOUT) ?: $config['timeout'] ?? self::DEFAULT_TIMEOUT;
75 + $this->profile = $config['profile'] ?? null;
76 + $this->retries = (int) \getenv(self::ENV_RETRIES) ?: $config['retries'] ?? self::DEFAULT_RETRIES;
77 + $this->client = $config['client'] ?? \Dudlewebs\WPMCS\s3\Aws\default_http_handler();
78 + $this->ec2MetadataV1Disabled = $config[self::CFG_EC2_METADATA_V1_DISABLED] ?? null;
79 + $this->endpoint = $config[self::CFG_EC2_METADATA_SERVICE_ENDPOINT] ?? null;
80 + if (!empty($this->endpoint) && !$this->isValidEndpoint($this->endpoint)) {
81 + throw new \InvalidArgumentException('The provided URI "' . $this->endpoint . '" is invalid, or contains an unsupported host');
82 + }
83 + $this->endpointMode = $config[self::CFG_EC2_METADATA_SERVICE_ENDPOINT_MODE] ?? null;
84 + $this->config = $config;
52 85 }
53 86 /**
54 87 * Loads instance profile credentials.
55 88 *
@@ -62,18 +95,16 @@
62 95 // Retrieve token or switch out of secure mode
63 96 $token = null;
64 97 while ($this->secureMode && \is_null($token)) {
65 98 try {
66 - $token = (yield $this->request(self::TOKEN_PATH, 'PUT', ['x-aws-ec2-metadata-token-ttl-seconds' => 21600]));
99 + $token = (yield $this->request(self::TOKEN_PATH, 'PUT', ['x-aws-ec2-metadata-token-ttl-seconds' => self::DEFAULT_TOKEN_TTL_SECONDS]));
67 100 } catch (TransferException $e) {
68 101 if ($this->getExceptionStatusCode($e) === 500 && $previousCredentials instanceof Credentials) {
69 102 goto generateCredentials;
103 + } elseif ($this->shouldFallbackToIMDSv1() && (!\method_exists($e, 'getResponse') || empty($e->getResponse()) || !\in_array($e->getResponse()->getStatusCode(), [400, 500, 502, 503, 504]))) {
104 + $this->secureMode = \false;
70 105 } 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 - }
106 + $this->handleRetryableException($e, [], $this->createErrorMessage('Error retrieving metadata token'));
76 107 }
77 108 }
78 109 $this->attempts++;
79 110 }
@@ -108,12 +139,10 @@
108 139 // 401 indicates insecure flow not supported, switch to
109 140 // attempting secure mode for subsequent calls
110 141 if (($this->getExceptionStatusCode($e) === 500 || \strpos($e->getMessage(), "cURL error 28") !== \false) && $previousCredentials instanceof Credentials) {
111 142 goto generateCredentials;
112 - } else {
113 - if (!empty($this->getExceptionStatusCode($e)) && $this->getExceptionStatusCode($e) === 401) {
114 - $this->secureMode = \true;
115 - }
143 + } elseif (!empty($this->getExceptionStatusCode($e)) && $this->getExceptionStatusCode($e) === 401) {
144 + $this->secureMode = \true;
116 145 }
117 146 $this->handleRetryableException($e, ['blacklist' => [401, 403]], $this->createErrorMessage($e->getMessage()));
118 147 }
119 148 $this->attempts++;
@@ -121,9 +150,9 @@
121 150 generateCredentials:
122 151 if (!isset($result)) {
123 152 $credentials = $previousCredentials;
124 153 } else {
125 - $credentials = new Credentials($result['AccessKeyId'], $result['SecretAccessKey'], $result['Token'], \strtotime($result['Expiration']));
154 + $credentials = new Credentials($result['AccessKeyId'], $result['SecretAccessKey'], $result['Token'], \strtotime($result['Expiration']), $result['AccountId'] ?? null, CredentialSources::IMDS);
126 155 }
127 156 if ($credentials->isExpired()) {
128 157 $credentials->extendExpiration();
129 158 }
@@ -143,9 +172,9 @@
143 172 if (\strcasecmp($disabled, 'true') === 0) {
144 173 throw new CredentialsException($this->createErrorMessage('EC2 metadata service access disabled'));
145 174 }
146 175 $fn = $this->client;
147 - $request = new Request($method, self::SERVER_URI . $url);
176 + $request = new Request($method, $this->resolveEndpoint() . $url);
148 177 $userAgent = 'aws-sdk-php/' . Sdk::VERSION;
149 178 if (\defined('Dudlewebs\\WPMCS\\s3\\HHVM_VERSION')) {
150 179 $userAgent .= ' HHVM/' . HHVM_VERSION;
151 180 }
@@ -197,6 +226,99 @@
197 226 if ($result['Code'] !== 'Success') {
198 227 throw new CredentialsException('Unexpected instance profile ' . 'response code: ' . $result['Code']);
199 228 }
200 229 return $result;
230 + }
231 + /**
232 + * This functions checks for whether we should fall back to IMDSv1 or not.
233 + * If $ec2MetadataV1Disabled is null then we will try to resolve this value from
234 + * the following sources:
235 + * - From environment: "AWS_EC2_METADATA_V1_DISABLED".
236 + * - From config file: aws_ec2_metadata_v1_disabled
237 + * - Defaulted to false
238 + *
239 + * @return bool
240 + */
241 + private function shouldFallbackToIMDSv1() : bool
242 + {
243 + $isImdsV1Disabled = \Dudlewebs\WPMCS\s3\Aws\boolean_value($this->ec2MetadataV1Disabled) ?? \Dudlewebs\WPMCS\s3\Aws\boolean_value(ConfigurationResolver::resolve(self::CFG_EC2_METADATA_V1_DISABLED, self::DEFAULT_AWS_EC2_METADATA_V1_DISABLED, 'bool', $this->config)) ?? self::DEFAULT_AWS_EC2_METADATA_V1_DISABLED;
244 + return !$isImdsV1Disabled;
245 + }
246 + /**
247 + * Resolves the metadata service endpoint. If the endpoint is not provided
248 + * or configured then, the default endpoint, based on the endpoint mode resolved,
249 + * will be used.
250 + * Example: if endpoint_mode is resolved to be IPv4 and the endpoint is not provided
251 + * then, the endpoint to be used will be http://169.254.169.254.
252 + *
253 + * @return string
254 + */
255 + private function resolveEndpoint() : string
256 + {
257 + $endpoint = $this->endpoint;
258 + if (\is_null($endpoint)) {
259 + $endpoint = ConfigurationResolver::resolve(self::CFG_EC2_METADATA_SERVICE_ENDPOINT, $this->getDefaultEndpoint(), 'string', $this->config);
260 + }
261 + if (!$this->isValidEndpoint($endpoint)) {
262 + throw new CredentialsException('The provided URI "' . $endpoint . '" is invalid, or contains an unsupported host');
263 + }
264 + if (\substr($endpoint, \strlen($endpoint) - 1) !== '/') {
265 + $endpoint = $endpoint . '/';
266 + }
267 + return $endpoint . 'latest/';
268 + }
269 + /**
270 + * Resolves the default metadata service endpoint.
271 + * If endpoint_mode is resolved as IPv4 then:
272 + * - endpoint = http://169.254.169.254
273 + * If endpoint_mode is resolved as IPv6 then:
274 + * - endpoint = http://[fd00:ec2::254]
275 + *
276 + * @return string
277 + */
278 + private function getDefaultEndpoint() : string
279 + {
280 + $endpointMode = $this->resolveEndpointMode();
281 + switch ($endpointMode) {
282 + case self::ENDPOINT_MODE_IPv4:
283 + return self::DEFAULT_METADATA_SERVICE_IPv4_ENDPOINT;
284 + case self::ENDPOINT_MODE_IPv6:
285 + return self::DEFAULT_METADATA_SERVICE_IPv6_ENDPOINT;
286 + }
287 + throw new CredentialsException("Invalid endpoint mode '{$endpointMode}' resolved");
288 + }
289 + /**
290 + * Resolves the endpoint mode to be considered when resolving the default
291 + * metadata service endpoint.
292 + *
293 + * @return string
294 + */
295 + private function resolveEndpointMode() : string
296 + {
297 + $endpointMode = $this->endpointMode;
298 + if (\is_null($endpointMode)) {
299 + $endpointMode = ConfigurationResolver::resolve(self::CFG_EC2_METADATA_SERVICE_ENDPOINT_MODE, self::ENDPOINT_MODE_IPv4, 'string', $this->config);
300 + }
301 + return $endpointMode;
302 + }
303 + /**
304 + * This method checks for whether a provide URI is valid.
305 + * @param string $uri this parameter is the uri to do the validation against to.
306 + *
307 + * @return string|null
308 + */
309 + private function isValidEndpoint($uri) : bool
310 + {
311 + // We make sure first the provided uri is a valid URL
312 + $isValidURL = \filter_var($uri, \FILTER_VALIDATE_URL) !== \false;
313 + if (!$isValidURL) {
314 + return \false;
315 + }
316 + // We make sure that if is a no secure host then it must be a loop back address.
317 + $parsedUri = \parse_url($uri);
318 + if ($parsedUri['scheme'] !== 'https') {
319 + $host = \trim($parsedUri['host'], '[]');
320 + return CredentialsUtils::isLoopBackAddress(\gethostbyname($host)) || \in_array($uri, [self::DEFAULT_METADATA_SERVICE_IPv4_ENDPOINT, self::DEFAULT_METADATA_SERVICE_IPv6_ENDPOINT]);
321 + }
322 + return \true;
201 323 }
202 324 }