Skip to content

Commit 5294f17

Browse files
jderusseNyholm
andauthored
Add a persisting cache layer for HTTP credentials (async-aws#600)
* Add a persisting cache layer for HTTP credentials * Add Psr Provider and remove default injection * Add example in doc * Apply suggestions * Integrate with Symfony * Fix CS * adjustExpireDate not nullable * Apply suggestions * Cache only expirable credentials Co-authored-by: Tobias Nyholm <[email protected]>
1 parent 2dac107 commit 5294f17

16 files changed

+215
-36
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"nette/php-generator": "^3.3",
3232
"nette/utils": "^3.0",
3333
"nyholm/symfony-bundle-test": "^1.6.1",
34+
"psr/cache": "^1.0",
3435
"symfony/cache": "^4.4 || ^5.0",
3536
"symfony/config": "^4.4 || ^5.0",
3637
"symfony/console": "^4.4 || ^5.0",

docs/authentication/index.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,25 @@ The providers are currently chained in the following order:
3030

3131
The default provider chain could be too slow or too complex for testing. It is recommended
3232
to use the `NullProvider` in tests where you don't provide valid configuration values.
33+
34+
## Caching credential
35+
36+
Fetching credential from AWS metadata endpoint (EC2 Instance, ECS Container,
37+
WebIdentity...) for each request can introduce latency. AsyncAws provides a
38+
`PsrCacheProvider` and a `SymfonyCacheProvider` decorator to persist and reuse
39+
credentials between each request.
40+
41+
```php
42+
use AsyncAws\Core\Credentials\CacheProvider;
43+
use AsyncAws\Core\Credentials\ChainProvider;
44+
use AsyncAws\Core\Credentials\SymfonyCacheProvider;
45+
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
46+
47+
$provider = ChainProvider::createDefaultChain();
48+
$provider = new SymfonyCacheProvider($provider, new FilesystemAdapter('test'));
49+
50+
// optional, decorate the provider with the in-memory Cache provider
51+
$provider = new CacheProvider($provider);
52+
53+
$s3 = new S3Client([], $provider);
54+
```

src/Core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
- Support for EventBridge in `AwsClientFactory`
88
- Support for IAM in `AwsClientFactory`
9+
- Add a `PsrCacheProvider` and `SymfonyCacheProvider` to persists crendentials in a cache pool
10+
- Add a `Credential::adjustExpireDate` method for adjusting the time according to the time difference with AWS clock
911
- Support for global and regional endpoints
1012

1113
## 1.1.0

src/Core/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"ext-SimpleXML": "*",
1616
"ext-hash": "*",
1717
"ext-json": "*",
18-
"psr/log": "~1.0",
18+
"psr/cache": "^1.0",
19+
"psr/log": "^1.0",
1920
"symfony/http-client": "^4.4 || ^5.0",
2021
"symfony/http-client-contracts": "^1.1.8 || ^2.0",
2122
"symfony/service-contracts": "^1.0 || ^2.0"

src/Core/src/AbstractApi.php

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,7 @@
66

77
use AsyncAws\Core\Credentials\CacheProvider;
88
use AsyncAws\Core\Credentials\ChainProvider;
9-
use AsyncAws\Core\Credentials\ConfigurationProvider;
10-
use AsyncAws\Core\Credentials\ContainerProvider;
119
use AsyncAws\Core\Credentials\CredentialProvider;
12-
use AsyncAws\Core\Credentials\IniFileProvider;
13-
use AsyncAws\Core\Credentials\InstanceProvider;
14-
use AsyncAws\Core\Credentials\WebIdentityProvider;
1510
use AsyncAws\Core\Exception\InvalidArgument;
1611
use AsyncAws\Core\Signer\Signer;
1712
use AsyncAws\Core\Signer\SignerV4;
@@ -68,13 +63,7 @@ public function __construct($configuration = [], ?CredentialProvider $credential
6863
$this->httpClient = $httpClient ?? HttpClient::create();
6964
$this->logger = $logger ?? new NullLogger();
7065
$this->configuration = $configuration;
71-
$this->credentialProvider = $credentialProvider ?? new CacheProvider(new ChainProvider([
72-
new ConfigurationProvider(),
73-
new WebIdentityProvider($this->logger),
74-
new IniFileProvider($this->logger),
75-
new ContainerProvider($this->httpClient, $this->logger),
76-
new InstanceProvider($this->httpClient, $this->logger),
77-
]));
66+
$this->credentialProvider = $credentialProvider ?? new CacheProvider(ChainProvider::createDefaultChain($this->httpClient, $this->logger));
7867
}
7968

8069
final public function getConfiguration(): Configuration

src/Core/src/AwsClientFactory.php

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,7 @@
1010
use AsyncAws\CognitoIdentityProvider\CognitoIdentityProviderClient;
1111
use AsyncAws\Core\Credentials\CacheProvider;
1212
use AsyncAws\Core\Credentials\ChainProvider;
13-
use AsyncAws\Core\Credentials\ConfigurationProvider;
14-
use AsyncAws\Core\Credentials\ContainerProvider;
1513
use AsyncAws\Core\Credentials\CredentialProvider;
16-
use AsyncAws\Core\Credentials\IniFileProvider;
17-
use AsyncAws\Core\Credentials\InstanceProvider;
18-
use AsyncAws\Core\Credentials\WebIdentityProvider;
1914
use AsyncAws\Core\Exception\InvalidArgument;
2015
use AsyncAws\Core\Exception\MissingDependency;
2116
use AsyncAws\Core\Sts\StsClient;
@@ -79,13 +74,7 @@ public function __construct($configuration = [], ?CredentialProvider $credential
7974
$this->httpClient = $httpClient ?? HttpClient::create();
8075
$this->logger = $logger ?? new NullLogger();
8176
$this->configuration = $configuration;
82-
$this->credentialProvider = $credentialProvider ?? new CacheProvider(new ChainProvider([
83-
new ConfigurationProvider(),
84-
new WebIdentityProvider($this->logger),
85-
new IniFileProvider($this->logger),
86-
new ContainerProvider($this->httpClient, $this->logger),
87-
new InstanceProvider($this->httpClient, $this->logger),
88-
]));
77+
$this->credentialProvider = $credentialProvider ?? new CacheProvider(ChainProvider::createDefaultChain($this->httpClient, $this->logger));
8978
}
9079

9180
public function cloudFormation(): CloudFormationClient

src/Core/src/Credentials/CacheProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use Symfony\Contracts\Service\ResetInterface;
99

1010
/**
11-
* Cache the Credential generated by the decorated CredentialProvider.
11+
* Cache the Credential generated by the decorated CredentialProvider in memory.
1212
*
1313
* The Credential will be reused until it expires.
1414
*

src/Core/src/Credentials/ChainProvider.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
namespace AsyncAws\Core\Credentials;
66

77
use AsyncAws\Core\Configuration;
8+
use Psr\Log\LoggerInterface;
9+
use Psr\Log\NullLogger;
10+
use Symfony\Component\HttpClient\HttpClient;
11+
use Symfony\Contracts\HttpClient\HttpClientInterface;
812
use Symfony\Contracts\Service\ResetInterface;
913

1014
/**
@@ -60,4 +64,18 @@ public function reset()
6064
{
6165
$this->lastSuccessfulProvider = [];
6266
}
67+
68+
public static function createDefaultChain(?HttpClientInterface $httpClient = null, ?LoggerInterface $logger = null): CredentialProvider
69+
{
70+
$httpClient = $httpClient ?? HttpClient::create();
71+
$logger = $logger ?? new NullLogger();
72+
73+
return new ChainProvider([
74+
new ConfigurationProvider(),
75+
new WebIdentityProvider($logger, null, $httpClient),
76+
new IniFileProvider($logger, null, $httpClient),
77+
new ContainerProvider($httpClient, $logger),
78+
new InstanceProvider($httpClient, $logger),
79+
]);
80+
}
6381
}

src/Core/src/Credentials/ContainerProvider.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,15 @@ public function getCredentials(Configuration $configuration): ?Credentials
6161
return null;
6262
}
6363

64+
if (null !== $date = $response->getHeaders(false)['date'][0] ?? null) {
65+
$date = new \DateTimeImmutable($date);
66+
}
67+
6468
return new Credentials(
6569
$result['AccessKeyId'],
6670
$result['SecretAccessKey'],
6771
$result['Token'],
68-
new \DateTimeImmutable($result['Expiration'])
72+
Credentials::adjustExpireDate(new \DateTimeImmutable($result['Expiration']), $date)
6973
);
7074
}
7175
}

src/Core/src/Credentials/Credentials.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
*/
1414
final class Credentials implements CredentialProvider
1515
{
16+
private const EXPIRATION_DRIFT = 30;
17+
1618
private $accessKeyId;
1719

1820
private $secretKey;
@@ -62,4 +64,13 @@ public function getCredentials(Configuration $configuration): ?Credentials
6264
{
6365
return $this->isExpired() ? null : $this;
6466
}
67+
68+
public static function adjustExpireDate(\DateTimeImmutable $expireDate, ?\DateTimeImmutable $reference = null): \DateTimeImmutable
69+
{
70+
if (null !== $reference) {
71+
$expireDate = (new \DateTimeImmutable())->add($reference->diff($expireDate));
72+
}
73+
74+
return $expireDate->sub(new \DateInterval(sprintf('PT%dS', self::EXPIRATION_DRIFT)));
75+
}
6576
}

0 commit comments

Comments
 (0)