123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- <?php
- namespace Aws\S3Control;
- use Aws\Api\Service;
- use Aws\Arn\AccessPointArnInterface;
- use Aws\Arn\ArnInterface;
- use Aws\Arn\ArnParser;
- use Aws\Arn\Exception\InvalidArnException;
- use Aws\Arn\S3\BucketArnInterface;
- use Aws\Arn\S3\OutpostsArnInterface;
- use Aws\CommandInterface;
- use Aws\Endpoint\PartitionEndpointProvider;
- use Aws\Exception\InvalidRegionException;
- use Aws\Exception\UnresolvedEndpointException;
- use Aws\S3\EndpointRegionHelperTrait;
- use GuzzleHttp\Psr7;
- use Psr\Http\Message\RequestInterface;
- /**
- * Checks for access point ARN in members targeting BucketName, modifying
- * endpoint as appropriate
- *
- * @internal
- */
- class EndpointArnMiddleware
- {
- use EndpointRegionHelperTrait;
- /**
- * Commands which do not do ARN expansion for a specific given shape name
- * @var array
- */
- private static $selectiveNonArnableCmds = [
- 'AccessPointName' => [
- 'CreateAccessPoint',
- ],
- 'BucketName' => [],
- ];
- /**
- * Commands which do not do ARN expansion at all for relevant members
- * @var array
- */
- private static $nonArnableCmds = [
- 'CreateBucket',
- 'ListRegionalBuckets',
- ];
- /**
- * Commands which trigger endpoint and signer redirection based on presence
- * of OutpostId
- * @var array
- */
- private static $outpostIdRedirectCmds = [
- 'CreateBucket',
- 'ListRegionalBuckets',
- ];
- /** @var callable */
- private $nextHandler;
- /** @var boolean */
- private $isUseEndpointV2;
- /**
- * Create a middleware wrapper function.
- *
- * @param Service $service
- * @param $region
- * @param array $config
- * @return callable
- */
- public static function wrap(
- Service $service,
- $region,
- array $config,
- $isUseEndpointV2
- )
- {
- return function (callable $handler) use ($service, $region, $config, $isUseEndpointV2) {
- return new self($handler, $service, $region, $config, $isUseEndpointV2);
- };
- }
- public function __construct(
- callable $nextHandler,
- Service $service,
- $region,
- array $config = [],
- $isUseEndpointV2 = false
- )
- {
- $this->partitionProvider = PartitionEndpointProvider::defaultProvider();
- $this->region = $region;
- $this->service = $service;
- $this->config = $config;
- $this->nextHandler = $nextHandler;
- $this->isUseEndpointV2 = $isUseEndpointV2;
- }
- public function __invoke(CommandInterface $cmd, RequestInterface $req)
- {
- $nextHandler = $this->nextHandler;
- $op = $this->service->getOperation($cmd->getName())->toArray();
- if (!empty($op['input']['shape'])
- && !in_array($cmd->getName(), self::$nonArnableCmds)
- ) {
- $service = $this->service->toArray();
- if (!empty($input = $service['shapes'][$op['input']['shape']])) {
- // Stores member name that targets 'BucketName' shape
- $bucketNameMember = null;
- // Stores member name that targets 'AccessPointName' shape
- $accesspointNameMember = null;
- foreach ($input['members'] as $key => $member) {
- if ($member['shape'] === 'BucketName') {
- $bucketNameMember = $key;
- }
- if ($member['shape'] === 'AccessPointName') {
- $accesspointNameMember = $key;
- }
- }
- // Determine if appropriate member contains ARN value and is
- // eligible for ARN expansion
- if (!is_null($bucketNameMember)
- && !empty($cmd[$bucketNameMember])
- && !in_array($cmd->getName(), self::$selectiveNonArnableCmds['BucketName'])
- && ArnParser::isArn($cmd[$bucketNameMember])
- ) {
- $arn = ArnParser::parse($cmd[$bucketNameMember]);
- !$this->isUseEndpointV2 && $partition = $this->validateBucketArn($arn);
- } elseif (!is_null($accesspointNameMember)
- && !empty($cmd[$accesspointNameMember])
- && !in_array($cmd->getName(), self::$selectiveNonArnableCmds['AccessPointName'])
- && ArnParser::isArn($cmd[$accesspointNameMember])
- ) {
- $arn = ArnParser::parse($cmd[$accesspointNameMember]);
- !$this->isUseEndpointV2 && $partition = $this->validateAccessPointArn($arn);
- }
- // Process only if an appropriate member contains an ARN value
- // and is an Outposts ARN
- if (!empty($arn) && $arn instanceof OutpostsArnInterface) {
- if (!$this->isUseEndpointV2) {
- // Generate host based on ARN
- $host = $this->generateOutpostsArnHost($arn, $req);
- $req = $req->withHeader('x-amz-outpost-id', $arn->getOutpostId());
- }
- // ARN replacement
- $path = $req->getUri()->getPath();
- if ($arn instanceof AccessPointArnInterface) {
- // Replace ARN with access point name
- $path = str_replace(
- urlencode($cmd[$accesspointNameMember]),
- $arn->getAccesspointName(),
- $path
- );
- // Replace ARN in the payload
- $req->getBody()->seek(0);
- $body = Psr7\Utils::streamFor(str_replace(
- $cmd[$accesspointNameMember],
- $arn->getAccesspointName(),
- $req->getBody()->getContents()
- ));
- // Replace ARN in the command
- $cmd[$accesspointNameMember] = $arn->getAccesspointName();
- } elseif ($arn instanceof BucketArnInterface) {
- // Replace ARN in the path
- $path = str_replace(
- urlencode($cmd[$bucketNameMember]),
- $arn->getBucketName(),
- $path
- );
- // Replace ARN in the payload
- $req->getBody()->seek(0);
- $newBody = str_replace(
- $cmd[$bucketNameMember],
- $arn->getBucketName(),
- $req->getBody()->getContents()
- );
- $body = Psr7\Utils::streamFor($newBody);
- // Replace ARN in the command
- $cmd[$bucketNameMember] = $arn->getBucketName();
- }
- // Validate or set account ID in command
- if (isset($cmd['AccountId'])) {
- if ($cmd['AccountId'] !== $arn->getAccountId()) {
- throw new \InvalidArgumentException("The account ID"
- . " supplied in the command ({$cmd['AccountId']})"
- . " does not match the account ID supplied in the"
- . " ARN (" . $arn->getAccountId() . ").");
- }
- } else {
- $cmd['AccountId'] = $arn->getAccountId();
- }
- // Set modified request
- if (isset($body)) {
- $req = $req->withBody($body);
- }
- if ($this->isUseEndpointV2) {
- $req = $req->withUri($req->getUri()->withPath($path));
- goto next;
- }
- $req = $req
- ->withUri($req->getUri()->withHost($host)->withPath($path))
- ->withHeader('x-amz-account-id', $arn->getAccountId());
- // Update signing region based on ARN data if configured to do so
- if ($this->config['use_arn_region']->isUseArnRegion()) {
- $region = $arn->getRegion();
- } else {
- $region = $this->region;
- }
- $endpointData = $partition([
- 'region' => $region,
- 'service' => $arn->getService()
- ]);
- $cmd['@context']['signing_region'] = $endpointData['signingRegion'];
- // Update signing service for Outposts ARNs
- if ($arn instanceof OutpostsArnInterface) {
- $cmd['@context']['signing_service'] = $arn->getService();
- }
- }
- }
- }
- if ($this->isUseEndpointV2) {
- goto next;
- }
- // For operations that redirect endpoint & signing service based on
- // presence of OutpostId member. These operations will likely not
- // overlap with operations that perform ARN expansion.
- if (in_array($cmd->getName(), self::$outpostIdRedirectCmds)
- && !empty($cmd['OutpostId'])
- ) {
- $req = $req->withUri(
- $req->getUri()->withHost($this->generateOutpostIdHost())
- );
- $cmd['@context']['signing_service'] = 's3-outposts';
- }
- next:
- return $nextHandler($cmd, $req);
- }
- private function generateOutpostsArnHost(
- OutpostsArnInterface $arn,
- RequestInterface $req
- ) {
- if (!empty($this->config['use_arn_region']->isUseArnRegion())) {
- $region = $arn->getRegion();
- } else {
- $region = $this->region;
- }
- $fipsString = $this->config['use_fips_endpoint']->isUseFipsEndpoint()
- ? "-fips"
- : "";
- $suffix = $this->getPartitionSuffix($arn, $this->partitionProvider);
- return "s3-outposts{$fipsString}.{$region}.{$suffix}";
- }
- private function generateOutpostIdHost()
- {
- $partition = $this->partitionProvider->getPartition(
- $this->region,
- $this->service->getEndpointPrefix()
- );
- $suffix = $partition->getDnsSuffix();
- return "s3-outposts.{$this->region}.{$suffix}";
- }
- private function validateBucketArn(ArnInterface $arn)
- {
- if ($arn instanceof BucketArnInterface) {
- return $this->validateArn($arn);
- }
- throw new InvalidArnException('Provided ARN was not a valid S3 bucket'
- . ' ARN.');
- }
- private function validateAccessPointArn(ArnInterface $arn)
- {
- if ($arn instanceof AccessPointArnInterface) {
- return $this->validateArn($arn);
- }
- throw new InvalidArnException('Provided ARN was not a valid S3 access'
- . ' point ARN.');
- }
- /**
- * Validates an ARN, returning a partition object corresponding to the ARN
- * if successful
- *
- * @param $arn
- * @return \Aws\Endpoint\Partition
- */
- private function validateArn(ArnInterface $arn)
- {
- // Dualstack is not supported with Outposts ARNs
- if ($arn instanceof OutpostsArnInterface
- && !empty($this->config['dual_stack'])
- ) {
- throw new UnresolvedEndpointException(
- 'Dualstack is currently not supported with S3 Outposts ARNs.'
- . ' Please disable dualstack or do not supply an Outposts ARN.');
- }
- // Get partitions for ARN and client region
- $arnPart = $this->partitionProvider->getPartitionByName(
- $arn->getPartition()
- );
- $clientPart = $this->partitionProvider->getPartition(
- $this->region,
- 's3'
- );
- // If client partition not found, try removing pseudo-region qualifiers
- if (!($clientPart->isRegionMatch($this->region, 's3'))) {
- $clientPart = $this->partitionProvider->getPartition(
- \Aws\strip_fips_pseudo_regions($this->region),
- 's3'
- );
- }
- // Verify that the partition matches for supplied partition and region
- if ($arn->getPartition() !== $clientPart->getName()) {
- throw new InvalidRegionException('The supplied ARN partition'
- . " does not match the client's partition.");
- }
- if ($clientPart->getName() !== $arnPart->getName()) {
- throw new InvalidRegionException('The corresponding partition'
- . ' for the supplied ARN region does not match the'
- . " client's partition.");
- }
- // Ensure ARN region matches client region unless
- // configured for using ARN region over client region
- $this->validateMatchingRegion($arn);
- // Ensure it is not resolved to fips pseudo-region for S3 Outposts
- $this->validateFipsConfigurations($arn);
- return $arnPart;
- }
- }
|