Skip to content

Commit 57a1225

Browse files
committed
Add Auth Token Generator for ElastiCache IAM authentication
1 parent 6ad549d commit 57a1225

File tree

3 files changed

+204
-0
lines changed

3 files changed

+204
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"type": "feature",
4+
"category": "ElastiCache",
5+
"description": "Add Aws\\ElastiCache\\AuthTokenGenerator class to generate IAM authentication tokens for ElastiCache for Redis."
6+
}
7+
]
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace Aws\ElastiCache;
4+
5+
use Aws;
6+
use Aws\Credentials\Credentials;
7+
use Aws\Credentials\CredentialsInterface;
8+
use Aws\Signature\SignatureV4;
9+
use GuzzleHttp\Promise;
10+
use GuzzleHttp\Psr7\Request;
11+
use GuzzleHttp\Psr7\Uri;
12+
13+
/**
14+
* Generates auth tokens for use with IAM authentication.
15+
*/
16+
class AuthTokenGenerator
17+
{
18+
private $credentialProvider;
19+
20+
/**
21+
* The constructor takes an instance of Credentials or a CredentialProvider.
22+
*
23+
* @param callable|Credentials $creds
24+
*/
25+
public function __construct($creds)
26+
{
27+
if ($creds instanceof CredentialsInterface) {
28+
$promise = new Promise\FulfilledPromise($creds);
29+
$this->credentialProvider = Aws\constantly($promise);
30+
} else {
31+
$this->credentialProvider = $creds;
32+
}
33+
}
34+
35+
/**
36+
* Create the token for ElastiCache login.
37+
*
38+
* @param string $replication_group_id The replication group id
39+
* @param string $region The region where the ElastiCache cluster is located
40+
* @param string $username The username to login as
41+
* @param int $lifetime The lifetime of the token in minutes
42+
*
43+
* @return string Token generated
44+
*/
45+
public function createToken($replication_group_id, $region, $username, $lifetime = 15)
46+
{
47+
if (! is_numeric($lifetime) || $lifetime > 15 || $lifetime <= 0) {
48+
throw new \InvalidArgumentException(
49+
"Lifetime must be a positive number less than or equal to 15, was {$lifetime}",
50+
null
51+
);
52+
}
53+
54+
$uri = new Uri();
55+
$uri = $uri->withHost($replication_group_id);
56+
$uri = $uri->withPath('/');
57+
$uri = $uri->withQuery('Action=connect&User='.$username);
58+
59+
$request = new Request('GET', $uri);
60+
$signer = new SignatureV4('elasticache', $region);
61+
$provider = $this->credentialProvider;
62+
63+
$url = (string) $signer->presign(
64+
$request,
65+
$provider()->wait(),
66+
'+'.$lifetime.' minutes'
67+
)->getUri();
68+
69+
// Remove 2 extra slash from the presigned url result
70+
return substr($url, 2);
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
namespace Aws\Test\ElastiCache;
3+
4+
use Aws\Credentials\Credentials;
5+
use Aws\ElastiCache\AuthTokenGenerator;
6+
use GuzzleHttp\Promise;
7+
use Yoast\PHPUnitPolyfills\TestCases\TestCase;
8+
9+
/**
10+
* @covers Aws\ElastiCache\AuthTokenGenerator
11+
*/
12+
class AuthTokenGeneratorTest extends TestCase
13+
{
14+
public function testCanCreateAuthTokenWthCredentialInstance()
15+
{
16+
$creds = new Credentials('foo', 'bar', 'baz');
17+
$connect = new AuthTokenGenerator($creds);
18+
$token = $connect->createToken(
19+
'my-replication-group',
20+
'us-west-2',
21+
'myRedisUser'
22+
);
23+
24+
$this->assertStringContainsString('my-replication-group', $token);
25+
$this->assertStringContainsString('us-west-2', $token);
26+
$this->assertStringContainsString('X-Amz-Credential=foo', $token);
27+
$this->assertStringContainsString('X-Amz-Expires=900', $token);
28+
$this->assertStringContainsString('X-Amz-SignedHeaders=host', $token);
29+
$this->assertStringContainsString('User=myRedisUser', $token);
30+
$this->assertStringContainsString('Action=connect', $token);
31+
}
32+
33+
public function testCanCreateAuthTokenWthCredentialProvider()
34+
{
35+
$accessKeyId = 'AKID';
36+
$secretKeyId = 'SECRET';
37+
$provider = function () use ($accessKeyId, $secretKeyId) {
38+
return Promise\Create::promiseFor(
39+
new Credentials($accessKeyId, $secretKeyId)
40+
);
41+
};
42+
43+
$connect = new AuthTokenGenerator($provider);
44+
$token = $connect->createToken(
45+
'my-replication-group',
46+
'us-west-2',
47+
'myRedisUser'
48+
);
49+
50+
$this->assertStringContainsString('my-replication-group', $token);
51+
$this->assertStringContainsString('us-west-2', $token);
52+
$this->assertStringContainsString('X-Amz-Credential=AKID', $token);
53+
$this->assertStringContainsString('X-Amz-Expires=900', $token);
54+
$this->assertStringContainsString('X-Amz-SignedHeaders=host', $token);
55+
$this->assertStringContainsString('User=myRedisUser', $token);
56+
$this->assertStringContainsString('Action=connect', $token);
57+
}
58+
59+
public function lifetimeProvider()
60+
{
61+
return [
62+
[1],
63+
[14],
64+
['14'],
65+
[15],
66+
];
67+
}
68+
69+
/**
70+
* @dataProvider lifetimeProvider
71+
*
72+
* @param $lifetime
73+
*/
74+
public function testCanCreateAuthTokenWthNonDefaultLifetime($lifetime)
75+
{
76+
$creds = new Credentials('foo', 'bar', 'baz');
77+
$connect = new AuthTokenGenerator($creds);
78+
$token = $connect->createToken(
79+
'my-replication-group',
80+
'us-west-2',
81+
'myRedisUser',
82+
$lifetime
83+
);
84+
$lifetimeInSeconds = $lifetime * 60;
85+
$this->assertStringContainsString('my-replication-group', $token);
86+
$this->assertStringContainsString('us-west-2', $token);
87+
$this->assertStringContainsString('X-Amz-Credential=foo', $token);
88+
$this->assertStringContainsString("X-Amz-Expires={$lifetimeInSeconds}", $token);
89+
$this->assertStringContainsString('X-Amz-SignedHeaders=host', $token);
90+
$this->assertStringContainsString('User=myRedisUser', $token);
91+
$this->assertStringContainsString('Action=connect', $token);
92+
}
93+
94+
public function lifetimeFailureProvider()
95+
{
96+
return [
97+
[0],
98+
['0'],
99+
[''],
100+
[16],
101+
['16'],
102+
[10000],
103+
[null],
104+
];
105+
}
106+
107+
/**
108+
* @dataProvider lifetimeFailureProvider
109+
*
110+
* @param $lifetime
111+
*/
112+
public function testThrowsExceptionWithInvalidLifetime($lifetime)
113+
{
114+
$this->expectExceptionMessage("Lifetime must be a positive number less than or equal to 15, was");
115+
$this->expectException(\InvalidArgumentException::class);
116+
$creds = new Credentials('foo', 'bar', 'baz');
117+
$connect = new AuthTokenGenerator($creds);
118+
$connect->createToken(
119+
'my-replication-group',
120+
'us-west-2',
121+
'myRedisUser',
122+
$lifetime
123+
);
124+
}
125+
}

0 commit comments

Comments
 (0)