Partition.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. <?php
  2. namespace Aws\Endpoint;
  3. use ArrayAccess;
  4. use Aws\HasDataTrait;
  5. use Aws\Sts\RegionalEndpoints\ConfigurationProvider;
  6. use Aws\S3\RegionalEndpoint\ConfigurationProvider as S3ConfigurationProvider;
  7. use InvalidArgumentException as Iae;
  8. /**
  9. * Default implementation of an AWS partition.
  10. */
  11. final class Partition implements ArrayAccess, PartitionInterface
  12. {
  13. use HasDataTrait;
  14. private $stsLegacyGlobalRegions = [
  15. 'ap-northeast-1',
  16. 'ap-south-1',
  17. 'ap-southeast-1',
  18. 'ap-southeast-2',
  19. 'aws-global',
  20. 'ca-central-1',
  21. 'eu-central-1',
  22. 'eu-north-1',
  23. 'eu-west-1',
  24. 'eu-west-2',
  25. 'eu-west-3',
  26. 'sa-east-1',
  27. 'us-east-1',
  28. 'us-east-2',
  29. 'us-west-1',
  30. 'us-west-2',
  31. ];
  32. /**
  33. * The partition constructor accepts the following options:
  34. *
  35. * - `partition`: (string, required) The partition name as specified in an
  36. * ARN (e.g., `aws`)
  37. * - `partitionName`: (string) The human readable name of the partition
  38. * (e.g., "AWS Standard")
  39. * - `dnsSuffix`: (string, required) The DNS suffix of the partition. This
  40. * value is used to determine how endpoints in the partition are resolved.
  41. * - `regionRegex`: (string) A PCRE regular expression that specifies the
  42. * pattern that region names in the endpoint adhere to.
  43. * - `regions`: (array, required) A map of the regions in the partition.
  44. * Each key is the region as present in a hostname (e.g., `us-east-1`),
  45. * and each value is a structure containing region information.
  46. * - `defaults`: (array) A map of default key value pairs to apply to each
  47. * endpoint of the partition. Any value in an `endpoint` definition will
  48. * supersede any values specified in `defaults`.
  49. * - `services`: (array, required) A map of service endpoint prefix name
  50. * (the value found in a hostname) to information about the service.
  51. *
  52. * @param array $definition
  53. *
  54. * @throws Iae if any required options are missing
  55. */
  56. public function __construct(array $definition)
  57. {
  58. foreach (['partition', 'regions', 'services', 'dnsSuffix'] as $key) {
  59. if (!isset($definition[$key])) {
  60. throw new Iae("Partition missing required $key field");
  61. }
  62. }
  63. $this->data = $definition;
  64. }
  65. public function getName()
  66. {
  67. return $this->data['partition'];
  68. }
  69. /**
  70. * @internal
  71. * @return mixed
  72. */
  73. public function getDnsSuffix()
  74. {
  75. return $this->data['dnsSuffix'];
  76. }
  77. public function isRegionMatch($region, $service)
  78. {
  79. if (isset($this->data['regions'][$region])
  80. || isset($this->data['services'][$service]['endpoints'][$region])
  81. ) {
  82. return true;
  83. }
  84. if (isset($this->data['regionRegex'])) {
  85. return (bool) preg_match(
  86. "@{$this->data['regionRegex']}@",
  87. $region
  88. );
  89. }
  90. return false;
  91. }
  92. public function getAvailableEndpoints(
  93. $service,
  94. $allowNonRegionalEndpoints = false
  95. ) {
  96. if ($this->isServicePartitionGlobal($service)) {
  97. return [$this->getPartitionEndpoint($service)];
  98. }
  99. if (isset($this->data['services'][$service]['endpoints'])) {
  100. $serviceRegions = array_keys(
  101. $this->data['services'][$service]['endpoints']
  102. );
  103. return $allowNonRegionalEndpoints
  104. ? $serviceRegions
  105. : array_intersect($serviceRegions, array_keys(
  106. $this->data['regions']
  107. ));
  108. }
  109. return [];
  110. }
  111. public function __invoke(array $args = [])
  112. {
  113. $service = isset($args['service']) ? $args['service'] : '';
  114. $region = isset($args['region']) ? $args['region'] : '';
  115. $scheme = isset($args['scheme']) ? $args['scheme'] : 'https';
  116. $options = isset($args['options']) ? $args['options'] : [];
  117. $data = $this->getEndpointData($service, $region, $options);
  118. $variant = $this->getVariant($options, $data);
  119. if (isset($variant['hostname'])) {
  120. $template = $variant['hostname'];
  121. } else {
  122. $template = isset($data['hostname']) ? $data['hostname'] : '';
  123. }
  124. $dnsSuffix = isset($variant['dnsSuffix'])
  125. ? $variant['dnsSuffix']
  126. : $this->data['dnsSuffix'];
  127. return [
  128. 'endpoint' => "{$scheme}://" . $this->formatEndpoint(
  129. $template,
  130. $service,
  131. $region,
  132. $dnsSuffix
  133. ),
  134. 'signatureVersion' => $this->getSignatureVersion($data),
  135. 'signingRegion' => isset($data['credentialScope']['region'])
  136. ? $data['credentialScope']['region']
  137. : $region,
  138. 'signingName' => isset($data['credentialScope']['service'])
  139. ? $data['credentialScope']['service']
  140. : $service,
  141. ];
  142. }
  143. private function getEndpointData($service, $region, $options)
  144. {
  145. $defaultRegion = $this->resolveRegion($service, $region, $options);
  146. $data = isset($this->data['services'][$service]['endpoints'][$defaultRegion])
  147. ? $this->data['services'][$service]['endpoints'][$defaultRegion]
  148. : [];
  149. $data += isset($this->data['services'][$service]['defaults'])
  150. ? $this->data['services'][$service]['defaults']
  151. : [];
  152. $data += isset($this->data['defaults'])
  153. ? $this->data['defaults']
  154. : [];
  155. return $data;
  156. }
  157. private function getSignatureVersion(array $data)
  158. {
  159. static $supportedBySdk = [
  160. 's3v4',
  161. 'v4',
  162. 'anonymous',
  163. ];
  164. $possibilities = array_intersect(
  165. $supportedBySdk,
  166. isset($data['signatureVersions'])
  167. ? $data['signatureVersions']
  168. : ['v4']
  169. );
  170. return array_shift($possibilities);
  171. }
  172. private function resolveRegion($service, $region, $options)
  173. {
  174. if (isset($this->data['services'][$service]['endpoints'][$region])
  175. && $this->isFipsEndpointUsed($region)
  176. ) {
  177. return $region;
  178. }
  179. if ($this->isServicePartitionGlobal($service)
  180. || $this->isStsLegacyEndpointUsed($service, $region, $options)
  181. || $this->isS3LegacyEndpointUsed($service, $region, $options)
  182. ) {
  183. return $this->getPartitionEndpoint($service);
  184. }
  185. return $region;
  186. }
  187. private function isServicePartitionGlobal($service)
  188. {
  189. return isset($this->data['services'][$service]['isRegionalized'])
  190. && false === $this->data['services'][$service]['isRegionalized']
  191. && isset($this->data['services'][$service]['partitionEndpoint']);
  192. }
  193. /**
  194. * STS legacy endpoints used for valid regions unless option is explicitly
  195. * set to 'regional'
  196. *
  197. * @param string $service
  198. * @param string $region
  199. * @param array $options
  200. * @return bool
  201. */
  202. private function isStsLegacyEndpointUsed($service, $region, $options)
  203. {
  204. return $service === 'sts'
  205. && in_array($region, $this->stsLegacyGlobalRegions)
  206. && (empty($options['sts_regional_endpoints'])
  207. || ConfigurationProvider::unwrap(
  208. $options['sts_regional_endpoints']
  209. )->getEndpointsType() !== 'regional'
  210. );
  211. }
  212. /**
  213. * S3 legacy us-east-1 endpoint used for valid regions unless option is explicitly
  214. * set to 'regional'
  215. *
  216. * @param string $service
  217. * @param string $region
  218. * @param array $options
  219. * @return bool
  220. */
  221. private function isS3LegacyEndpointUsed($service, $region, $options)
  222. {
  223. return $service === 's3'
  224. && $region === 'us-east-1'
  225. && (empty($options['s3_us_east_1_regional_endpoint'])
  226. || S3ConfigurationProvider::unwrap(
  227. $options['s3_us_east_1_regional_endpoint']
  228. )->getEndpointsType() !== 'regional'
  229. );
  230. }
  231. private function getPartitionEndpoint($service)
  232. {
  233. return $this->data['services'][$service]['partitionEndpoint'];
  234. }
  235. private function formatEndpoint($template, $service, $region, $dnsSuffix)
  236. {
  237. return strtr($template, [
  238. '{service}' => $service,
  239. '{region}' => $region,
  240. '{dnsSuffix}' => $dnsSuffix,
  241. ]);
  242. }
  243. /**
  244. * @param $region
  245. * @return bool
  246. */
  247. private function isFipsEndpointUsed($region)
  248. {
  249. return strpos($region, "fips") !== false;
  250. }
  251. /**
  252. * @param array $options
  253. * @param array $data
  254. * @return array
  255. */
  256. private function getVariant(array $options, array $data)
  257. {
  258. $variantTags = [];
  259. if (isset($options['use_fips_endpoint'])) {
  260. $useFips = $options['use_fips_endpoint'];
  261. if (is_bool($useFips)) {
  262. $useFips && $variantTags[] = 'fips';
  263. } elseif ($useFips->isUseFipsEndpoint()) {
  264. $variantTags[] = 'fips';
  265. }
  266. }
  267. if (isset($options['use_dual_stack_endpoint'])) {
  268. $useDualStack = $options['use_dual_stack_endpoint'];
  269. if (is_bool($useDualStack)) {
  270. $useDualStack && $variantTags[] = 'dualstack';
  271. } elseif ($useDualStack->isUseDualStackEndpoint()) {
  272. $variantTags[] = 'dualstack';
  273. }
  274. }
  275. if (!empty($variantTags)) {
  276. if (isset($data['variants'])) {
  277. foreach ($data['variants'] as $variant) {
  278. if (array_count_values($variant['tags']) == array_count_values($variantTags)) {
  279. return $variant;
  280. }
  281. }
  282. }
  283. if (isset($this->data['defaults']['variants'])) {
  284. foreach ($this->data['defaults']['variants'] as $variant) {
  285. if (array_count_values($variant['tags']) == array_count_values($variantTags)) {
  286. return $variant;
  287. }
  288. }
  289. }
  290. }
  291. }
  292. }