Skip to content

Commit

Permalink
Enable S3 express auth (#2839)
Browse files Browse the repository at this point in the history
Co-authored-by: SamRemis <sjremis94@amazon.com>
  • Loading branch information
SamRemis and SamRemis authored Nov 28, 2023
1 parent 716d803 commit 434fadb
Show file tree
Hide file tree
Showing 32 changed files with 3,586 additions and 795 deletions.
7 changes: 7 additions & 0 deletions .changes/nextrelease/feature-s3express-auth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"type": "feature",
"category": "S3",
"description": "This release adds support for the S3express identity provider"
}
]
6 changes: 5 additions & 1 deletion src/AwsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,11 @@ private function addSignatureMiddleware()
return SignatureProvider::resolve($provider, $version, $name, $region);
};
$this->handlerList->appendSign(
Middleware::signer($this->credentialProvider, $resolver, $this->tokenProvider),
Middleware::signer($this->credentialProvider,
$resolver,
$this->tokenProvider,
$this->getConfig()
),
'signer'
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ public function setAuthSchemes(array $authSchemes)
* Get auth schemes added to command as required
* for endpoint resolution
*
* @returns array | null
* @returns array
*/
public function getAuthSchemes()
{
return $this->authSchemes;
return $this->authSchemes ?: [];
}

/** @deprecated */
Expand Down
3 changes: 2 additions & 1 deletion src/EndpointV2/EndpointV2SerializerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ private function applyAuthSchemeToCommand($endpoint, $command)

private function selectAuthScheme($authSchemes)
{
$validAuthSchemes = ['sigv4', 'sigv4a', 'none', 'bearer'];
$validAuthSchemes = ['sigv4', 'sigv4a', 'none', 'bearer', 'sigv4-s3express'];
$invalidAuthSchemes = [];

foreach($authSchemes as $authScheme) {
Expand Down Expand Up @@ -204,6 +204,7 @@ private function normalizeAuthScheme($authScheme)
if (isset($authScheme['disableDoubleEncoding'])
&& $authScheme['disableDoubleEncoding'] === true
&& $authScheme['name'] !== 'sigv4a'
&& $authScheme['name'] !== 'sigv4-s3express'
) {
$normalizedAuthScheme['version'] = 's3v4';
} elseif ($authScheme['name'] === 'none') {
Expand Down
6 changes: 6 additions & 0 deletions src/Identity/S3/S3ExpressIdentity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php
namespace Aws\Identity\S3;

use Aws\Credentials\Credentials;

class S3ExpressIdentity extends Credentials {}
44 changes: 44 additions & 0 deletions src/Identity/S3/S3ExpressIdentityProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
namespace Aws\Identity\S3;

use Aws;
use Aws\LruArrayCache;
use GuzzleHttp\Promise;

class S3ExpressIdentityProvider
{

private $cache;
private $s3Client;
public function __construct(string $clientRegion, array $config = [])
{
$this->cache = new LruArrayCache(100);
$this->s3Client = isset($config['client'])
? $config['client'] // internal use only
: new Aws\S3\S3Client([
'region' => $clientRegion,
'disable_express_session_auth' => true
]);
}

public function __invoke($command)
{
return function () use ($command) {
$bucket = $command['Bucket'];
if ($identity = $this->cache->get($bucket)) {
if (!$identity->isExpired()) {
return Promise\Create::promiseFor($identity);
}
}
$response = $this->s3Client->createSession(['Bucket' => $bucket]);
$identity = new Aws\Identity\S3\S3ExpressIdentity(
$response['Credentials']['AccessKeyId'],
$response['Credentials']['SecretAccessKey'],
$response['Credentials']['SessionToken'],
$response['Credentials']['Expiration']->getTimestamp()
);
$this->cache->set($bucket, $identity);
return Promise\Create::promiseFor($identity);
};
}
}
31 changes: 19 additions & 12 deletions src/Middleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Aws\Credentials\CredentialsInterface;
use Aws\EndpointV2\EndpointProviderV2;
use Aws\Exception\AwsException;
use Aws\Signature\S3ExpressSignature;
use Aws\Token\TokenAuthorization;
use Aws\Token\TokenInterface;
use GuzzleHttp\Promise;
Expand Down Expand Up @@ -125,13 +126,13 @@ public static function requestBuilder(
*
* @return callable
*/
public static function signer(callable $credProvider, callable $signatureFunction, $tokenProvider = null)
public static function signer(callable $credProvider, callable $signatureFunction, $tokenProvider = null, $config = [])
{
return function (callable $handler) use ($signatureFunction, $credProvider, $tokenProvider) {
return function (callable $handler) use ($signatureFunction, $credProvider, $tokenProvider, $config) {
return function (
CommandInterface $command,
RequestInterface $request
) use ($handler, $signatureFunction, $credProvider, $tokenProvider) {
) use ($handler, $signatureFunction, $credProvider, $tokenProvider, $config) {
$signer = $signatureFunction($command);
if ($signer instanceof TokenAuthorization) {
return $tokenProvider()->then(
Expand All @@ -143,17 +144,23 @@ function (TokenInterface $token)
);
}
);
}

if ($signer instanceof S3ExpressSignature) {
$credentialPromise = $config['s3_express_identity_provider']($command)();
} else {
return $credProvider()->then(
function (CredentialsInterface $creds)
use ($handler, $command, $signer, $request) {
return $handler(
$command,
$signer->signRequest($request, $creds)
);
}
);
$credentialPromise = $credProvider();
}

return $credentialPromise->then(
function (CredentialsInterface $creds)
use ($handler, $command, $signer, $request) {
return $handler(
$command,
$signer->signRequest($request, $creds)
);
}
);
};
};
}
Expand Down
53 changes: 42 additions & 11 deletions src/S3/ApplyChecksumMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use GuzzleHttp\Psr7;
use InvalidArgumentException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;

/**
* Apply required or optional checksums to requests before sending.
Expand Down Expand Up @@ -79,11 +80,7 @@ public function __invoke(
if (is_array($supportedAlgorithms)
&& in_array($requestedAlgorithm, $supportedAlgorithms)
) {
$headerName = "x-amz-checksum-{$requestedAlgorithm}";
$encoded = $this->getEncodedValue($requestedAlgorithm, $body);
if (!$request->hasHeader($headerName)) {
$request = $request->withHeader($headerName, $encoded);
}
$request = $this->addAlgorithmHeader($requestedAlgorithm, $request, $body);
} else {
throw new InvalidArgumentException(
"Unsupported algorithm supplied for input variable {$checksumMemberName}."
Expand All @@ -99,14 +96,19 @@ public function __invoke(
$checksumRequired = isset($checksumInfo['requestChecksumRequired'])
? $checksumInfo['requestChecksumRequired']
: null;
if ((!empty($checksumRequired) && !$request->hasHeader('Content-MD5'))
if ((!empty($checksumRequired))
|| (in_array($name, self::$sha256AndMd5) && $addContentMD5)
) {
// Set the content MD5 header for operations that require it.
$request = $request->withHeader(
'Content-MD5',
base64_encode(Psr7\Utils::hash($body, 'md5', true))
);
//S3Express doesn't support MD5; default to crc32 instead
if ($this->isS3Express($command)) {
$request = $this->addAlgorithmHeader('crc32', $request, $body);
} elseif (!$request->hasHeader('Content-MD5')) {
// Set the content MD5 header for operations that require it.
$request = $request->withHeader(
'Content-MD5',
base64_encode(Psr7\Utils::hash($body, 'md5', true))
);
}
return $next($command, $request);
}
}
Expand All @@ -121,4 +123,33 @@ public function __invoke(

return $next($command, $request);
}

/**
* @param string $requestedAlgorithm
* @param RequestInterface $request
* @param StreamInterface $body
* @return RequestInterface
*/
private function addAlgorithmHeader(
string $requestedAlgorithm,
RequestInterface $request,
StreamInterface $body
) {
$headerName = "x-amz-checksum-{$requestedAlgorithm}";
if (!$request->hasHeader($headerName)) {
$encoded = $this->getEncodedValue($requestedAlgorithm, $body);
$request = $request->withHeader($headerName, $encoded);
}
return $request;
}

/**
* @param CommandInterface $command
* @return bool
*/
private function isS3Express($command): bool
{
$authSchemes = $command->getAuthSchemes();
return isset($authSchemes['name']) && $authSchemes['name'] == 's3express';
}
}
Loading

0 comments on commit 434fadb

Please sign in to comment.