PluginProbe
Media Cloud Sync / 1.4.1
Media Cloud Sync v1.4.1
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 / EcsCredentialProvider.php

EcsCredentialProvider.php in Media Cloud Sync 1.4.1, at includes/sdk/s3/Aws/Credentials/EcsCredentialProvider.php

207 lines 8.0 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\Arn\Arn;
6 use Dudlewebs\WPMCS\s3\Aws\Exception\CredentialsException;
7 use Dudlewebs\WPMCS\s3\GuzzleHttp\Exception\ConnectException;
8 use Dudlewebs\WPMCS\s3\GuzzleHttp\Exception\GuzzleException;
9 use Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7\Request;
10 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise;
11 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\PromiseInterface;
12 use Dudlewebs\WPMCS\s3\Psr\Http\Message\ResponseInterface;
13 /**
14 * Credential provider that fetches container credentials with GET request.
15 * container environment variables are used in constructing request URI.
16 */
17 class EcsCredentialProvider
18 {
19 const SERVER_URI = 'http://169.254.170.2';
20 const ENV_URI = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI";
21 const ENV_FULL_URI = "AWS_CONTAINER_CREDENTIALS_FULL_URI";
22 const ENV_AUTH_TOKEN = "AWS_CONTAINER_AUTHORIZATION_TOKEN";
23 const ENV_AUTH_TOKEN_FILE = "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE";
24 const ENV_TIMEOUT = 'AWS_METADATA_SERVICE_TIMEOUT';
25 const EKS_SERVER_HOST_IPV4 = '169.254.170.23';
26 const EKS_SERVER_HOST_IPV6 = 'fd00:ec2::23';
27 const ENV_RETRIES = 'AWS_METADATA_SERVICE_NUM_ATTEMPTS';
28 const DEFAULT_ENV_TIMEOUT = 1.0;
29 const DEFAULT_ENV_RETRIES = 3;
30 /** @var callable */
31 private $client;
32 /** @var float|mixed */
33 private $timeout;
34 /** @var int */
35 private $retries;
36 /** @var int */
37 private $attempts;
38 /**
39 * The constructor accepts following options:
40 * - timeout: (optional) Connection timeout, in seconds, default 1.0
41 * - retries: Optional number of retries to be attempted, default 3.
42 * - client: An EcsClient to make request from
43 *
44 * @param array $config Configuration options
45 */
46 public function __construct(array $config = [])
47 {
48 $this->timeout = (float) isset($config['timeout']) ? $config['timeout'] : (\getenv(self::ENV_TIMEOUT) ?: self::DEFAULT_ENV_TIMEOUT);
49 $this->retries = (int) isset($config['retries']) ? $config['retries'] : ((int) \getenv(self::ENV_RETRIES) ?: self::DEFAULT_ENV_RETRIES);
50 $this->client = $config['client'] ?? \Dudlewebs\WPMCS\s3\Aws\default_http_handler();
51 }
52 /**
53 * Load container credentials.
54 *
55 * @return PromiseInterface
56 * @throws GuzzleException
57 */
58 public function __invoke()
59 {
60 $this->attempts = 0;
61 $uri = $this->getEcsUri();
62 if ($this->isCompatibleUri($uri)) {
63 return Promise\Coroutine::of(function () {
64 $client = $this->client;
65 $request = new Request('GET', $this->getEcsUri());
66 $headers = $this->getHeadersForAuthToken();
67 $credentials = null;
68 while ($credentials === null) {
69 $credentials = (yield $client($request, ['timeout' => $this->timeout, 'proxy' => '', 'headers' => $headers])->then(function (ResponseInterface $response) {
70 $result = $this->decodeResult((string) $response->getBody());
71 if (!isset($result['AccountId']) && isset($result['RoleArn'])) {
72 try {
73 $parsedArn = new Arn($result['RoleArn']);
74 $result['AccountId'] = $parsedArn->getAccountId();
75 } catch (\Exception $e) {
76 // AccountId will be null
77 }
78 }
79 return new Credentials($result['AccessKeyId'], $result['SecretAccessKey'], $result['Token'], \strtotime($result['Expiration']), $result['AccountId'] ?? null, CredentialSources::ECS);
80 })->otherwise(function ($reason) {
81 $reason = \is_array($reason) ? $reason['exception'] : $reason;
82 $isRetryable = $reason instanceof ConnectException;
83 if ($isRetryable && $this->attempts < $this->retries) {
84 \sleep((int) \pow(1.2, $this->attempts));
85 } else {
86 $msg = $reason->getMessage();
87 throw new CredentialsException(\sprintf('Error retrieving credentials from container metadata after attempt %d/%d (%s)', $this->attempts, $this->retries, $msg));
88 }
89 }));
90 $this->attempts++;
91 }
92 (yield $credentials);
93 });
94 }
95 throw new CredentialsException("Uri '{$uri}' contains an unsupported host.");
96 }
97 /**
98 * Returns the number of attempts that have been done.
99 *
100 * @return int
101 */
102 public function getAttempts() : int
103 {
104 return $this->attempts;
105 }
106 /**
107 * Retrieves authorization token.
108 *
109 * @return array|false|string
110 */
111 private function getEcsAuthToken()
112 {
113 if (!empty($path = \getenv(self::ENV_AUTH_TOKEN_FILE))) {
114 $token = @\file_get_contents($path);
115 if (\false === $token) {
116 \clearstatcache(\true, \dirname($path) . \DIRECTORY_SEPARATOR . @\readlink($path));
117 \clearstatcache(\true, \dirname($path) . \DIRECTORY_SEPARATOR . \dirname(@\readlink($path)));
118 \clearstatcache(\true, $path);
119 }
120 if (!\is_readable($path)) {
121 throw new CredentialsException("Failed to read authorization token from '{$path}': no such file or directory.");
122 }
123 $token = @\file_get_contents($path);
124 if (empty($token)) {
125 throw new CredentialsException("Invalid authorization token read from `{$path}`. Token file is empty!");
126 }
127 return $token;
128 }
129 return \getenv(self::ENV_AUTH_TOKEN);
130 }
131 /**
132 * Provides headers for credential metadata request.
133 *
134 * @return array|array[]|string[]
135 */
136 private function getHeadersForAuthToken()
137 {
138 $authToken = self::getEcsAuthToken();
139 $headers = [];
140 if (!empty($authToken)) {
141 $headers = ['Authorization' => $authToken];
142 }
143 return $headers;
144 }
145 /** @deprecated */
146 public function setHeaderForAuthToken()
147 {
148 $authToken = self::getEcsAuthToken();
149 $headers = [];
150 if (!empty($authToken)) {
151 $headers = ['Authorization' => $authToken];
152 }
153 return $headers;
154 }
155 /**
156 * Fetch container metadata URI from container environment variable.
157 *
158 * @return string Returns container metadata URI
159 */
160 private function getEcsUri()
161 {
162 $credsUri = \getenv(self::ENV_URI);
163 if ($credsUri === \false) {
164 $credsUri = $_SERVER[self::ENV_URI] ?? '';
165 }
166 if (empty($credsUri)) {
167 $credFullUri = \getenv(self::ENV_FULL_URI);
168 if ($credFullUri === \false) {
169 $credFullUri = $_SERVER[self::ENV_FULL_URI] ?? '';
170 }
171 if (!empty($credFullUri)) {
172 return $credFullUri;
173 }
174 }
175 return self::SERVER_URI . $credsUri;
176 }
177 private function decodeResult($response)
178 {
179 $result = \json_decode($response, \true);
180 if (!isset($result['AccessKeyId'])) {
181 throw new CredentialsException('Unexpected container metadata credentials value');
182 }
183 return $result;
184 }
185 /**
186 * Determines whether or not a given request URI is a valid
187 * container credential request URI.
188 *
189 * @param $uri
190 *
191 * @return bool
192 */
193 private function isCompatibleUri($uri)
194 {
195 $parsed = \parse_url($uri);
196 if ($parsed['scheme'] !== 'https') {
197 $host = \trim($parsed['host'], '[]');
198 $ecsHost = \parse_url(self::SERVER_URI)['host'];
199 $eksHost = self::EKS_SERVER_HOST_IPV4;
200 if ($host !== $ecsHost && $host !== $eksHost && $host !== self::EKS_SERVER_HOST_IPV6 && !CredentialsUtils::isLoopBackAddress(\gethostbyname($host))) {
201 return \false;
202 }
203 }
204 return \true;
205 }
206 }
207