Skip to content

Commit ba4cbd7

Browse files
CSharpRUcmlaragosunilgoderigotechnologymkrauser
authored
dev to main (#64)
* LeaseTrait::$leaseId and LeaseTrait::getLeaseId() should return a string not int. (#58) fixes #57 * LeaseTrait::$leaseId and LeaseTrait::getLeaseId() should return a string not int. (#58) (#59) (#60) fixes #57 Co-authored-by: Conrad Lara <cmlara@cmlara.com> Co-authored-by: Conrad Lara <cmlara@cmlara.com> * Add php 8.1 and 8.2 (#62) * add php 8.1 * add php 8.2 --------- Co-authored-by: Sunil Soprey <sunil@derigotech.com> * add AWS IAM Authentication strategy (#61) --------- Co-authored-by: Conrad Lara <cmlara@cmlara.com> Co-authored-by: Sunil Soprey <gosunilgo@users.noreply.github.com> Co-authored-by: Sunil Soprey <sunil@derigotech.com> Co-authored-by: Matthias Krauser <matthias@krauser.eu>
1 parent 918bfff commit ba4cbd7

File tree

5 files changed

+258
-2
lines changed

5 files changed

+258
-2
lines changed

.github/workflows/tests.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ jobs:
1515
- '7.3'
1616
- '7.4'
1717
- '8.0'
18+
- '8.1'
19+
- '8.2'
1820
steps:
1921
- name: Checkout code
2022
uses: actions/checkout@v2
@@ -64,4 +66,4 @@ jobs:
6466
with:
6567
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
6668
coverage-reports: tests/_output/coverage.xml
67-
69+

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"psr/cache": "^1.0|^2.0|^3.0",
2424
"psr/log": "^1.0|^2.0|^3.0",
2525
"psr/http-client": "^1.0",
26-
"psr/http-factory": "^1.0"
26+
"psr/http-factory": "^1.0",
27+
"aws/aws-sdk-php": "^3.0"
2728
},
2829
"require-dev": {
2930
"codeception/codeception": "^4.1",
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
namespace Vault\AuthenticationStrategies;
4+
5+
use Aws\Middleware;
6+
use Aws\Sts\StsClient;
7+
use Psr\Http\Message\RequestInterface;
8+
use Psr\Http\Client\ClientExceptionInterface;
9+
use Vault\Exceptions\AuthenticationException;
10+
use Vault\ResponseModels\Auth;
11+
12+
/**
13+
* Class AwsIamAuthenticationStrategy
14+
*
15+
* @package Vault\AuthenticationStrategy
16+
*/
17+
class AwsIamAuthenticationStrategy extends AbstractAuthenticationStrategy
18+
{
19+
/**
20+
* @var string
21+
*/
22+
protected $methodPathSegment = 'aws';
23+
24+
/** @var string */
25+
private $role;
26+
27+
/** @var string */
28+
private $region;
29+
30+
/** @var ?string */
31+
private $serverId;
32+
33+
/**
34+
* @param string $role The name of the vault role
35+
* @param string $region The AWS region to use
36+
* @param ?string $vaultServerId If set, this string is used as X-Vault-AWS-IAM-Server-ID, to protect against replay attacks
37+
* @param ?StsClient $stsClient Custom instance of StsClient
38+
*/
39+
public function __construct(
40+
$role,
41+
$region,
42+
$vaultServerId = null,
43+
$stsClient = null
44+
) {
45+
$this->role = $role;
46+
$this->region = $region;
47+
$this->serverId = $vaultServerId;
48+
$this->stsClient = $stsClient;
49+
}
50+
51+
/**
52+
* Returns auth for further interactions with Vault.
53+
*
54+
* @return Auth
55+
* @throws AuthenticationException
56+
* @throws ClientExceptionInterface
57+
*/
58+
public function authenticate(): Auth
59+
{
60+
if (!$this->methodPathSegment) {
61+
throw new AuthenticationException('methodPathSegment must be set before usage');
62+
}
63+
64+
if (!$this->stsClient) {
65+
$this->stsClient = new StsClient([
66+
'region' => $this->region,
67+
'version' => 'latest',
68+
'sts_regional_endpoints' => 'regional',
69+
]);
70+
}
71+
72+
73+
// Creating a signed command, to get the parameters for the actual login-request to vault
74+
$command = $this->stsClient->getCommand('GetCallerIdentity');
75+
76+
if ($this->serverId) {
77+
$command->getHandlerList()->appendBuild(
78+
Middleware::mapRequest(function (RequestInterface $request) {
79+
return $request->withHeader('X-Vault-AWS-IAM-Server-ID', $this->serverId);
80+
}),
81+
'add-header'
82+
);
83+
}
84+
85+
$request = \Aws\serialize($command);
86+
87+
$response = $this->client->write(
88+
sprintf('/auth/%s/login', $this->methodPathSegment),
89+
[
90+
'role' => $this->role,
91+
'iam_http_request_method' => $request->getMethod(),
92+
'iam_request_url' => base64_encode($request->getUri()),
93+
'iam_request_body' => base64_encode($request->getBody()),
94+
'iam_request_headers' => base64_encode(json_encode($request->getHeaders())),
95+
]
96+
);
97+
98+
return $response->getAuth();
99+
}
100+
101+
/**
102+
* @return string
103+
*/
104+
public function getMethodPathSegment(): string
105+
{
106+
return $this->methodPathSegment;
107+
}
108+
109+
/**
110+
* @param string $methodPathSegment
111+
*
112+
* @return static
113+
*/
114+
public function setMethodPathSegment(string $methodPathSegment)
115+
{
116+
$this->methodPathSegment = $methodPathSegment;
117+
118+
return $this;
119+
}
120+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
2+
-
3+
request:
4+
method: POST
5+
url: 'http://127.0.0.1:8200/v1/auth/aws/login'
6+
headers:
7+
Host: '127.0.0.1:8200'
8+
Expect: null
9+
Accept-Encoding: null
10+
User-Agent: VaultPHP/1.0.0
11+
Content-Type: application/json
12+
Accept: null
13+
body: '{"role":"dev-role","iam_http_request_method":"POST","iam_request_url":"aHR0cHM6Ly9zdHMuZXUtY2VudHJhbC0xLmFtYXpvbmF3cy5jb20=","iam_request_body":"QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNQ==","iam_request_headers":"eyJIb3N0IjpbInN0cy5ldS1jZW50cmFsLTEuYW1hem9uYXdzLmNvbSJdLCJDb250ZW50LUxlbmd0aCI6WyI0MyJdLCJDb250ZW50LVR5cGUiOlsiYXBwbGljYXRpb25cL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCJdLCJYLUFtei1Vc2VyLUFnZW50IjpbImF3cy1zZGstcGhwXC8zLjI2MC4zIE9TXC9EYXJ3aW5cLzIyLjEuMCBsYW5nXC9waHBcLzguMC4yNyJdLCJVc2VyLUFnZW50IjpbImF3cy1zZGstcGhwXC8zLjI2MC4zIE9TXC9EYXJ3aW5cLzIyLjEuMCBsYW5nXC9waHBcLzguMC4yNyJdLCJYLVZhdWx0LUFXUy1JQU0tU2VydmVyLUlEIjpbImxvY2FsaG9zdCJdLCJhd3Mtc2RrLXJldHJ5IjpbIjBcLzAiXX0="}'
14+
response:
15+
status:
16+
http_version: '1.1'
17+
code: '200'
18+
message: OK
19+
headers:
20+
Cache-Control: no-store
21+
Content-Type: application/json
22+
Date: 'Tue, 01 Aug 2017 06:54:33 GMT'
23+
Content-Length: '366'
24+
body: "{\"request_id\":\"08962062-3aab-fd89-3413-99c3521ecc75\",\"lease_id\":\"\",\"renewable\":false,\"lease_duration\":0,\"data\":null,\"wrap_info\":null,\"warnings\":null,\"auth\":{\"client_token\":\"9e64c3b8-01f7-7a64-1575-30a91f7d1ae4\",\"accessor\":\"033ccada-d332-9415-0acc-0ec51e81dbd1\",\"policies\":[\"default\",\"test\"],\"metadata\":{\"username\":\"test\"},\"lease_duration\":2764800,\"renewable\":true}}\n"
25+
-
26+
request:
27+
method: GET
28+
url: 'http://127.0.0.1:8200/v1/auth/token/lookup-self'
29+
headers:
30+
Host: '127.0.0.1:8200'
31+
Accept-Encoding: null
32+
User-Agent: VaultPHP/1.0.0
33+
Content-Type: application/json
34+
X-Vault-Token: 9e64c3b8-01f7-7a64-1575-30a91f7d1ae4
35+
Accept: null
36+
response:
37+
status:
38+
http_version: '1.1'
39+
code: '200'
40+
message: OK
41+
headers:
42+
Cache-Control: no-store
43+
Content-Type: application/json
44+
Date: 'Tue, 01 Aug 2017 06:54:33 GMT'
45+
Content-Length: '504'
46+
body: "{\"request_id\":\"ab3f3dc9-3633-0c18-44e2-f3f58cb56d0a\",\"lease_id\":\"\",\"renewable\":false,\"lease_duration\":0,\"data\":{\"accessor\":\"033ccada-d332-9415-0acc-0ec51e81dbd1\",\"creation_time\":1501570473,\"creation_ttl\":2764800,\"display_name\":\"userpass-test\",\"explicit_max_ttl\":0,\"id\":\"9e64c3b8-01f7-7a64-1575-30a91f7d1ae4\",\"meta\":{\"username\":\"test\"},\"num_uses\":0,\"orphan\":true,\"path\":\"auth/userpass/login/test\",\"policies\":[\"default\",\"test\"],\"renewable\":true,\"ttl\":2764800},\"wrap_info\":null,\"warnings\":null,\"auth\":null}\n"
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
use Aws\Sts\StsClient;
4+
use Codeception\Test\Unit;
5+
use Psr\Http\Client\ClientExceptionInterface;
6+
use Vault\AuthenticationStrategies\AwsIamAuthenticationStrategy;
7+
use Vault\Client;
8+
use VCR\VCR;
9+
use Laminas\Diactoros\RequestFactory;
10+
use Laminas\Diactoros\StreamFactory;
11+
use Laminas\Diactoros\Uri;
12+
13+
class AwsIamAuthenticationStrategyTest extends Unit
14+
{
15+
/**
16+
* @var UnitTester
17+
*/
18+
protected $tester;
19+
20+
/**
21+
* @throws \Psr\Cache\InvalidArgumentException
22+
* @throws ClientExceptionInterface
23+
* @throws \Vault\Exceptions\RuntimeException
24+
*/
25+
public function testCanAuthenticate(): void
26+
{
27+
$client = new Client(
28+
new Uri('http://127.0.0.1:8200'),
29+
new \AlexTartan\GuzzlePsr18Adapter\Client(),
30+
new RequestFactory(),
31+
new StreamFactory()
32+
);
33+
34+
$stsClient = new StsClient([
35+
'region' => 'eu-central-1',
36+
'version' => 'latest',
37+
'sts_regional_endpoints' => 'regional'
38+
]);
39+
// These middlewares would break the test, due to some dynamic headers
40+
$stsClient->getHandlerList()->remove('invocation-id');
41+
$stsClient->getHandlerList()->remove('signer');
42+
43+
$client->setAuthenticationStrategy(
44+
new AwsIamAuthenticationStrategy(
45+
'dev-role',
46+
'eu-central-1',
47+
'localhost',
48+
$stsClient
49+
)
50+
);
51+
52+
$this->assertEquals($client->getAuthenticationStrategy()->getClient(), $client);
53+
$this->assertTrue($client->authenticate());
54+
$this->assertNotEmpty($client->getToken());
55+
$this->assertNotEmpty($client->getToken()->getAuth()->getLeaseDuration());
56+
$this->assertNotEmpty($client->getToken()->getAuth()->isRenewable());
57+
}
58+
59+
protected function setUp(): void
60+
{
61+
VCR::turnOn();
62+
VCR::configure()->setMode(VCR::MODE_ONCE);
63+
64+
VCR::insertCassette('authentication-strategies/aws-iam');
65+
66+
parent::setUp();
67+
}
68+
69+
protected function tearDown(): void
70+
{
71+
// To stop recording requests, eject the cassette
72+
VCR::eject();
73+
74+
// Turn off VCR to stop intercepting requests
75+
VCR::turnOff();
76+
77+
parent::tearDown();
78+
}
79+
80+
protected function _before()
81+
{
82+
}
83+
84+
protected function _after()
85+
{
86+
}
87+
}

0 commit comments

Comments
 (0)