PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / trunk
Booking for Appointments and Events Calendar – Amelia vtrunk
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / vendor / google / auth / src / CredentialSource / AwsNativeSource.php
ameliabooking / vendor / google / auth / src / CredentialSource Last commit date
AwsNativeSource.php 6 months ago FileSource.php 6 months ago UrlSource.php 6 months ago
AwsNativeSource.php
361 lines
1 <?php
2 /*
3 * Copyright 2023 Google Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 namespace AmeliaVendor\Google\Auth\CredentialSource;
19
20 use AmeliaVendor\Google\Auth\ExternalAccountCredentialSourceInterface;
21 use AmeliaVendor\Google\Auth\HttpHandler\HttpClientCache;
22 use AmeliaVendor\Google\Auth\HttpHandler\HttpHandlerFactory;
23 use AmeliaVendor\GuzzleHttp\Psr7\Request;
24
25 /**
26 * Authenticates requests using AWS credentials.
27 */
28 class AwsNativeSource implements ExternalAccountCredentialSourceInterface
29 {
30 private const CRED_VERIFICATION_QUERY = 'Action=GetCallerIdentity&Version=2011-06-15';
31
32 private string $audience;
33 private string $regionalCredVerificationUrl;
34 private ?string $regionUrl;
35 private ?string $securityCredentialsUrl;
36 private ?string $imdsv2SessionTokenUrl;
37
38 /**
39 * @param string $audience The audience for the credential.
40 * @param string $regionalCredVerificationUrl The regional AWS GetCallerIdentity action URL used to determine the
41 * AWS account ID and its roles. This is not called by this library, but
42 * is sent in the subject token to be called by the STS token server.
43 * @param string|null $regionUrl This URL should be used to determine the current AWS region needed for the signed
44 * request construction.
45 * @param string|null $securityCredentialsUrl The AWS metadata server URL used to retrieve the access key, secret
46 * key and security token needed to sign the GetCallerIdentity request.
47 * @param string|null $imdsv2SessionTokenUrl Presence of this URL enforces the auth libraries to fetch a Session
48 * Token from AWS. This field is required for EC2 instances using IMDSv2.
49 */
50 public function __construct(
51 string $audience,
52 string $regionalCredVerificationUrl,
53 ?string $regionUrl = null,
54 ?string $securityCredentialsUrl = null,
55 ?string $imdsv2SessionTokenUrl = null
56 ) {
57 $this->audience = $audience;
58 $this->regionalCredVerificationUrl = $regionalCredVerificationUrl;
59 $this->regionUrl = $regionUrl;
60 $this->securityCredentialsUrl = $securityCredentialsUrl;
61 $this->imdsv2SessionTokenUrl = $imdsv2SessionTokenUrl;
62 }
63
64 public function fetchSubjectToken(?callable $httpHandler = null): string
65 {
66 if (is_null($httpHandler)) {
67 $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient());
68 }
69
70 $headers = [];
71 if ($this->imdsv2SessionTokenUrl) {
72 $headers = [
73 'X-aws-ec2-metadata-token' => self::getImdsV2SessionToken($this->imdsv2SessionTokenUrl, $httpHandler)
74 ];
75 }
76
77 if (!$signingVars = self::getSigningVarsFromEnv()) {
78 if (!$this->securityCredentialsUrl) {
79 throw new \LogicException('Unable to get credentials from ENV, and no security credentials URL provided');
80 }
81 $signingVars = self::getSigningVarsFromUrl(
82 $httpHandler,
83 $this->securityCredentialsUrl,
84 self::getRoleName($httpHandler, $this->securityCredentialsUrl, $headers),
85 $headers
86 );
87 }
88
89 if (!$region = self::getRegionFromEnv()) {
90 if (!$this->regionUrl) {
91 throw new \LogicException('Unable to get region from ENV, and no region URL provided');
92 }
93 $region = self::getRegionFromUrl($httpHandler, $this->regionUrl, $headers);
94 }
95 $url = str_replace('{region}', $region, $this->regionalCredVerificationUrl);
96 $host = parse_url($url)['host'] ?? '';
97
98 // From here we use the signing vars to create the signed request to receive a token
99 [$accessKeyId, $secretAccessKey, $securityToken] = $signingVars;
100 $headers = self::getSignedRequestHeaders($region, $host, $accessKeyId, $secretAccessKey, $securityToken);
101
102 // Inject x-goog-cloud-target-resource into header
103 $headers['x-goog-cloud-target-resource'] = $this->audience;
104
105 // Format headers as they're expected in the subject token
106 $formattedHeaders= array_map(
107 fn ($k, $v) => ['key' => $k, 'value' => $v],
108 array_keys($headers),
109 $headers,
110 );
111
112 $request = [
113 'headers' => $formattedHeaders,
114 'method' => 'POST',
115 'url' => $url,
116 ];
117
118 return urlencode(json_encode($request) ?: '');
119 }
120
121 /**
122 * @internal
123 */
124 public static function getImdsV2SessionToken(string $imdsV2Url, callable $httpHandler): string
125 {
126 $headers = [
127 'X-aws-ec2-metadata-token-ttl-seconds' => '21600'
128 ];
129 $request = new Request(
130 'PUT',
131 $imdsV2Url,
132 $headers
133 );
134
135 $response = $httpHandler($request);
136 return (string) $response->getBody();
137 }
138
139 /**
140 * @see http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
141 *
142 * @internal
143 *
144 * @return array<string, string>
145 */
146 public static function getSignedRequestHeaders(
147 string $region,
148 string $host,
149 string $accessKeyId,
150 string $secretAccessKey,
151 ?string $securityToken
152 ): array {
153 $service = 'sts';
154
155 # Create a date for headers and the credential string in ISO-8601 format
156 $amzdate = gmdate('Ymd\THis\Z');
157 $datestamp = gmdate('Ymd'); # Date w/o time, used in credential scope
158
159 # Create the canonical headers and signed headers. Header names
160 # must be trimmed and lowercase, and sorted in code point order from
161 # low to high. Note that there is a trailing \n.
162 $canonicalHeaders = sprintf("host:%s\nx-amz-date:%s\n", $host, $amzdate);
163 if ($securityToken) {
164 $canonicalHeaders .= sprintf("x-amz-security-token:%s\n", $securityToken);
165 }
166
167 # Step 5: Create the list of signed headers. This lists the headers
168 # in the canonicalHeaders list, delimited with ";" and in alpha order.
169 # Note: The request can include any headers; $canonicalHeaders and
170 # $signedHeaders lists those that you want to be included in the
171 # hash of the request. "Host" and "x-amz-date" are always required.
172 $signedHeaders = 'host;x-amz-date';
173 if ($securityToken) {
174 $signedHeaders .= ';x-amz-security-token';
175 }
176
177 # Step 6: Create payload hash (hash of the request body content). For GET
178 # requests, the payload is an empty string ("").
179 $payloadHash = hash('sha256', '');
180
181 # Step 7: Combine elements to create canonical request
182 $canonicalRequest = implode("\n", [
183 'POST', // method
184 '/', // canonical URL
185 self::CRED_VERIFICATION_QUERY, // query string
186 $canonicalHeaders,
187 $signedHeaders,
188 $payloadHash
189 ]);
190
191 # ************* TASK 2: CREATE THE STRING TO SIGN*************
192 # Match the algorithm to the hashing algorithm you use, either SHA-1 or
193 # SHA-256 (recommended)
194 $algorithm = 'AWS4-HMAC-SHA256';
195 $scope = implode('/', [$datestamp, $region, $service, 'aws4_request']);
196 $stringToSign = implode("\n", [$algorithm, $amzdate, $scope, hash('sha256', $canonicalRequest)]);
197
198 # ************* TASK 3: CALCULATE THE SIGNATURE *************
199 # Create the signing key using the function defined above.
200 // (done above)
201 $signingKey = self::getSignatureKey($secretAccessKey, $datestamp, $region, $service);
202
203 # Sign the string_to_sign using the signing_key
204 $signature = bin2hex(self::hmacSign($signingKey, $stringToSign));
205
206 # ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
207 # The signing information can be either in a query string value or in
208 # a header named Authorization. This code shows how to use a header.
209 # Create authorization header and add to request headers
210 $authorizationHeader = sprintf(
211 '%s Credential=%s/%s, SignedHeaders=%s, Signature=%s',
212 $algorithm,
213 $accessKeyId,
214 $scope,
215 $signedHeaders,
216 $signature
217 );
218
219 # The request can include any headers, but MUST include "host", "x-amz-date",
220 # and (for this scenario) "Authorization". "host" and "x-amz-date" must
221 # be included in the canonical_headers and signed_headers, as noted
222 # earlier. Order here is not significant.
223 $headers = [
224 'host' => $host,
225 'x-amz-date' => $amzdate,
226 'Authorization' => $authorizationHeader,
227 ];
228 if ($securityToken) {
229 $headers['x-amz-security-token'] = $securityToken;
230 }
231
232 return $headers;
233 }
234
235 /**
236 * @internal
237 */
238 public static function getRegionFromEnv(): ?string
239 {
240 $region = getenv('AWS_REGION');
241 if (empty($region)) {
242 $region = getenv('AWS_DEFAULT_REGION');
243 }
244 return $region ?: null;
245 }
246
247 /**
248 * @internal
249 *
250 * @param callable $httpHandler
251 * @param string $regionUrl
252 * @param array<string, string|string[]> $headers Request headers to send in with the request.
253 */
254 public static function getRegionFromUrl(callable $httpHandler, string $regionUrl, array $headers): string
255 {
256 // get the region/zone from the region URL
257 $regionRequest = new Request('GET', $regionUrl, $headers);
258 $regionResponse = $httpHandler($regionRequest);
259
260 // Remove last character. For example, if us-east-2b is returned,
261 // the region would be us-east-2.
262 return substr((string) $regionResponse->getBody(), 0, -1);
263 }
264
265 /**
266 * @internal
267 *
268 * @param callable $httpHandler
269 * @param string $securityCredentialsUrl
270 * @param array<string, string|string[]> $headers Request headers to send in with the request.
271 */
272 public static function getRoleName(callable $httpHandler, string $securityCredentialsUrl, array $headers): string
273 {
274 // Get the AWS role name
275 $roleRequest = new Request('GET', $securityCredentialsUrl, $headers);
276 $roleResponse = $httpHandler($roleRequest);
277 $roleName = (string) $roleResponse->getBody();
278
279 return $roleName;
280 }
281
282 /**
283 * @internal
284 *
285 * @param callable $httpHandler
286 * @param string $securityCredentialsUrl
287 * @param array<string, string|string[]> $headers Request headers to send in with the request.
288 * @return array{string, string, ?string}
289 */
290 public static function getSigningVarsFromUrl(
291 callable $httpHandler,
292 string $securityCredentialsUrl,
293 string $roleName,
294 array $headers
295 ): array {
296 // Get the AWS credentials
297 $credsRequest = new Request(
298 'GET',
299 $securityCredentialsUrl . '/' . $roleName,
300 $headers
301 );
302 $credsResponse = $httpHandler($credsRequest);
303 $awsCreds = json_decode((string) $credsResponse->getBody(), true);
304 return [
305 $awsCreds['AccessKeyId'], // accessKeyId
306 $awsCreds['SecretAccessKey'], // secretAccessKey
307 $awsCreds['Token'], // token
308 ];
309 }
310
311 /**
312 * @internal
313 *
314 * @return array{string, string, ?string}
315 */
316 public static function getSigningVarsFromEnv(): ?array
317 {
318 $accessKeyId = getenv('AWS_ACCESS_KEY_ID');
319 $secretAccessKey = getenv('AWS_SECRET_ACCESS_KEY');
320 if ($accessKeyId && $secretAccessKey) {
321 return [
322 $accessKeyId,
323 $secretAccessKey,
324 getenv('AWS_SESSION_TOKEN') ?: null, // session token (can be null)
325 ];
326 }
327
328 return null;
329 }
330
331 /**
332 * Return HMAC hash in binary string
333 */
334 private static function hmacSign(string $key, string $msg): string
335 {
336 return hash_hmac('sha256', self::utf8Encode($msg), $key, true);
337 }
338
339 /**
340 * @TODO add a fallback when mbstring is not available
341 */
342 private static function utf8Encode(string $string): string
343 {
344 return mb_convert_encoding($string, 'UTF-8', 'ISO-8859-1');
345 }
346
347 private static function getSignatureKey(
348 string $key,
349 string $dateStamp,
350 string $regionName,
351 string $serviceName
352 ): string {
353 $kDate = self::hmacSign(self::utf8Encode('AWS4' . $key), $dateStamp);
354 $kRegion = self::hmacSign($kDate, $regionName);
355 $kService = self::hmacSign($kRegion, $serviceName);
356 $kSigning = self::hmacSign($kService, 'aws4_request');
357
358 return $kSigning;
359 }
360 }
361