From ea3f4b6392629b60bf3fc1e6f6a970a3760a3911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=BCbner?= Date: Fri, 24 Jun 2022 18:05:56 +0200 Subject: [PATCH] Sanitize host name for AWS requests before signing in AWSAuthV4 transport (#2090) --- CHANGELOG.md | 1 + phpstan-baseline.neon | 4 ++++ src/Transport/AwsAuthV4.php | 23 +++++++++++++++++++++-- tests/Transport/AwsAuthV4Test.php | 31 +++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) mode change 100755 => 100644 src/Transport/AwsAuthV4.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 45301a8361..385e28f2ce 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Bumped `elasticsearch/elasticsearch` to `7.10` to be able to use `OpenPointInTime` class [#2113](https://github.com/ruflin/Elastica/pull/2113) * Updated `php-cs-fixer` to `3.9.5` [#2110](https://github.com/ruflin/Elastica/pull/2110) * Changed `Elastica\Index\Settings::get` adding ability to get default settings by @krasilnikovm [#2115](https://github.com/ruflin/Elastica/pull/2115) +* Update `AWSAuthV4 transport` to sanitize host name for AWS requests before signing [#2090](https://github.com/ruflin/Elastica/pull/2090) ### Deprecated ### Removed * Removed `CallbackStrategyTestHelper` and `ErrorsCollector` from `tests` [#2111](https://github.com/ruflin/Elastica/pull/2111) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d2acebd88b..f41a145076 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -135,3 +135,7 @@ parameters: count: 1 path: tests/SnapshotTest.php + - + message: "#^Function GuzzleHttp\\\\Psr7\\\\modify_request not found\\.$#" + count: 1 + path: src/Transport/AwsAuthV4.php diff --git a/src/Transport/AwsAuthV4.php b/src/Transport/AwsAuthV4.php old mode 100755 new mode 100644 index da25ed112a..04f8cdc017 --- a/src/Transport/AwsAuthV4.php +++ b/src/Transport/AwsAuthV4.php @@ -10,6 +10,7 @@ use GuzzleHttp\Client; use GuzzleHttp\HandlerStack; use GuzzleHttp\Middleware; +use GuzzleHttp\Psr7; use Psr\Http\Message\RequestInterface; class AwsAuthV4 extends Guzzle @@ -42,15 +43,33 @@ private function getSigningMiddleware(): callable : \getenv('AWS_REGION'); $signer = new SignatureV4('es', $region); $credProvider = $this->getCredentialProvider(); + $transport = $this; return Middleware::mapRequest(static function (RequestInterface $req) use ( $signer, - $credProvider + $credProvider, + $transport ) { - return $signer->signRequest($req, $credProvider()->wait()); + return $signer->signRequest($transport->sanitizeRequest($req), $credProvider()->wait()); }); } + private function sanitizeRequest(RequestInterface $request): RequestInterface + { + // Trailing dots are valid parts of DNS host names (see RFC 1034), + // but interferes with header signing where AWS expects a stripped host name. + if ('.' === \substr($request->getHeader('host')[0], -1)) { + $changes = ['set_headers' => ['host' => \rtrim($request->getHeader('host')[0], '.')]]; + if (\class_exists(Psr7\Utils::class)) { + $request = Psr7\Utils::modifyRequest($request, $changes); + } else { + $request = Psr7\modify_request($request, $changes); + } + } + + return $request; + } + private function getCredentialProvider(): callable { $connection = $this->getConnection(); diff --git a/tests/Transport/AwsAuthV4Test.php b/tests/Transport/AwsAuthV4Test.php index 577d7d4281..61206d7db8 100644 --- a/tests/Transport/AwsAuthV4Test.php +++ b/tests/Transport/AwsAuthV4Test.php @@ -205,4 +205,35 @@ public function testSignsWithEnvironmentalCredentials(): void ); } } + + /** + * @group unit + * @depends testSignsWithProvidedCredentials + */ + public function testStripsTrailingDotInHost(): void + { + $host = $this->_getHost(); + $hostWithTrailingDot = $host.'.'; + + $config = [ + 'persistent' => false, + 'transport' => 'AwsAuthV4', + 'aws_access_key_id' => 'foo', + 'aws_secret_access_key' => 'bar', + 'aws_session_token' => 'baz', + 'aws_region' => 'us-east-1', + 'host' => $hostWithTrailingDot, + ]; + $client = $this->_getClient($config); + + try { + $client->request('_stats'); + } catch (GuzzleException $e) { + $guzzleException = $e->getGuzzleException(); + $this->assertInstanceOf(ConnectException::class, $guzzleException); + $request = $guzzleException->getRequest(); + $this->assertSame($host, $request->getHeader('host')[0]); + $this->assertSame($hostWithTrailingDot, $request->getUri()->getHost()); + } + } }